tracing_honeycomb/
visitor.rs

1use chrono::{DateTime, Utc};
2use libhoney::{json, Value};
3use std::collections::HashMap;
4use std::fmt;
5use tracing::field::{Field, Visit};
6use tracing_distributed::{Event, Span};
7
8use crate::{SpanId, TraceId};
9
10const MILLIS_PER_SECOND: f64 = 1000_f64;
11
12// Visitor that builds honeycomb-compatible values from tracing fields.
13#[derive(Default, Debug)]
14#[doc(hidden)]
15pub struct HoneycombVisitor(pub(crate) HashMap<String, Value>);
16
17// reserved field names (TODO: document)
18static RESERVED_WORDS: [&str; 9] = [
19    "trace.span_id",
20    "trace.trace_id",
21    "trace.parent_id",
22    "service_name",
23    "level",
24    "Timestamp",
25    "name",
26    "target",
27    "duration_ms",
28];
29
30impl Visit for HoneycombVisitor {
31    fn record_i64(&mut self, field: &Field, value: i64) {
32        self.0
33            .insert(mk_field_name(field.name().to_string()), json!(value));
34    }
35
36    fn record_u64(&mut self, field: &Field, value: u64) {
37        self.0
38            .insert(mk_field_name(field.name().to_string()), json!(value));
39    }
40
41    fn record_bool(&mut self, field: &Field, value: bool) {
42        self.0
43            .insert(mk_field_name(field.name().to_string()), json!(value));
44    }
45
46    fn record_str(&mut self, field: &Field, value: &str) {
47        self.0
48            .insert(mk_field_name(field.name().to_string()), json!(value));
49    }
50
51    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
52        let s = format!("{:?}", value);
53        self.0
54            .insert(mk_field_name(field.name().to_string()), json!(s));
55    }
56}
57
58fn mk_field_name(s: String) -> String {
59    // TODO: do another pass, optimize for efficiency (lazy static set?)
60    if RESERVED_WORDS.contains(&&s[..]) {
61        format!("tracing.{}", s)
62    } else {
63        s
64    }
65}
66
67pub(crate) fn event_to_values(
68    event: Event<HoneycombVisitor, SpanId, TraceId>,
69) -> (HashMap<String, libhoney::Value>, DateTime<Utc>) {
70    let mut values = event.values.0;
71
72    values.insert(
73        // magic honeycomb string (trace.trace_id)
74        "trace.trace_id".to_string(),
75        // using explicit trace id passed in from ctx (req'd for lazy eval)
76        json!(event.trace_id.to_string()),
77    );
78
79    values.insert(
80        // magic honeycomb string (trace.parent_id)
81        "trace.parent_id".to_string(),
82        event
83            .parent_id
84            .map(|pid| json!(pid.to_string()))
85            .unwrap_or(json!(null)),
86    );
87
88    // magic honeycomb string (service_name)
89    values.insert("service_name".to_string(), json!(event.service_name));
90
91    values.insert(
92        "level".to_string(),
93        json!(format!("{}", event.meta.level())),
94    );
95
96    // not honeycomb-special but tracing-provided
97    values.insert("name".to_string(), json!(event.meta.name()));
98    values.insert("target".to_string(), json!(event.meta.target()));
99
100    (values, event.initialized_at.into())
101}
102
103pub(crate) fn span_to_values(
104    span: Span<HoneycombVisitor, SpanId, TraceId>,
105) -> (HashMap<String, libhoney::Value>, DateTime<Utc>) {
106    let mut values = span.values.0;
107
108    values.insert(
109        // magic honeycomb string (trace.span_id)
110        "trace.span_id".to_string(),
111        json!(span.id.to_string()),
112    );
113
114    values.insert(
115        // magic honeycomb string (trace.trace_id)
116        "trace.trace_id".to_string(),
117        // using explicit trace id passed in from ctx (req'd for lazy eval)
118        json!(span.trace_id.to_string()),
119    );
120
121    values.insert(
122        // magic honeycomb string (trace.parent_id)
123        "trace.parent_id".to_string(),
124        span.parent_id
125            .map(|pid| json!(pid.to_string()))
126            .unwrap_or(json!(null)),
127    );
128
129    // magic honeycomb string (service_name)
130    values.insert("service_name".to_string(), json!(span.service_name));
131
132    values.insert("level".to_string(), json!(format!("{}", span.meta.level())));
133
134    // not honeycomb-special but tracing-provided
135    values.insert("name".to_string(), json!(span.meta.name()));
136    values.insert("target".to_string(), json!(span.meta.target()));
137
138    match span.completed_at.duration_since(span.initialized_at) {
139        Ok(d) => {
140            // honeycomb-special (I think, todo: get full list of known values)
141            values.insert(
142                "duration_ms".to_string(),
143                json!(d.as_secs_f64() * MILLIS_PER_SECOND),
144            );
145        }
146        Err(e) => {
147            eprintln!("error comparing system times in tracing-honeycomb, indicates possible clock skew: {:?}", e);
148        }
149    }
150
151    (values, span.initialized_at.into())
152}