opentelemetry_proto/transform/
logs.rs
1#[cfg(feature = "gen-tonic-messages")]
2pub mod tonic {
3 use crate::{
4 tonic::{
5 common::v1::{
6 any_value::Value, AnyValue, ArrayValue, InstrumentationScope, KeyValue,
7 KeyValueList,
8 },
9 logs::v1::{LogRecord, ResourceLogs, ScopeLogs, SeverityNumber},
10 resource::v1::Resource,
11 Attributes,
12 },
13 transform::common::{to_nanos, tonic::ResourceAttributesWithSchema},
14 };
15 use opentelemetry::logs::{AnyValue as LogsAnyValue, Severity};
16 use std::borrow::Cow;
17 use std::collections::HashMap;
18
19 impl From<LogsAnyValue> for AnyValue {
20 fn from(value: LogsAnyValue) -> Self {
21 AnyValue {
22 value: Some(value.into()),
23 }
24 }
25 }
26
27 impl From<LogsAnyValue> for Value {
28 fn from(value: LogsAnyValue) -> Self {
29 match value {
30 LogsAnyValue::Double(f) => Value::DoubleValue(f),
31 LogsAnyValue::Int(i) => Value::IntValue(i),
32 LogsAnyValue::String(s) => Value::StringValue(s.into()),
33 LogsAnyValue::Boolean(b) => Value::BoolValue(b),
34 LogsAnyValue::ListAny(v) => Value::ArrayValue(ArrayValue {
35 values: v
36 .into_iter()
37 .map(|v| AnyValue {
38 value: Some(v.into()),
39 })
40 .collect(),
41 }),
42 LogsAnyValue::Map(m) => Value::KvlistValue(KeyValueList {
43 values: m
44 .into_iter()
45 .map(|(key, value)| KeyValue {
46 key: key.into(),
47 value: Some(AnyValue {
48 value: Some(value.into()),
49 }),
50 })
51 .collect(),
52 }),
53 LogsAnyValue::Bytes(v) => Value::BytesValue(v),
54 }
55 }
56 }
57
58 impl From<opentelemetry_sdk::logs::LogRecord> for LogRecord {
59 fn from(log_record: opentelemetry_sdk::logs::LogRecord) -> Self {
60 let trace_context = log_record.trace_context.as_ref();
61 let severity_number = match log_record.severity_number {
62 Some(Severity::Trace) => SeverityNumber::Trace,
63 Some(Severity::Trace2) => SeverityNumber::Trace2,
64 Some(Severity::Trace3) => SeverityNumber::Trace3,
65 Some(Severity::Trace4) => SeverityNumber::Trace4,
66 Some(Severity::Debug) => SeverityNumber::Debug,
67 Some(Severity::Debug2) => SeverityNumber::Debug2,
68 Some(Severity::Debug3) => SeverityNumber::Debug3,
69 Some(Severity::Debug4) => SeverityNumber::Debug4,
70 Some(Severity::Info) => SeverityNumber::Info,
71 Some(Severity::Info2) => SeverityNumber::Info2,
72 Some(Severity::Info3) => SeverityNumber::Info3,
73 Some(Severity::Info4) => SeverityNumber::Info4,
74 Some(Severity::Warn) => SeverityNumber::Warn,
75 Some(Severity::Warn2) => SeverityNumber::Warn2,
76 Some(Severity::Warn3) => SeverityNumber::Warn3,
77 Some(Severity::Warn4) => SeverityNumber::Warn4,
78 Some(Severity::Error) => SeverityNumber::Error,
79 Some(Severity::Error2) => SeverityNumber::Error2,
80 Some(Severity::Error3) => SeverityNumber::Error3,
81 Some(Severity::Error4) => SeverityNumber::Error4,
82 Some(Severity::Fatal) => SeverityNumber::Fatal,
83 Some(Severity::Fatal2) => SeverityNumber::Fatal2,
84 Some(Severity::Fatal3) => SeverityNumber::Fatal3,
85 Some(Severity::Fatal4) => SeverityNumber::Fatal4,
86 None => SeverityNumber::Unspecified,
87 };
88
89 LogRecord {
90 time_unix_nano: log_record.timestamp.map(to_nanos).unwrap_or_default(),
91 observed_time_unix_nano: to_nanos(log_record.observed_timestamp.unwrap()),
92 severity_number: severity_number.into(),
93 severity_text: log_record.severity_text.map(Into::into).unwrap_or_default(),
94 body: log_record.body.map(Into::into),
95 attributes: {
96 let mut attributes = log_record
97 .attributes
98 .map(Attributes::from_iter)
99 .unwrap_or_default()
100 .0;
101 if let Some(event_name) = log_record.event_name.as_ref() {
102 attributes.push(KeyValue {
103 key: "name".into(),
104 value: Some(AnyValue {
105 value: Some(Value::StringValue(event_name.to_string())),
106 }),
107 })
108 }
109 attributes
110 },
111 dropped_attributes_count: 0,
112 flags: trace_context
113 .map(|ctx| {
114 ctx.trace_flags
115 .map(|flags| flags.to_u8() as u32)
116 .unwrap_or_default()
117 })
118 .unwrap_or_default(),
119 span_id: trace_context
120 .map(|ctx| ctx.span_id.to_bytes().to_vec())
121 .unwrap_or_default(),
122 trace_id: trace_context
123 .map(|ctx| ctx.trace_id.to_bytes().to_vec())
124 .unwrap_or_default(),
125 }
126 }
127 }
128
129 impl
130 From<(
131 opentelemetry_sdk::export::logs::LogData,
132 &ResourceAttributesWithSchema,
133 )> for ResourceLogs
134 {
135 fn from(
136 data: (
137 opentelemetry_sdk::export::logs::LogData,
138 &ResourceAttributesWithSchema,
139 ),
140 ) -> Self {
141 let (log_data, resource) = data;
142
143 ResourceLogs {
144 resource: Some(Resource {
145 attributes: resource.attributes.0.clone(),
146 dropped_attributes_count: 0,
147 }),
148 schema_url: resource.schema_url.clone().unwrap_or_default(),
149 scope_logs: vec![ScopeLogs {
150 schema_url: log_data
151 .instrumentation
152 .schema_url
153 .clone()
154 .map(Into::into)
155 .unwrap_or_default(),
156 scope: Some((log_data.instrumentation, log_data.record.target.clone()).into()),
157 log_records: vec![log_data.record.into()],
158 }],
159 }
160 }
161 }
162
163 pub fn group_logs_by_resource_and_scope(
164 logs: Vec<opentelemetry_sdk::export::logs::LogData>,
165 resource: &ResourceAttributesWithSchema,
166 ) -> Vec<ResourceLogs> {
167 let scope_map = logs.iter().fold(
169 HashMap::new(),
170 |mut scope_map: HashMap<
171 Cow<'static, str>,
172 Vec<&opentelemetry_sdk::export::logs::LogData>,
173 >,
174 log| {
175 let key = log
176 .record
177 .target
178 .clone()
179 .unwrap_or_else(|| log.instrumentation.name.clone());
180 scope_map.entry(key).or_default().push(log);
181 scope_map
182 },
183 );
184
185 let scope_logs = scope_map
186 .into_iter()
187 .map(|(key, log_data)| ScopeLogs {
188 scope: Some(InstrumentationScope::from((
189 &log_data.first().unwrap().instrumentation,
190 Some(key),
191 ))),
192 schema_url: resource.schema_url.clone().unwrap_or_default(),
193 log_records: log_data
194 .into_iter()
195 .map(|log_data| log_data.record.clone().into())
196 .collect(),
197 })
198 .collect();
199
200 vec![ResourceLogs {
201 resource: Some(Resource {
202 attributes: resource.attributes.0.clone(),
203 dropped_attributes_count: 0,
204 }),
205 scope_logs,
206 schema_url: resource.schema_url.clone().unwrap_or_default(),
207 }]
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use crate::transform::common::tonic::ResourceAttributesWithSchema;
214 use opentelemetry::logs::LogRecord as _;
215 use opentelemetry_sdk::export::logs::LogData;
216 use opentelemetry_sdk::{logs::LogRecord, Resource};
217 use std::time::SystemTime;
218
219 fn create_test_log_data(instrumentation_name: &str, _message: &str) -> LogData {
220 let mut logrecord = LogRecord::default();
221 logrecord.set_timestamp(SystemTime::now());
222 logrecord.set_observed_timestamp(SystemTime::now());
223 LogData {
224 instrumentation: opentelemetry_sdk::InstrumentationLibrary::builder(
225 instrumentation_name.to_string(),
226 )
227 .build(),
228 record: logrecord,
229 }
230 }
231
232 #[test]
233 fn test_group_logs_by_resource_and_scope_single_scope() {
234 let resource = Resource::default();
235 let log1 = create_test_log_data("test-lib", "Log 1");
236 let log2 = create_test_log_data("test-lib", "Log 2");
237
238 let logs = vec![log1, log2];
239 let resource: ResourceAttributesWithSchema = (&resource).into(); let grouped_logs =
242 crate::transform::logs::tonic::group_logs_by_resource_and_scope(logs, &resource);
243
244 assert_eq!(grouped_logs.len(), 1);
245 let resource_logs = &grouped_logs[0];
246 assert_eq!(resource_logs.scope_logs.len(), 1);
247
248 let scope_logs = &resource_logs.scope_logs[0];
249 assert_eq!(scope_logs.log_records.len(), 2);
250 }
251
252 #[test]
253 fn test_group_logs_by_resource_and_scope_multiple_scopes() {
254 let resource = Resource::default();
255 let log1 = create_test_log_data("lib1", "Log 1");
256 let log2 = create_test_log_data("lib2", "Log 2");
257
258 let logs = vec![log1, log2];
259 let resource: ResourceAttributesWithSchema = (&resource).into(); let grouped_logs =
261 crate::transform::logs::tonic::group_logs_by_resource_and_scope(logs, &resource);
262
263 assert_eq!(grouped_logs.len(), 1);
264 let resource_logs = &grouped_logs[0];
265 assert_eq!(resource_logs.scope_logs.len(), 2);
266
267 let scope_logs_1 = &resource_logs
268 .scope_logs
269 .iter()
270 .find(|scope| scope.scope.as_ref().unwrap().name == "lib1")
271 .unwrap();
272 let scope_logs_2 = &resource_logs
273 .scope_logs
274 .iter()
275 .find(|scope| scope.scope.as_ref().unwrap().name == "lib2")
276 .unwrap();
277
278 assert_eq!(scope_logs_1.log_records.len(), 1);
279 assert_eq!(scope_logs_2.log_records.len(), 1);
280 }
281}