Skip to content

Commit 6ec00ae

Browse files
authored
Merge branch 'main' into main
2 parents 73df360 + b33f0cc commit 6ec00ae

File tree

26 files changed

+964
-159
lines changed

26 files changed

+964
-159
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ for specific dates and for Zoom meeting links. "OTel Rust SIG" is the name of
88
meeting for this group.
99

1010
Meeting notes are available as a public [Google
11-
doc](https://docs.google.com/document/d/1tGKuCsSnyT2McDncVJrMgg74_z8V06riWZa0Sr79I_4/edit).
11+
doc](https://docs.google.com/document/d/12upOzNk8c3SFTjsL6IRohCWMgzLKoknSCOOdMakbWo4/edit).
1212
If you have trouble accessing the doc, please get in touch on
1313
[Slack](https://cloud-native.slack.com/archives/C03GDP0H023).
1414

Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,12 @@ opentelemetry = { path = "opentelemetry" }
8585
opentelemetry_sdk = { path = "opentelemetry-sdk" }
8686
opentelemetry-stdout = { path = "opentelemetry-stdout" }
8787

88+
[workspace.lints.rust]
89+
rust_2024_compatibility = { level = "warn", priority = -1 }
90+
# No need to enable those, because it either not needed or results in ugly syntax
91+
edition_2024_expr_fragment_specifier = "allow"
92+
if_let_rescope = "allow"
93+
tail_expr_drop_order = "allow"
94+
8895
[workspace.lints.clippy]
8996
all = { level = "warn", priority = 1 }

docs/design/logs.md

+13
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ convey that decision back to logger, allowing appender to avoid even the cost of
296296
creating a `LogRecord` in the first place if there is no listener. This check is
297297
done for each log emission, and can react dynamically to changes in interest, by
298298
enabling/disabling ETW/user-event listener.
299+
5. `tracing` has a notion of "target", which is expected to be mapped to OTel's
300+
concept of Instrumentation Scope for Logs, when `OpenTelemetry-Tracing-Appender`
301+
bridges `tracing` to OpenTelemetry. Since scopes are tied to Loggers, a naive
302+
approach would require creating a separate logger for each unique target. This
303+
would necessitate an RWLock-protected HashMap lookup, introducing contention and
304+
reducing throughput. To avoid this, `OpenTelemetry-Tracing-Appender` instead
305+
stores the target directly in the LogRecord as a top-level field, ensuring fast
306+
access in the hot path. Components processing the LogRecord can retrieve the
307+
target via LogRecord.target(), treating it as the scope. The OTLP Exporter
308+
already handles this automatically, so end-users will see “target” reflected in
309+
the Instrumentation Scope. An alternative design would be to use thread-local
310+
HashMaps - but it can cause increased memory usage, as there can be 100s of
311+
unique targets. (because `tracing` defaults to using module path as target).
299312

300313
### Perf test - benchmarks
301314

opentelemetry-appender-log/src/lib.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,11 @@ where
136136
{
137137
fn enabled(&self, _metadata: &Metadata) -> bool {
138138
#[cfg(feature = "spec_unstable_logs_enabled")]
139-
return self
140-
.logger
141-
.event_enabled(severity_of_level(_metadata.level()), _metadata.target());
139+
return self.logger.event_enabled(
140+
severity_of_level(_metadata.level()),
141+
_metadata.target(),
142+
None,
143+
);
142144
#[cfg(not(feature = "spec_unstable_logs_enabled"))]
143145
true
144146
}

opentelemetry-appender-tracing/CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ simple string, but require format arguments as in the below example.
3131
error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", "This is an example message with format arguments {} and {}", "foo", "bar");
3232
```
3333

34+
Fixes [2658](https://github.com/open-telemetry/opentelemetry-rust/issues/2658)
35+
InstrumentationScope(Logger) used by the appender now uses an empty ("") named
36+
Logger. Previously, a Logger with name and version of the crate was used.
37+
Receivers (processors, exporters) are expected to use `LogRecord.target()` as
38+
scope name. This is already done in OTLP Exporters, so this change should be
39+
transparent to most users.
40+
41+
- Passes event name to the `event_enabled` method on the `Logger`. This allows
42+
implementations (SDK, processor, exporters) to leverage this additional
43+
information to determine if an event is enabled.
44+
3445
## 0.28.1
3546

3647
Released 2025-Feb-12

opentelemetry-appender-tracing/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ name = "logs"
4444
harness = false
4545
required-features = ["spec_unstable_logs_enabled"]
4646

47+
[[bench]]
48+
name = "log-attributes"
49+
harness = false
50+
4751
[lib]
4852
bench = false
4953

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
// Run this benchmark with:
3+
// cargo bench --bench log-attributes
4+
// Adding results in comments for a quick reference.
5+
// Apple M4 Pro
6+
// Total Number of Cores: 14 (10 performance and 4 efficiency)
7+
8+
| Test | Average time | Increment |
9+
|----------------------|--------------|-----------|
10+
| otel_0_attributes | 72 ns | - |
11+
| otel_1_attributes | 117 ns | +45 ns |
12+
| otel_2_attributes | 155 ns | +38 ns |
13+
| otel_3_attributes | 196 ns | +41 ns |
14+
| otel_4_attributes | 240 ns | +44 ns |
15+
| otel_5_attributes | 278 ns | +38 ns |
16+
| otel_6_attributes | 346 ns | +68 ns | // Array is full. 6th attribute causes vec! to be allocated
17+
| otel_7_attributes | 390 ns | +44 ns |
18+
| otel_8_attributes | 431 ns | +41 ns |
19+
| otel_9_attributes | 480 ns | +49 ns |
20+
| otel_10_attributes | 519 ns | +39 ns |
21+
| otel_11_attributes | 625 ns | +106 ns | // vec! initial capacity is 5. 11th attribute causes vec! to be reallocated
22+
| otel_12_attributes | 676 ns | +51 ns |
23+
*/
24+
25+
use criterion::{criterion_group, criterion_main, Criterion};
26+
use opentelemetry::InstrumentationScope;
27+
use opentelemetry_appender_tracing::layer as tracing_layer;
28+
use opentelemetry_sdk::error::OTelSdkResult;
29+
use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord, SdkLoggerProvider};
30+
use opentelemetry_sdk::Resource;
31+
#[cfg(not(target_os = "windows"))]
32+
use pprof::criterion::{Output, PProfProfiler};
33+
use tracing::error;
34+
use tracing_subscriber::prelude::*;
35+
use tracing_subscriber::Registry;
36+
37+
#[derive(Debug)]
38+
struct NoopProcessor;
39+
40+
impl LogProcessor for NoopProcessor {
41+
fn emit(&self, _: &mut SdkLogRecord, _: &InstrumentationScope) {}
42+
43+
fn force_flush(&self) -> OTelSdkResult {
44+
Ok(())
45+
}
46+
47+
fn shutdown(&self) -> OTelSdkResult {
48+
Ok(())
49+
}
50+
}
51+
52+
/// Creates a single benchmark for a specific number of attributes
53+
fn create_benchmark(c: &mut Criterion, num_attributes: usize) {
54+
let provider = SdkLoggerProvider::builder()
55+
.with_resource(
56+
Resource::builder_empty()
57+
.with_service_name("benchmark")
58+
.build(),
59+
)
60+
.with_log_processor(NoopProcessor)
61+
.build();
62+
63+
let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider);
64+
let subscriber = Registry::default().with(ot_layer);
65+
66+
tracing::subscriber::with_default(subscriber, || {
67+
c.bench_function(&format!("otel_{}_attributes", num_attributes), |b| {
68+
b.iter(|| {
69+
// Dynamically generate the error! macro call based on the number of attributes
70+
match num_attributes {
71+
0 => {
72+
error!(
73+
name : "CheckoutFailed",
74+
message = "Unable to process checkout."
75+
);
76+
}
77+
1 => {
78+
error!(
79+
name : "CheckoutFailed",
80+
field1 = "field1",
81+
message = "Unable to process checkout."
82+
);
83+
}
84+
2 => {
85+
error!(
86+
name : "CheckoutFailed",
87+
field1 = "field1",
88+
field2 = "field2",
89+
message = "Unable to process checkout."
90+
);
91+
}
92+
3 => {
93+
error!(
94+
name : "CheckoutFailed",
95+
field1 = "field1",
96+
field2 = "field2",
97+
field3 = "field3",
98+
message = "Unable to process checkout."
99+
);
100+
}
101+
4 => {
102+
error!(
103+
name : "CheckoutFailed",
104+
field1 = "field1",
105+
field2 = "field2",
106+
field3 = "field3",
107+
field4 = "field4",
108+
message = "Unable to process checkout."
109+
);
110+
}
111+
5 => {
112+
error!(
113+
name : "CheckoutFailed",
114+
field1 = "field1",
115+
field2 = "field2",
116+
field3 = "field3",
117+
field4 = "field4",
118+
field5 = "field5",
119+
message = "Unable to process checkout."
120+
);
121+
}
122+
6 => {
123+
error!(
124+
name : "CheckoutFailed",
125+
field1 = "field1",
126+
field2 = "field2",
127+
field3 = "field3",
128+
field4 = "field4",
129+
field5 = "field5",
130+
field6 = "field6",
131+
message = "Unable to process checkout."
132+
);
133+
}
134+
7 => {
135+
error!(
136+
name : "CheckoutFailed",
137+
field1 = "field1",
138+
field2 = "field2",
139+
field3 = "field3",
140+
field4 = "field4",
141+
field5 = "field5",
142+
field6 = "field6",
143+
field7 = "field7",
144+
message = "Unable to process checkout."
145+
);
146+
}
147+
8 => {
148+
error!(
149+
name : "CheckoutFailed",
150+
field1 = "field1",
151+
field2 = "field2",
152+
field3 = "field3",
153+
field4 = "field4",
154+
field5 = "field5",
155+
field6 = "field6",
156+
field7 = "field7",
157+
field8 = "field8",
158+
message = "Unable to process checkout."
159+
);
160+
}
161+
9 => {
162+
error!(
163+
name : "CheckoutFailed",
164+
field1 = "field1",
165+
field2 = "field2",
166+
field3 = "field3",
167+
field4 = "field4",
168+
field5 = "field5",
169+
field6 = "field6",
170+
field7 = "field7",
171+
field8 = "field8",
172+
field9 = "field9",
173+
message = "Unable to process checkout."
174+
);
175+
}
176+
10 => {
177+
error!(
178+
name : "CheckoutFailed",
179+
field1 = "field1",
180+
field2 = "field2",
181+
field3 = "field3",
182+
field4 = "field4",
183+
field5 = "field5",
184+
field6 = "field6",
185+
field7 = "field7",
186+
field8 = "field8",
187+
field9 = "field9",
188+
field10 = "field10",
189+
message = "Unable to process checkout."
190+
);
191+
}
192+
11 => {
193+
error!(
194+
name : "CheckoutFailed",
195+
field1 = "field1",
196+
field2 = "field2",
197+
field3 = "field3",
198+
field4 = "field4",
199+
field5 = "field5",
200+
field6 = "field6",
201+
field7 = "field7",
202+
field8 = "field8",
203+
field9 = "field9",
204+
field10 = "field10",
205+
field11 = "field11",
206+
message = "Unable to process checkout."
207+
);
208+
}
209+
12 => {
210+
error!(
211+
name : "CheckoutFailed",
212+
field1 = "field1",
213+
field2 = "field2",
214+
field3 = "field3",
215+
field4 = "field4",
216+
field5 = "field5",
217+
field6 = "field6",
218+
field7 = "field7",
219+
field8 = "field8",
220+
field9 = "field9",
221+
field10 = "field10",
222+
field11 = "field11",
223+
field12 = "field12",
224+
message = "Unable to process checkout."
225+
);
226+
}
227+
_ => {
228+
// Fall back to 10 attributes for any higher number
229+
error!(
230+
name : "CheckoutFailed",
231+
field1 = "field1",
232+
field2 = "field2",
233+
field3 = "field3",
234+
field4 = "field4",
235+
field5 = "field5",
236+
field6 = "field6",
237+
field7 = "field7",
238+
field8 = "field8",
239+
field9 = "field9",
240+
field10 = "field10",
241+
message = "Unable to process checkout."
242+
);
243+
}
244+
}
245+
});
246+
});
247+
});
248+
}
249+
250+
fn criterion_benchmark(c: &mut Criterion) {
251+
create_benchmark(c, 2);
252+
// Run benchmarks for 0 to 12 attributes
253+
// for num_attributes in 0..=12 {
254+
// create_benchmark(c, 2);
255+
// }
256+
}
257+
258+
#[cfg(not(target_os = "windows"))]
259+
criterion_group! {
260+
name = benches;
261+
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
262+
targets = criterion_benchmark
263+
}
264+
265+
#[cfg(target_os = "windows")]
266+
criterion_group! {
267+
name = benches;
268+
config = Criterion::default();
269+
targets = criterion_benchmark
270+
}
271+
272+
criterion_main!(benches);

0 commit comments

Comments
 (0)