Skip to content

Commit b70e1c3

Browse files
authored
feat: User_Events exporter - add support for serializing traceid and spanid (#198)
1 parent ccca0df commit b70e1c3

File tree

4 files changed

+162
-8
lines changed

4 files changed

+162
-8
lines changed

opentelemetry-user-events-logs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
The provider name must:
1111
- Be less than 234 characters.
1212
- Contain only ASCII letters, digits, and the underscore (`'_'`) character.
13+
- Added support for TraceId,SpanId
1314

1415
## v0.10.0
1516

opentelemetry-user-events-logs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ futures-executor = "0.3"
2121

2222
[dev-dependencies]
2323
opentelemetry-appender-tracing = { workspace = true }
24+
opentelemetry_sdk = { workspace = true, features = ["logs", "trace"] }
2425
tracing = { version = "0.1", default-features = false, features = ["std"] }
2526
tracing-core = "0.1.31"
2627
tracing-subscriber = { version = "0.3.0", default-features = false, features = ["env-filter", "fmt", "registry", "std"] }

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

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ pub use logs::*;
1111
mod tests {
1212

1313
use super::*;
14+
use opentelemetry::trace::Tracer;
15+
use opentelemetry::trace::{TraceContextExt, TracerProvider};
1416
use opentelemetry_appender_tracing::layer;
15-
use opentelemetry_sdk::logs::LoggerProviderBuilder;
17+
use opentelemetry_sdk::{
18+
logs::LoggerProviderBuilder,
19+
trace::{Sampler, SdkTracerProvider},
20+
};
1621
use serde_json::{from_str, Value};
1722
use std::process::Command;
1823
use tracing::error;
@@ -25,7 +30,7 @@ mod tests {
2530
#[test]
2631
fn integration_test_basic() {
2732
// Run using the below command
28-
// sudo -E ~/.cargo/bin/cargo test integration -- --nocapture --ignored
33+
// sudo -E ~/.cargo/bin/cargo test integration_test_basic -- --nocapture --ignored
2934

3035
// Basic check if user_events are available
3136
check_user_events_available().expect("Kernel does not support user_events. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html.");
@@ -133,6 +138,143 @@ mod tests {
133138
);
134139
}
135140

141+
#[ignore]
142+
#[test]
143+
fn integration_test_with_tracing() {
144+
// Run using the below command
145+
// sudo -E ~/.cargo/bin/cargo test integration_test_with_tracing -- --nocapture --ignored
146+
147+
// Basic check if user_events are available
148+
check_user_events_available().expect("Kernel does not support user_events. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html.");
149+
150+
// setup tracing
151+
let tracer_provider = SdkTracerProvider::builder()
152+
.with_sampler(Sampler::AlwaysOn)
153+
.build();
154+
let tracer = tracer_provider.tracer("test-tracer");
155+
156+
let logger_provider = LoggerProviderBuilder::default()
157+
.with_user_event_exporter("myprovider")
158+
.build();
159+
160+
// Once provider with user_event exporter is created, it should create the TracePoints
161+
// following providername_level_k1 format
162+
// Validate that the TracePoints are created.
163+
let user_event_status = check_user_events_available().expect("Kernel does not support user_events. Verify your distribution/kernel supports user_events: https://docs.kernel.org/trace/user_events.html.");
164+
assert!(user_event_status.contains("myprovider_L1K1"));
165+
assert!(user_event_status.contains("myprovider_L2K1"));
166+
assert!(user_event_status.contains("myprovider_L3K1"));
167+
assert!(user_event_status.contains("myprovider_L4K1"));
168+
assert!(user_event_status.contains("myprovider_L5K1"));
169+
170+
let filter_otel =
171+
EnvFilter::new("info").add_directive("opentelemetry=off".parse().unwrap());
172+
let otel_layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
173+
let otel_layer = otel_layer.with_filter(filter_otel);
174+
let subscriber = tracing_subscriber::registry().with(otel_layer);
175+
let _guard = tracing::subscriber::set_default(subscriber);
176+
177+
// Start perf recording in a separate thread and emit logs in parallel.
178+
let perf_thread =
179+
std::thread::spawn(|| run_perf_and_decode(5, "user_events:myprovider_L2K1"));
180+
181+
// Give a little time for perf to start recording
182+
std::thread::sleep(std::time::Duration::from_millis(1000));
183+
184+
// ACT
185+
let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| {
186+
let trace_id = cx.span().span_context().trace_id();
187+
let span_id = cx.span().span_context().span_id();
188+
189+
// logging is done inside span context.
190+
error!(
191+
name: "my-event-name",
192+
target: "my-target",
193+
event_id = 20,
194+
user_name = "otel user",
195+
user_email = "otel.user@opentelemetry.com"
196+
);
197+
(trace_id, span_id)
198+
});
199+
200+
// Wait for the perf thread to complete and get the results
201+
let result = perf_thread.join().expect("Perf thread panicked");
202+
203+
assert!(result.is_ok());
204+
let json_content = result.unwrap();
205+
assert!(!json_content.is_empty());
206+
207+
let formatted_output = json_content.trim().to_string();
208+
/*
209+
// Sample output from perf-decode
210+
{
211+
"./perf.data": [
212+
{ "n": "myprovider:my-event-name", "__csver__": 1024, "PartA": { "time": "2025-03-07T16:31:28.279214367+00:00" }, "PartC": { "user_name": "otel user", "user_email": "otel.user@opentelemetry.com" }, "PartB": { "_typeName": "Log", "severityNumber": 2, "severityText": "ERROR", "eventId": 20, "name": "my-event-name" }, "meta": { "time": 81252.403220286, "cpu": 4, "pid": 21084, "tid": 21085, "level": 2, "keyword": "0x1" } } ]
213+
}
214+
*/
215+
216+
let json_value: Value = from_str(&formatted_output).expect("Failed to parse JSON");
217+
let perf_data_key = json_value
218+
.as_object()
219+
.expect("JSON is not an object")
220+
.keys()
221+
.find(|k| k.contains("perf.data"))
222+
.expect("No perf.data key found in JSON");
223+
224+
let events = json_value[perf_data_key]
225+
.as_array()
226+
.expect("Events for perf.data is not an array");
227+
228+
// Find the specific event. Its named providername:eventname format.
229+
let event = events
230+
.iter()
231+
.find(|e| {
232+
if let Some(name) = e.get("n") {
233+
name.as_str().unwrap_or("") == "myprovider:my-event-name"
234+
} else {
235+
false
236+
}
237+
})
238+
.expect("Event 'myprovider:my-event-name' not found");
239+
240+
// Validate event structure and fields
241+
assert_eq!(event["n"].as_str().unwrap(), "myprovider:my-event-name");
242+
assert_eq!(event["__csver__"].as_i64().unwrap(), 1024);
243+
244+
// Validate PartA
245+
let part_a = &event["PartA"];
246+
// Only check if the time field exists, not the actual value
247+
assert!(part_a.get("time").is_some(), "PartA.time is missing");
248+
249+
let part_a_ext_dt = part_a.get("ext_dt").expect("PartA.ext_dt is missing");
250+
251+
// Validate trace_id and span_id
252+
assert_eq!(
253+
part_a_ext_dt["traceId"].as_str().unwrap(),
254+
format!("{:x}", trace_id_expected)
255+
);
256+
assert_eq!(
257+
part_a_ext_dt["spanId"].as_str().unwrap(),
258+
format!("{:x}", span_id_expected)
259+
);
260+
261+
// Validate PartB
262+
let part_b = &event["PartB"];
263+
assert_eq!(part_b["_typeName"].as_str().unwrap(), "Log");
264+
assert_eq!(part_b["severityNumber"].as_i64().unwrap(), 2);
265+
assert_eq!(part_b["severityText"].as_str().unwrap(), "ERROR");
266+
assert_eq!(part_b["eventId"].as_i64().unwrap(), 20);
267+
assert_eq!(part_b["name"].as_str().unwrap(), "my-event-name");
268+
269+
// Validate PartC
270+
let part_c = &event["PartC"];
271+
assert_eq!(part_c["user_name"].as_str().unwrap(), "otel user");
272+
assert_eq!(
273+
part_c["user_email"].as_str().unwrap(),
274+
"otel.user@opentelemetry.com"
275+
);
276+
}
277+
136278
fn check_user_events_available() -> Result<String, String> {
137279
let output = Command::new("sudo")
138280
.arg("cat")

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,24 @@ impl UserEventsExporter {
193193
.timestamp()
194194
.or(log_record.observed_timestamp())
195195
.unwrap_or_else(SystemTime::now);
196+
let time: String = chrono::DateTime::to_rfc3339(
197+
&chrono::DateTime::<chrono::Utc>::from(event_time),
198+
);
196199
cs_a_count += 1; // for event_time
197-
eb.add_struct("PartA", cs_a_count, 0);
198-
{
199-
let time: String = chrono::DateTime::to_rfc3339(
200-
&chrono::DateTime::<chrono::Utc>::from(event_time),
201-
);
202-
eb.add_str("time", time, FieldFormat::Default, 0);
200+
201+
if let Some(trace_context) = log_record.trace_context() {
202+
cs_a_count += 1; // for ext_dt
203+
eb.add_struct("PartA", cs_a_count, 0);
204+
eb.add_struct("ext_dt", 2, 0);
205+
eb.add_str("traceId", trace_context.trace_id.to_string(), FieldFormat::Default, 0);
206+
eb.add_str("spanId", trace_context.span_id.to_string(), FieldFormat::Default, 0);
207+
}
208+
else {
209+
eb.add_struct("PartA", cs_a_count, 0);
203210
}
211+
212+
eb.add_str("time", time, FieldFormat::Default, 0);
213+
204214
//populate CS PartC
205215
let (mut is_event_id, mut event_id) = (false, 0);
206216
let (mut is_part_c_present, mut cs_c_bookmark, mut cs_c_count) = (false, 0, 0);

0 commit comments

Comments
 (0)