Skip to content

Commit c5cd7a9

Browse files
committed
fix: Ensure InstrumentationScope is properly propagated.
With the current implementation, there is no way for an InstrumentationScope to export its version and attributes correctly. The reason why this is happening is because when the InstrumentationScope is exported, the target key is always `Some`. This is because when the code creates the `scope_map`, the target key always has a value, either comming from the log record's target, or from the name of the instrumentation scope. That means that when the code calls `InstrumentationScope::from((&InstrumentationScope, Option<Cow<'static, str>>))`, the option is always `Some`. This causes the new InstrumentationScope to always from version and attributes from the incoming scope. This change assumes that if the target and the instrumentation scope name match, the user wants to preserve the version and attributes of the incoming scope. There are other alternatives implementations that check whether the log record target is None that could also be valid, but I think both would have the same outcome.
1 parent 5250df2 commit c5cd7a9

File tree

1 file changed

+133
-10
lines changed
  • opentelemetry-proto/src/transform

1 file changed

+133
-10
lines changed

opentelemetry-proto/src/transform/logs.rs

Lines changed: 133 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,7 @@ pub mod tonic {
195195
let scope_logs = scope_map
196196
.into_iter()
197197
.map(|(key, log_data)| ScopeLogs {
198-
scope: Some(InstrumentationScope::from((
199-
log_data.first().unwrap().1,
200-
Some(key.into_owned().into()),
201-
))),
198+
scope: Some(new_instrumentation_scope(log_data.first().unwrap().1, key)),
202199
schema_url: resource.schema_url.clone().unwrap_or_default(),
203200
log_records: log_data
204201
.into_iter()
@@ -217,6 +214,22 @@ pub mod tonic {
217214
schema_url: resource.schema_url.clone().unwrap_or_default(),
218215
}]
219216
}
217+
218+
/// Ensure that InstrumentationScope is propagated correctly.
219+
/// These are the rules:
220+
///
221+
/// 1. If the log scope name matches the key, return a new scope with all the fields from the original scope.
222+
/// 2. Otherwise, return a new scope with the key as the name of the scope, and the rest of the fields empty.
223+
fn new_instrumentation_scope(
224+
log_scope: &opentelemetry::InstrumentationScope,
225+
key: Cow<'static, str>,
226+
) -> InstrumentationScope {
227+
if key == log_scope.name() {
228+
return InstrumentationScope::from((log_scope, None));
229+
}
230+
231+
InstrumentationScope::from((log_scope, Some(key)))
232+
}
220233
}
221234

222235
#[cfg(test)]
@@ -227,6 +240,7 @@ mod tests {
227240
use opentelemetry::logs::LoggerProvider;
228241
use opentelemetry::time::now;
229242
use opentelemetry::InstrumentationScope;
243+
use opentelemetry::KeyValue;
230244
use opentelemetry_sdk::error::OTelSdkResult;
231245
use opentelemetry_sdk::logs::LogProcessor;
232246
use opentelemetry_sdk::logs::SdkLoggerProvider;
@@ -246,6 +260,7 @@ mod tests {
246260
fn create_test_log_data(
247261
instrumentation_name: &str,
248262
_message: &str,
263+
attrs: Vec<KeyValue>,
249264
) -> (SdkLogRecord, InstrumentationScope) {
250265
let processor = MockProcessor {};
251266
let logger = SdkLoggerProvider::builder()
@@ -255,16 +270,17 @@ mod tests {
255270
let mut logrecord = logger.create_log_record();
256271
logrecord.set_timestamp(now());
257272
logrecord.set_observed_timestamp(now());
258-
let instrumentation =
259-
InstrumentationScope::builder(instrumentation_name.to_string()).build();
273+
let instrumentation = InstrumentationScope::builder(instrumentation_name.to_string())
274+
.with_attributes(attrs)
275+
.build();
260276
(logrecord, instrumentation)
261277
}
262278

263279
#[test]
264280
fn test_group_logs_by_resource_and_scope_single_scope() {
265281
let resource = Resource::builder().build();
266-
let (log_record1, instrum_lib1) = create_test_log_data("test-lib", "Log 1");
267-
let (log_record2, instrum_lib2) = create_test_log_data("test-lib", "Log 2");
282+
let (log_record1, instrum_lib1) = create_test_log_data("test-lib", "Log 1", vec![]);
283+
let (log_record2, instrum_lib2) = create_test_log_data("test-lib", "Log 2", vec![]);
268284

269285
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
270286
let log_batch = LogBatch::new(&logs);
@@ -284,8 +300,8 @@ mod tests {
284300
#[test]
285301
fn test_group_logs_by_resource_and_scope_multiple_scopes() {
286302
let resource = Resource::builder().build();
287-
let (log_record1, instrum_lib1) = create_test_log_data("lib1", "Log 1");
288-
let (log_record2, instrum_lib2) = create_test_log_data("lib2", "Log 2");
303+
let (log_record1, instrum_lib1) = create_test_log_data("lib1", "Log 1", vec![]);
304+
let (log_record2, instrum_lib2) = create_test_log_data("lib2", "Log 2", vec![]);
289305

290306
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
291307
let log_batch = LogBatch::new(&logs);
@@ -311,4 +327,111 @@ mod tests {
311327
assert_eq!(scope_logs_1.log_records.len(), 1);
312328
assert_eq!(scope_logs_2.log_records.len(), 1);
313329
}
330+
331+
#[test]
332+
fn test_group_logs_by_resource_preserving_scope_attributes_when_log_target_empty() {
333+
let resource = Resource::builder().build();
334+
let (log_record1, instrum_lib1) =
335+
create_test_log_data("lib1", "Log 1", vec![KeyValue::new("key1", "value1")]);
336+
let (log_record2, instrum_lib2) =
337+
create_test_log_data("lib2", "Log 2", vec![KeyValue::new("key2", "value2")]);
338+
339+
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
340+
let log_batch = LogBatch::new(&logs);
341+
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
342+
let grouped_logs =
343+
crate::transform::logs::tonic::group_logs_by_resource_and_scope(log_batch, &resource);
344+
345+
assert_eq!(grouped_logs.len(), 1);
346+
let resource_logs = &grouped_logs[0];
347+
assert_eq!(resource_logs.scope_logs.len(), 2);
348+
349+
let scope_logs_1 = &resource_logs
350+
.scope_logs
351+
.iter()
352+
.find(|scope| scope.scope.as_ref().unwrap().name == "lib1")
353+
.unwrap();
354+
let scope_logs_2 = &resource_logs
355+
.scope_logs
356+
.iter()
357+
.find(|scope| scope.scope.as_ref().unwrap().name == "lib2")
358+
.unwrap();
359+
360+
assert_eq!(1, scope_logs_1.scope.as_ref().unwrap().attributes.len());
361+
assert_eq!(1, scope_logs_2.scope.as_ref().unwrap().attributes.len());
362+
}
363+
364+
#[test]
365+
fn test_group_logs_by_resource_preserving_scope_attributes_when_log_target_matching() {
366+
let resource = Resource::builder().build();
367+
let (mut log_record1, instrum_lib1) =
368+
create_test_log_data("lib1", "Log 1", vec![KeyValue::new("key1", "value1")]);
369+
let (mut log_record2, instrum_lib2) =
370+
create_test_log_data("lib2", "Log 2", vec![KeyValue::new("key2", "value2")]);
371+
372+
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
373+
let log_batch = LogBatch::new(&logs);
374+
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
375+
let grouped_logs =
376+
crate::transform::logs::tonic::group_logs_by_resource_and_scope(log_batch, &resource);
377+
378+
// This makes the grouping to match the existent InstrumentationScope, preserving the scope attributes.
379+
log_record1.set_target("lib1");
380+
log_record2.set_target("lib2");
381+
382+
assert_eq!(grouped_logs.len(), 1);
383+
let resource_logs = &grouped_logs[0];
384+
assert_eq!(resource_logs.scope_logs.len(), 2);
385+
386+
let scope_logs_1 = &resource_logs
387+
.scope_logs
388+
.iter()
389+
.find(|scope| scope.scope.as_ref().unwrap().name == "lib1")
390+
.unwrap();
391+
let scope_logs_2 = &resource_logs
392+
.scope_logs
393+
.iter()
394+
.find(|scope| scope.scope.as_ref().unwrap().name == "lib2")
395+
.unwrap();
396+
397+
assert_eq!(1, scope_logs_1.scope.as_ref().unwrap().attributes.len());
398+
assert_eq!(1, scope_logs_2.scope.as_ref().unwrap().attributes.len());
399+
}
400+
401+
#[test]
402+
fn test_group_logs_by_resource_ignoring_scope_attributes_when_log_target_not_matching() {
403+
let resource = Resource::builder().build();
404+
let (mut log_record1, instrum_lib1) =
405+
create_test_log_data("lib1", "Log 1", vec![KeyValue::new("key1", "value1")]);
406+
let (mut log_record2, instrum_lib2) =
407+
create_test_log_data("lib2", "Log 2", vec![KeyValue::new("key2", "value2")]);
408+
409+
// This makes the grouping to not match the existent InstrumentationScope, ignoring the scope attributes.
410+
log_record1.set_target("target1");
411+
log_record2.set_target("target2");
412+
413+
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
414+
let log_batch = LogBatch::new(&logs);
415+
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
416+
let grouped_logs =
417+
crate::transform::logs::tonic::group_logs_by_resource_and_scope(log_batch, &resource);
418+
419+
assert_eq!(grouped_logs.len(), 1);
420+
let resource_logs = &grouped_logs[0];
421+
assert_eq!(resource_logs.scope_logs.len(), 2);
422+
423+
let scope_logs_1 = &resource_logs
424+
.scope_logs
425+
.iter()
426+
.find(|scope| scope.scope.as_ref().unwrap().name == "target1")
427+
.unwrap();
428+
let scope_logs_2 = &resource_logs
429+
.scope_logs
430+
.iter()
431+
.find(|scope| scope.scope.as_ref().unwrap().name == "target2")
432+
.unwrap();
433+
434+
assert!(scope_logs_1.scope.as_ref().unwrap().attributes.is_empty());
435+
assert!(scope_logs_2.scope.as_ref().unwrap().attributes.is_empty());
436+
}
314437
}

0 commit comments

Comments
 (0)