Skip to content

Commit 1c7f22e

Browse files
authored
User-Events exporter - add validations, tests and nit cleanups (#195)
1 parent 644768f commit 1c7f22e

File tree

10 files changed

+166
-84
lines changed

10 files changed

+166
-84
lines changed

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"rustc",
5959
"Tescher",
6060
"tracepoint",
61+
"tracepoints",
6162
"Tracepoints",
6263
"Zhongyang",
6364
"zipkin"

opentelemetry-user-events-logs/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## vNext
44

5+
- Fixed contention in `event_enabled()` check and `export()` path, by caching the
6+
EventSets, addressing
7+
[159](https://github.com/open-telemetry/opentelemetry-rust-contrib/issues/159)
8+
- //TODO:(actually do this after upstream release) Shutdown now de-registers all the EventSets created
9+
- Added validation for the provider name in `with_user_event_exporter(provider_name)`.
10+
The provider name must:
11+
- Be less than 234 characters.
12+
- Contain only ASCII letters, digits, and the underscore (`'_'`) character.
13+
514
## v0.10.0
615

716
- Removed provider group from being appended to the tracepoint name.

opentelemetry-user-events-logs/examples/basic-logs.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ fn init_logger() -> SdkLoggerProvider {
2020
}
2121

2222
fn main() {
23-
// Example with tracing appender.
24-
// Create a new tracing::Fmt layer to print the logs to stdout. It has a
25-
// default filter of `info` level and above, and `debug` and above for logs
26-
// from OpenTelemetry crates. The filter levels can be customized as needed.
23+
// OpenTelemetry layer with a filter to ensure OTel's own logs are not fed back into
24+
// the OpenTelemetry pipeline.
2725
let filter_otel = EnvFilter::new("info").add_directive("opentelemetry=off".parse().unwrap());
2826
let logger_provider = init_logger();
2927
let otel_layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
3028
let otel_layer = otel_layer.with_filter(filter_otel);
3129

30+
// Create a new tracing::Fmt layer to print the logs to stdout. It has a
31+
// default filter of `info` level and above, and `debug` and above for logs
32+
// from OpenTelemetry crates. The filter levels can be customized as needed.
3233
let filter_fmt = EnvFilter::new("info").add_directive("opentelemetry=debug".parse().unwrap());
3334
let fmt_layer = tracing_subscriber::fmt::layer().with_filter(filter_fmt);
3435

@@ -37,8 +38,6 @@ fn main() {
3738
.with(fmt_layer)
3839
.init();
3940

40-
// event_id is passed as an attribute now, there is nothing in metadata where a
41-
// numeric id can be stored.
4241
// run in a loop to ensure that tracepoints are not removed from kernel fs
4342

4443
let running = Arc::new(AtomicBool::new(true));
@@ -50,6 +49,8 @@ fn main() {
5049
.expect("Error setting Ctrl-C handler");
5150

5251
while running.load(Ordering::SeqCst) {
52+
// event_id is passed as an attribute now, there is nothing in metadata where a
53+
// numeric id can be stored.
5354
error!(
5455
name: "my-event-name",
5556
event_id = 20,

opentelemetry-user-events-logs/src/logs/exporter.rs

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,38 @@ thread_local! { static EBW: RefCell<EventBuilder> = RefCell::new(EventBuilder::n
1313
/// UserEventsExporter is a log exporter that exports logs in EventHeader format to user_events tracepoint.
1414
pub(crate) struct UserEventsExporter {
1515
provider: Mutex<Provider>,
16+
name: String,
1617
event_sets: Vec<Arc<EventSet>>,
1718
}
1819

1920
const EVENT_ID: &str = "event_id";
2021

2122
impl UserEventsExporter {
2223
/// Create instance of the exporter
23-
pub(crate) fn new(provider_name: &str) -> Self {
24+
pub(crate) fn new(provider_name: &str) -> Result<Self, String> {
25+
// Validate provider_name
26+
if provider_name.len() >= 234 {
27+
return Err("Provider name must be less than 234 characters.".to_string());
28+
}
29+
if !provider_name
30+
.chars()
31+
.all(|c| c.is_ascii_alphanumeric() || c == '_')
32+
{
33+
return Err(
34+
"Provider name must contain only ASCII letters, digits, and '_'.".to_string(),
35+
);
36+
}
37+
2438
let mut eventheader_provider: Provider =
2539
Provider::new(provider_name, &Provider::new_options());
2640
let event_sets = Self::register_events(&mut eventheader_provider);
2741
otel_debug!(name: "UserEvents.Created", provider_name = provider_name);
28-
UserEventsExporter {
42+
let name = eventheader_provider.name().to_string();
43+
Ok(UserEventsExporter {
2944
provider: Mutex::new(eventheader_provider),
45+
name,
3046
event_sets,
31-
}
47+
})
3248
}
3349

3450
fn register_events(
@@ -298,7 +314,7 @@ impl UserEventsExporter {
298314

299315
impl Debug for UserEventsExporter {
300316
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301-
f.write_str("user_events log exporter")
317+
write!(f, "user_events log exporter (provider: {})", self.name)
302318
}
303319
}
304320

@@ -335,3 +351,95 @@ impl opentelemetry_sdk::logs::LogExporter for UserEventsExporter {
335351
}
336352
}
337353
}
354+
355+
#[cfg(test)]
356+
mod tests {
357+
use super::*;
358+
#[test]
359+
fn exporter_debug() {
360+
let exporter = UserEventsExporter::new("test_provider");
361+
assert_eq!(
362+
format!("{:?}", exporter.expect("Failed to create exporter")),
363+
"user_events log exporter (provider: test_provider)"
364+
);
365+
}
366+
367+
#[test]
368+
fn valid_provider_name() {
369+
let valid_names = vec![
370+
"ValidName",
371+
"valid_name",
372+
"Valid123",
373+
"valid_123",
374+
"_valid_name",
375+
"VALID_NAME",
376+
];
377+
378+
for valid_name in valid_names {
379+
let result = UserEventsExporter::new(valid_name);
380+
assert!(
381+
result.is_ok(),
382+
"Expected '{}' to be valid, but it was rejected",
383+
valid_name
384+
);
385+
}
386+
}
387+
388+
#[test]
389+
fn provider_name_too_long() {
390+
let long_name = "a".repeat(234);
391+
let result = UserEventsExporter::new(&long_name);
392+
assert!(result.is_err());
393+
assert_eq!(
394+
result.err().unwrap(),
395+
"Provider name must be less than 234 characters.".to_string()
396+
);
397+
}
398+
399+
#[test]
400+
fn provider_name_contains_invalid_characters() {
401+
// Define a vector of invalid provider names to test
402+
let invalid_names = vec![
403+
"Invalid Name", // space
404+
"Invalid:Name", // colon
405+
"Invalid\0Name", // null character
406+
"Invalid-Name", // hyphen
407+
"InvalidName!", // exclamation mark
408+
"InvalidName@", // at symbol
409+
"Invalid+Name", // plus
410+
"Invalid&Name", // ampersand
411+
"Invalid#Name", // hash
412+
"Invalid%Name", // percent
413+
"Invalid/Name", // slash
414+
"Invalid\\Name", // backslash
415+
"Invalid=Name", // equals
416+
"Invalid?Name", // question mark
417+
"Invalid;Name", // semicolon
418+
"Invalid,Name", // comma
419+
];
420+
421+
// Expected error message
422+
let expected_error =
423+
"Provider name must contain only ASCII letters, digits, and '_'.".to_string();
424+
425+
// Test each invalid name
426+
for invalid_name in invalid_names {
427+
let result = UserEventsExporter::new(invalid_name);
428+
429+
// Assert that the result is an error
430+
assert!(
431+
result.is_err(),
432+
"Expected '{}' to be invalid, but it was accepted",
433+
invalid_name
434+
);
435+
436+
// Assert that the error message is as expected
437+
assert_eq!(
438+
result.err().unwrap(),
439+
expected_error,
440+
"Wrong error message for invalid name: '{}'",
441+
invalid_name
442+
);
443+
}
444+
}
445+
}
Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use exporter::UserEventsExporter;
2+
use opentelemetry::otel_warn;
23
use opentelemetry_sdk::logs::LoggerProviderBuilder;
34
use reentrant_logprocessor::ReentrantLogProcessor;
45

@@ -9,24 +10,42 @@ mod reentrant_logprocessor;
910
pub trait UserEventsLoggerProviderBuilderExt {
1011
/// Adds a user event exporter to the logger provider builder with the given provider name.
1112
///
13+
/// The provider name must:
14+
/// - Be less than 234 characters.
15+
/// - Contain only ASCII letters, digits, and the underscore (`'_'`) character.
16+
/// - Be short, human-readable, and unique enough to avoid conflicts with other provider names.
17+
/// - Typically include a company name and a component name, e.g., "MyCompany_MyComponent".
18+
///
19+
/// If an invalid provider name is provided, this method will not add the exporter to the builder.
20+
///
1221
/// Tracepoint names are generated by combining the provider name, event
1322
/// level and keyword (currently hardcoded to `1`) in the following format:
1423
/// `ProviderName + '_' + 'L' + EventLevel + 'K' + EventKeyword`
1524
///
1625
/// For example, if "myprovider" is the provider name, the following tracepoint names are created:
17-
/// - `myprovider_L4K1`
1826
/// - `myprovider_L5K1`
27+
/// - `myprovider_L4K1`
1928
/// - `myprovider_L3K1`
2029
/// - `myprovider_L2K1`
2130
/// - `myprovider_L1K1`
2231
///
32+
/// perf tool can be used to record events from the tracepoints.
33+
/// For example the following will capture level 2 (Error) and 3(Warning) events:
34+
/// perf record -e user_events:myprovider_L2K1,user_events:myprovider_L3K1
2335
fn with_user_event_exporter(self, provider_name: &str) -> Self;
2436
}
2537

2638
impl UserEventsLoggerProviderBuilderExt for LoggerProviderBuilder {
2739
fn with_user_event_exporter(self, provider_name: &str) -> Self {
28-
let exporter = UserEventsExporter::new(provider_name);
29-
let reenterant_processor = ReentrantLogProcessor::new(exporter);
30-
self.with_log_processor(reenterant_processor)
40+
match UserEventsExporter::new(provider_name) {
41+
Ok(exporter) => {
42+
let reenterant_processor = ReentrantLogProcessor::new(exporter);
43+
self.with_log_processor(reenterant_processor)
44+
}
45+
Err(e) => {
46+
otel_warn!(name: "User_Events.Exporter.CreationFailed", reason = e);
47+
self
48+
}
49+
}
3150
}
3251
}

opentelemetry-user-events-logs/src/logs/reentrant_logprocessor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ impl<T: LogExporter> opentelemetry_sdk::logs::LogProcessor for ReentrantLogProce
3636

3737
// Nothing to shutdown
3838
fn shutdown(&self) -> OTelSdkResult {
39+
// TODO: Actually invoke shutdown on the exporter
40+
// This cannot be done today as it requires mutable reference to exporter.
3941
Ok(())
4042
}
4143

stress/Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ version = "0.1.0"
44
edition = "2021"
55
publish = false
66

7-
[[bin]]
8-
name = "eventheader"
9-
path = "src/eventheader.rs"
10-
doc = false
11-
127
[[bin]]
138
name = "user_events"
149
path = "src/user_events.rs"
@@ -21,11 +16,9 @@ doc = false
2116

2217
[dependencies]
2318
ctrlc = "3.2.5"
24-
lazy_static = "1.4.0"
2519
num_cpus = "1.15.0"
2620
num-format = "0.4.4"
2721
sysinfo = { version = "0.32", optional = true }
28-
eventheader_dynamic = "0.4.0"
2922

3023
opentelemetry-appender-tracing = { workspace = true, features= ["spec_unstable_logs_enabled"] }
3124
opentelemetry_sdk = { workspace = true, features = ["logs", "spec_unstable_logs_enabled"] }

stress/src/etw_logs.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,18 @@ fn init_logger() -> SdkLoggerProvider {
4343
.build()
4444
}
4545

46-
// Function that performs the logging task
47-
fn log_event_task() {
48-
info!(
49-
name = "my-event-name",
50-
event_id = 20,
51-
user_name = "otel user",
52-
user_email = "otel@opentelemetry.io"
53-
);
54-
}
55-
5646
fn main() {
5747
let logger_provider = init_logger();
5848
let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
5949
tracing_subscriber::registry().with(layer).init();
6050

6151
throughput::test_throughput(|| {
62-
log_event_task();
52+
info!(
53+
name : "my-event-name",
54+
event_id = 20,
55+
user_name = "otel user",
56+
user_email = "otel@opentelemetry.io"
57+
);
6358
});
6459

6560
println!("Stress test completed.");

stress/src/eventheader.rs

Lines changed: 0 additions & 41 deletions
This file was deleted.

stress/src/user_events.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,6 @@ fn init_logger() -> SdkLoggerProvider {
3939
.build()
4040
}
4141

42-
// Function that performs the logging task
43-
fn log_event_task() {
44-
error!(
45-
name = "my-event-name",
46-
event_id = 20,
47-
user_name = "otel user",
48-
user_email = "otel@opentelemetry.io"
49-
);
50-
}
51-
5242
fn main() {
5343
// Initialize the logger
5444
let logger_provider = init_logger();
@@ -60,7 +50,12 @@ fn main() {
6050
// Use the provided stress test framework
6151
println!("Starting stress test for UserEventsExporter...");
6252
throughput::test_throughput(|| {
63-
log_event_task();
53+
error!(
54+
name : "my-event-name",
55+
event_id = 20,
56+
user_name = "otel user",
57+
user_email = "otel@opentelemetry.io"
58+
);
6459
});
6560
println!("Stress test completed.");
6661
}

0 commit comments

Comments
 (0)