Skip to content

Commit d38fe41

Browse files
authored
Merge branch 'main' into jwl/fix-multiple-header
2 parents 4b1b789 + bc5e6ce commit d38fe41

File tree

9 files changed

+239
-127
lines changed

9 files changed

+239
-127
lines changed

opentelemetry-sdk/CHANGELOG.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,25 @@
3333
}
3434
}
3535
```
36+
3637
- **Breaking** The SpanExporter::export() method no longer requires a mutable reference to self.
37-
Before:
38+
Before:
39+
3840
```rust
3941
async fn export(&mut self, batch: Vec<SpanData>) -> OTelSdkResult
4042
```
41-
After:
42-
```rust
43+
44+
After:
45+
46+
```rust
4347
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult
4448
```
49+
4550
Custom exporters will need to internally synchronize any mutable state, if applicable.
46-
51+
52+
- Bug Fix: `BatchLogProcessor` now correctly calls `shutdown` on the exporter
53+
when its `shutdown` is invoked.
54+
4755
## 0.28.0
4856

4957
Released 2025-Feb-10

opentelemetry-sdk/src/logs/batch_log_processor.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ impl BatchLogProcessor {
436436
&current_batch_size,
437437
&config,
438438
);
439+
let _ = exporter.shutdown();
439440
let _ = sender.send(result);
440441

