opentelemetry_application_insights/
logs.rs1use 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}