-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathtrace_context.rs
More file actions
132 lines (119 loc) · 4.92 KB
/
Copy pathtrace_context.rs
File metadata and controls
132 lines (119 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! W3C Trace Context propagation for distributed tracing.
//!
//! The GitHub Copilot CLI propagates [W3C Trace Context] headers (`traceparent`
//! and `tracestate`) so SDK consumers can correlate spans created by the
//! CLI with their own observability pipelines.
//!
//! Two injection paths are supported:
//!
//! - **Per-turn override** via [`MessageOptions::traceparent`] /
//! [`MessageOptions::tracestate`](crate::types::MessageOptions::tracestate),
//! which take precedence when set.
//! - **Ambient callback** via
//! [`ClientOptions::on_get_trace_context`](crate::ClientOptions::on_get_trace_context),
//! which the SDK invokes before `session.create`, `session.resume`, and
//! `session.send` whenever the per-turn override is absent.
//!
//! [W3C Trace Context]: https://www.w3.org/TR/trace-context/
//! [`MessageOptions::traceparent`]: crate::types::MessageOptions::traceparent
use async_trait::async_trait;
/// W3C Trace Context headers propagated to and from the GitHub Copilot CLI.
///
/// `traceparent` carries the trace and parent-span identifiers; `tracestate`
/// carries vendor-specific extensions. Either field may be `None` when the
/// caller has nothing to propagate; in that case the corresponding wire
/// field is omitted.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct TraceContext {
/// `traceparent` HTTP header value.
pub traceparent: Option<String>,
/// `tracestate` HTTP header value.
pub tracestate: Option<String>,
}
impl TraceContext {
/// Construct an empty [`TraceContext`]; both fields default to unset
/// (the SDK skips trace-context injection on the wire).
pub fn new() -> Self {
Self::default()
}
/// Construct a [`TraceContext`] from a `traceparent` header value, with
/// no `tracestate`.
///
/// Equivalent to `TraceContext::new().with_traceparent(value)`; kept
/// for ergonomics in the common single-header case.
pub fn from_traceparent(traceparent: impl Into<String>) -> Self {
Self::new().with_traceparent(traceparent)
}
/// Set or replace the `traceparent` header value, returning `self` for
/// chaining.
pub fn with_traceparent(mut self, traceparent: impl Into<String>) -> Self {
self.traceparent = Some(traceparent.into());
self
}
/// Set or replace the `tracestate` header value, returning `self` for
/// chaining.
pub fn with_tracestate(mut self, tracestate: impl Into<String>) -> Self {
self.tracestate = Some(tracestate.into());
self
}
/// Returns `true` when neither `traceparent` nor `tracestate` is set.
pub fn is_empty(&self) -> bool {
self.traceparent.is_none() && self.tracestate.is_none()
}
}
/// Async provider that returns the current [`TraceContext`] for outbound
/// session RPCs.
///
/// Set via
/// [`ClientOptions::on_get_trace_context`](crate::ClientOptions::on_get_trace_context).
/// The SDK invokes [`get_trace_context`](Self::get_trace_context) before
/// each `session.create`, `session.resume`, and `session.send` whenever
/// the call site does not carry a per-turn override.
///
/// Implementations should handle errors internally and return
/// [`TraceContext::default()`] to skip injection — no `Result` return type
/// is exposed because trace propagation is a best-effort observability
/// feature, not a correctness-critical RPC parameter.
#[async_trait]
pub trait TraceContextProvider: Send + Sync + 'static {
/// Return the current trace context, or [`TraceContext::default()`] to
/// skip injection.
async fn get_trace_context(&self) -> TraceContext;
}
/// Inject `traceparent` / `tracestate` from `ctx` into the JSON `params`
/// object if either field is set. No-op when both are `None`.
pub(crate) fn inject_trace_context(params: &mut serde_json::Value, ctx: &TraceContext) {
if let Some(tp) = &ctx.traceparent {
params["traceparent"] = serde_json::Value::String(tp.clone());
}
if let Some(ts) = &ctx.tracestate {
params["tracestate"] = serde_json::Value::String(ts.clone());
}
}
#[cfg(test)]
mod tests {
use super::TraceContext;
#[test]
fn new_yields_empty_context() {
let ctx = TraceContext::new();
assert!(ctx.is_empty());
assert!(ctx.traceparent.is_none());
assert!(ctx.tracestate.is_none());
}
#[test]
fn builder_composes_traceparent_and_tracestate() {
let ctx = TraceContext::new()
.with_traceparent("00-trace-span-01")
.with_tracestate("vendor=key");
assert_eq!(ctx.traceparent.as_deref(), Some("00-trace-span-01"));
assert_eq!(ctx.tracestate.as_deref(), Some("vendor=key"));
assert!(!ctx.is_empty());
}
#[test]
fn from_traceparent_matches_builder() {
let direct = TraceContext::from_traceparent("00-trace-span-01");
let chained = TraceContext::new().with_traceparent("00-trace-span-01");
assert_eq!(direct, chained);
}
}