441442
otel_debug!(
@@ -925,7 +926,8 @@ mod tests {
925926
processor.shutdown().unwrap();
926927
// todo: expect to see errors here. How should we assert this?
927928
processor.emit(&mut record, &instrumentation);
928-
assert_eq!(1, exporter.get_emitted_logs().unwrap().len())
929+
assert_eq!(1, exporter.get_emitted_logs().unwrap().len());
930+
assert!(exporter.is_shutdown_called());
929931
}
930932

931933
#[tokio::test(flavor = "current_thread")]

opentelemetry-sdk/src/logs/in_memory_exporter.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::logs::{LogBatch, LogExporter};
44
use crate::Resource;
55
use opentelemetry::InstrumentationScope;
66
use std::borrow::Cow;
7+
use std::sync::atomic::AtomicBool;
78
use std::sync::{Arc, Mutex};
89

910
type LogResult<T> = Result<T, OTelSdkError>;
@@ -42,6 +43,7 @@ pub struct InMemoryLogExporter {
4243
logs: Arc<Mutex<Vec<OwnedLogData>>>,
4344
resource: Arc<Mutex<Resource>>,
4445
should_reset_on_shutdown: bool,
46+
shutdown_called: Arc<AtomicBool>,
4547
}
4648

4749
impl Default for InMemoryLogExporter {
@@ -124,6 +126,7 @@ impl InMemoryLogExporterBuilder {
124126
logs: Arc::new(Mutex::new(Vec::new())),
125127
resource: Arc::new(Mutex::new(Resource::builder().build())),
126128
should_reset_on_shutdown: self.reset_on_shutdown,
129+
shutdown_called: Arc::new(AtomicBool::new(false)),
127130
}
128131
}
129132

@@ -137,6 +140,12 @@ impl InMemoryLogExporterBuilder {
137140
}
138141

139142
impl InMemoryLogExporter {
143+
/// Returns true if shutdown was called.
144+
pub fn is_shutdown_called(&self) -> bool {
145+
self.shutdown_called
146+
.load(std::sync::atomic::Ordering::Relaxed)
147+
}
148+
140149
/// Returns the logs emitted via Logger as a vector of `LogDataWithResource`.
141150
///
142151
/// # Example
@@ -203,6 +212,8 @@ impl LogExporter for InMemoryLogExporter {
203212
}
204213

205214
fn shutdown(&mut self) -> OTelSdkResult {
215+
self.shutdown_called
216+
.store(true, std::sync::atomic::Ordering::Relaxed);
206217
if self.should_reset_on_shutdown {
207218
self.reset();
208219
}

opentelemetry-sdk/src/logs/simple_log_processor.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ mod tests {
223223

224224
processor.emit(&mut record, &instrumentation);
225225

226-
assert_eq!(1, exporter.get_emitted_logs().unwrap().len())
226+
assert_eq!(1, exporter.get_emitted_logs().unwrap().len());
227+
assert!(exporter.is_shutdown_called());
227228
}
228229

229230
#[test]

opentelemetry-sdk/src/metrics/meter_provider.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,25 @@ mod tests {
562562
assert_eq!(provider.inner.meters.lock().unwrap().len(), 5);
563563
}
564564

565+
#[test]
566+
fn same_meter_reused_same_scope_attributes() {
567+
let meter_provider = super::SdkMeterProvider::builder().build();
568+
let make_scope = |attributes| {
569+
InstrumentationScope::builder("test.meter")
570+
.with_version("v0.1.0")
571+
.with_schema_url("http://example.com")
572+
.with_attributes(attributes)
573+
.build()
574+
};
575+
576+
let _meter1 =
577+
meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
578+
let _meter2 =
579+
meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
580+
581+
assert_eq!(meter_provider.inner.meters.lock().unwrap().len(), 1);
582+
}
583+
565584
#[test]
566585
fn with_resource_multiple_calls_ensure_additive() {
567586
let builder = SdkMeterProvider::builder()

opentelemetry-sdk/src/metrics/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ mod tests {
782782
.build();
783783

784784
// Act
785-
// Meters are identical except for scope attributes, but scope attributes are not an identifying property.
785+
// Meters are identical.
786786
// Hence there should be a single metric stream output for this test.
787787
let make_scope = |attributes| {
788788
InstrumentationScope::builder("test.meter")
@@ -795,7 +795,7 @@ mod tests {
795795
let meter1 =
796796
meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
797797
let meter2 =
798-
meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value2")]));
798+
meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
799799

800800
let counter1 = meter1
801801
.u64_counter("my_counter")

opentelemetry/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- *Breaking* Moved `TraceError` enum from `opentelemetry::trace::TraceError` to `opentelemetry_sdk::trace::TraceError`
77
- *Breaking* Moved `TraceResult` type alias from `opentelemetry::trace::TraceResult` to `opentelemetry_sdk::trace::TraceResult`
88
- {PLACEHOLDER} - Remove the above completely. // TODO fill this when changes are actually in.
9+
- Bug Fix: `InstrumentationScope` implementation for `PartialEq` and `Hash` fixed to include Attributes also.
910

1011
## 0.28.0
1112

opentelemetry/src/common.rs

Lines changed: 189 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use std::borrow::{Borrow, Cow};
22
use std::sync::Arc;
33
use std::{fmt, hash};
44

5+
use std::hash::{Hash, Hasher};
6+
57
/// The key part of attribute [KeyValue] pairs.
68
///
79
/// See the [attribute naming] spec for guidelines.
@@ -399,6 +401,42 @@ impl KeyValue {
399401
}
400402
}
401403

404+
struct F64Hashable(f64);
405+
406+
impl PartialEq for F64Hashable {
407+
fn eq(&self, other: &Self) -> bool {
408+
self.0.to_bits() == other.0.to_bits()
409+
}
410+
}
411+
412+
impl Eq for F64Hashable {}
413+
414+
impl Hash for F64Hashable {
415+
fn hash<H: Hasher>(&self, state: &mut H) {
416+
self.0.to_bits().hash(state);
417+
}
418+
}
419+
420+
impl Hash for KeyValue {
421+
fn hash<H: Hasher>(&self, state: &mut H) {
422+
self.key.hash(state);
423+
match &self.value {
424+
Value::F64(f) => F64Hashable(*f).hash(state),
425+
Value::Array(a) => match a {
426+
Array::Bool(b) => b.hash(state),
427+
Array::I64(i) => i.hash(state),
428+
Array::F64(f) => f.iter().for_each(|f| F64Hashable(*f).hash(state)),
429+
Array::String(s) => s.hash(state),
430+
},
431+
Value::Bool(b) => b.hash(state),
432+
Value::I64(i) => i.hash(state),
433+
Value::String(s) => s.hash(state),
434+
};
435+
}
436+
}
437+
438+
impl Eq for KeyValue {}
439+
402440
/// Information about a library or crate providing instrumentation.
403441
///
404442
/// An instrumentation scope should be named to follow any naming conventions
@@ -427,22 +465,33 @@ pub struct InstrumentationScope {
427465
attributes: Vec<KeyValue>,
428466
}
429467

430-
// Uniqueness for InstrumentationScope does not depend on attributes
431-
impl Eq for InstrumentationScope {}
432-
433468
impl PartialEq for InstrumentationScope {
434469
fn eq(&self, other: &Self) -> bool {
435470
self.name == other.name
436471
&& self.version == other.version
437472
&& self.schema_url == other.schema_url
473+
&& {
474+
let mut self_attrs = self.attributes.clone();
475+
let mut other_attrs = other.attributes.clone();
476+
self_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
477+
other_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
478+
self_attrs == other_attrs
479+
}
438480
}
439481
}
440482

483+
impl Eq for InstrumentationScope {}
484+
441485
impl hash::Hash for InstrumentationScope {
442486
fn hash<H: hash::Hasher>(&self, state: &mut H) {
443487
self.name.hash(state);
444488
self.version.hash(state);
445489
self.schema_url.hash(state);
490+
let mut sorted_attrs = self.attributes.clone();
491+
sorted_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
492+
for attribute in sorted_attrs {
493+
attribute.hash(state);
494+
}
446495
}
447496
}
448497

@@ -561,3 +610,140 @@ impl InstrumentationScopeBuilder {
561610
}
562611
}
563612
}
613+
614+
#[cfg(test)]
615+
mod tests {
616+
use std::hash::{Hash, Hasher};
617+
618+
use crate::{InstrumentationScope, KeyValue};
619+
620+
use rand::random;
621+
use std::collections::hash_map::DefaultHasher;
622+
use std::f64;
623+
624+
#[test]
625+
fn kv_float_equality() {
626+
let kv1 = KeyValue::new("key", 1.0);
627+
let kv2 = KeyValue::new("key", 1.0);
628+
assert_eq!(kv1, kv2);
629+
630+
let kv1 = KeyValue::new("key", 1.0);
631+
let kv2 = KeyValue::new("key", 1.01);
632+
assert_ne!(kv1, kv2);
633+
634+
let kv1 = KeyValue::new("key", f64::NAN);
635+
let kv2 = KeyValue::new("key", f64::NAN);
636+
assert_ne!(kv1, kv2, "NAN is not equal to itself");
637+
638+
for float_val in [
639+
f64::INFINITY,
640+
f64::NEG_INFINITY,
641+
f64::MAX,
642+
f64::MIN,
643+
f64::MIN_POSITIVE,
644+
]
645+
.iter()
646+
{
647+
let kv1 = KeyValue::new("key", *float_val);
648+
let kv2 = KeyValue::new("key", *float_val);
649+
assert_eq!(kv1, kv2);
650+
}
651+
652+
for _ in 0..100 {
653+
let random_value = random::<f64>();
654+
let kv1 = KeyValue::new("key", random_value);
655+
let kv2 = KeyValue::new("key", random_value);
656+
assert_eq!(kv1, kv2);
657+
}
658+
}
659+
660+
#[test]
661+
fn kv_float_hash() {
662+
for float_val in [
663+
f64::NAN,
664+
f64::INFINITY,
665+
f64::NEG_INFINITY,
666+
f64::MAX,
667+
f64::MIN,
668+
f64::MIN_POSITIVE,
669+
]
670+
.iter()
671+
{
672+
let kv1 = KeyValue::new("key", *float_val);
673+
let kv2 = KeyValue::new("key", *float_val);
674+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
675+
}
676+
677+
for _ in 0..100 {
678+
let random_value = random::<f64>();
679+
let kv1 = KeyValue::new("key", random_value);
680+
let kv2 = KeyValue::new("key", random_value);
681+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
682+
}
683+
}
684+
685+
fn hash_helper<T: Hash>(item: &T) -> u64 {
686+
let mut hasher = DefaultHasher::new();
687+
item.hash(&mut hasher);
688+
hasher.finish()
689+
}
690+
691+
#[test]
692+
fn instrumentation_scope_equality() {
693+
let scope1 = InstrumentationScope::builder("my-crate")
694+
.with_version("v0.1.0")
695+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
696+
.with_attributes([KeyValue::new("k", "v")])
697+
.build();
698+
let scope2 = InstrumentationScope::builder("my-crate")
699+
.with_version("v0.1.0")
700+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
701+
.with_attributes([KeyValue::new("k", "v")])
702+
.build();
703+
assert_eq!(scope1, scope2);
704+
}
705+
706+
#[test]
707+
fn instrumentation_scope_equality_attributes_diff_order() {
708+
let scope1 = InstrumentationScope::builder("my-crate")
709+
.with_version("v0.1.0")
710+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
711+
.with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
712+
.build();
713+
let scope2 = InstrumentationScope::builder("my-crate")
714+
.with_version("v0.1.0")
715+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
716+
.with_attributes([KeyValue::new("k2", "v2"), KeyValue::new("k1", "v1")])
717+
.build();
718+
assert_eq!(scope1, scope2);
719+
720+
// assert hash are same for both scopes
721+
let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
722+
scope1.hash(&mut hasher1);
723+
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
724+
scope2.hash(&mut hasher2);
725+
assert_eq!(hasher1.finish(), hasher2.finish());
726+
}
727+
728+
#[test]
729+
fn instrumentation_scope_equality_different_attributes() {
730+
let scope1 = InstrumentationScope::builder("my-crate")
731+
.with_version("v0.1.0")
732+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
733+
.with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
734+
.build();
735+
let scope2 = InstrumentationScope::builder("my-crate")
736+
.with_version("v0.1.0")
737+
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
738+
.with_attributes([KeyValue::new("k2", "v3"), KeyValue::new("k4", "v5")])
739+
.build();
740+
assert_ne!(scope1, scope2);
741+
742+
// assert hash are same for both scopes
743+
let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
744+
scope1.hash(&mut hasher1);
745+
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
746+
scope2.hash(&mut hasher2);
747+
assert_ne!(hasher1.finish(), hasher2.finish());
748+
}
749+
}

0 commit comments

Comments
 (0)