opentelemetry_application_insights/
logs.rs

1use crate::{
2    convert::{
3        attrs_map_to_properties, attrs_to_map, attrs_to_properties, time_to_string, AttrValue,
4    },
5    models::{Data, Envelope, ExceptionData, ExceptionDetails, MessageData, SeverityLevel},
6    tags::get_tags_for_log,
7    Exporter,
8};
9use opentelemetry::{logs::Severity, InstrumentationScope};
10use opentelemetry_http::HttpClient;
11use opentelemetry_sdk::{
12    error::OTelSdkResult,
13    logs::{LogBatch, LogExporter, SdkLogRecord},
14    Resource,
15};
16use opentelemetry_semantic_conventions as semcov;
17use std::{sync::Arc, time::SystemTime};
18
19fn is_exception(record: &SdkLogRecord) -> bool {
20    record.attributes_iter().any(|(k, _)| {
21        k.as_str() == semcov::trace::EXCEPTION_TYPE
22            || k.as_str() == semcov::trace::EXCEPTION_MESSAGE
23    })
24}
25
26impl<C> Exporter<C> {
27    fn create_envelope_for_log(
28        &self,
29        (record, instrumentation_scope): (&SdkLogRecord, &InstrumentationScope),
30    ) -> Envelope {
31        let event_resource = if self.resource_attributes_in_events_and_logs {
32            Some(&self.resource)
33        } else {
34            None
35        };
36        let (data, name) = if is_exception(record) {
37            (
38                Data::Exception(RecordAndResource(record, event_resource).into()),
39                "Microsoft.ApplicationInsights.Exception",
40            )
41        } else {
42            (
43                Data::Message(RecordAndResource(record, event_resource).into()),
44                "Microsoft.ApplicationInsights.Message",
45            )
46        };
47
48        Envelope {
49            name,
50            time: time_to_string(
51                record
52                    .timestamp()
53                    .or(record.observed_timestamp())
54                    .unwrap_or_else(SystemTime::now),
55            )
56            .into(),
57            sample_rate: None,
58            i_key: Some(self.instrumentation_key.clone().into()),
59            tags: Some(get_tags_for_log(
60                record,
61                instrumentation_scope,
62                &self.resource,
63            )),
64            data: Some(data),
65        }
66    }
67}
68
69#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
70impl<C> LogExporter for Exporter<C>
71where
72    C: HttpClient + 'static,
73{
74    fn export(
75        &self,
76        batch: LogBatch<'_>,
77    ) -> impl std::future::Future<Output = OTelSdkResult> + Send {
78        let client = Arc::clone(&self.client);
79        let endpoint = Arc::clone(&self.track_endpoint);
80        let envelopes: Vec<_> = batch
81            .iter()
82            .map(|log| self.create_envelope_for_log(log))
83            .collect();
84
85        async move {
86            crate::uploader::send(
87                client.as_ref(),
88                endpoint.as_ref(),
89                envelopes,
90                self.retry_notify.clone(),
91            )
92            .await
93            .map_err(Into::into)
94        }
95    }
96
97    fn set_resource(&mut self, resource: &Resource) {
98        self.resource = resource.clone();
99    }
100}
101
102impl From<Severity> for SeverityLevel {
103    fn from(severity: Severity) -> Self {
104        match severity {
105            Severity::Trace
106            | Severity::Trace2
107            | Severity::Trace3
108            | Severity::Trace4
109            | Severity::Debug
110            | Severity::Debug2
111            | Severity::Debug3
112            | Severity::Debug4 => SeverityLevel::Verbose,
113            Severity::Info | Severity::Info2 | Severity::Info3 | Severity::Info4 => {
114                SeverityLevel::Information
115            }
116            Severity::Warn | Severity::Warn2 | Severity::Warn3 | Severity::Warn4 => {
117                SeverityLevel::Warning
118            }
119            Severity::Error | Severity::Error2 | Severity::Error3 | Severity::Error4 => {
120                SeverityLevel::Error
121            }
122            Severity::Fatal | Severity::Fatal2 | Severity::Fatal3 | Severity::Fatal4 => {
123                SeverityLevel::Critical
124            }
125        }
126    }
127}
128
129struct RecordAndResource<'a>(&'a SdkLogRecord, Option<&'a Resource>);
130
131impl From<RecordAndResource<'_>> for ExceptionData {
132    fn from(RecordAndResource(record, resource): RecordAndResource) -> ExceptionData {
133        let mut attrs = attrs_to_map(record.attributes_iter());
134        let exception = ExceptionDetails {
135            type_name: attrs
136                .remove(semcov::trace::EXCEPTION_TYPE)
137                .map(Into::into)
138                .unwrap_or_else(|| "".into()),
139            message: attrs
140                .remove(semcov::trace::EXCEPTION_MESSAGE)
141                .map(Into::into)
142                .unwrap_or_else(|| "".into()),
143            stack: attrs
144                .remove(semcov::trace::EXCEPTION_STACKTRACE)
145                .map(Into::into),
146        };
147        ExceptionData {
148            ver: 2,
149            exceptions: vec![exception],
150            severity_level: record.severity_number().map(Into::into),
151            properties: attrs_map_to_properties(attrs, resource),
152        }
153    }
154}
155
156impl From<RecordAndResource<'_>> for MessageData {
157    fn from(RecordAndResource(record, resource): RecordAndResource) -> MessageData {
158        MessageData {
159            ver: 2,
160            severity_level: record.severity_number().map(Into::into),
161            message: record
162                .body()
163                .as_ref()
164                .map(|v| v.as_str().into_owned())
165                .unwrap_or_else(|| "".into())
166                .into(),
167            properties: attrs_to_properties(
168                record.attributes_iter(),
169                resource,
170                #[cfg(feature = "trace")]
171                &[],
172            ),
173        }
174    }
175}