|
1 | | -//! Extract and inject [trace context](https://w3c.github.io/trace-context/) headers. |
| 1 | +//! HTTP timings and traces. |
2 | 2 | //! |
3 | | -//! ## Examples |
| 3 | +//! This module implements parsers and serializers for timing-related headers. |
| 4 | +//! These headers enable tracing and timing requests, and help answer the |
| 5 | +//! question of: _"Where is my program spending its time?"_ |
4 | 6 | //! |
5 | | -//! ``` |
6 | | -//! use http_types::trace::TraceContext; |
| 7 | +//! # Specifications |
7 | 8 | //! |
8 | | -//! let mut res = http_types::Response::new(200); |
9 | | -//! |
10 | | -//! res.insert_header( |
11 | | -//! "traceparent", |
12 | | -//! "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01" |
13 | | -//! ); |
14 | | -//! |
15 | | -//! let context = TraceContext::extract(&res).unwrap(); |
16 | | -//! |
17 | | -//! let trace_id = u128::from_str_radix("0af7651916cd43dd8448eb211c80319c", 16); |
18 | | -//! let parent_id = u64::from_str_radix("00f067aa0ba902b7", 16); |
19 | | -//! |
20 | | -//! assert_eq!(context.trace_id(), trace_id.unwrap()); |
21 | | -//! assert_eq!(context.parent_id(), parent_id.ok()); |
22 | | -//! assert_eq!(context.sampled(), true); |
23 | | -//! ``` |
24 | | -
|
25 | | -use rand::Rng; |
26 | | -use std::fmt; |
27 | | - |
28 | | -use crate::Headers; |
29 | | - |
30 | | -/// A TraceContext object |
31 | | -#[derive(Debug)] |
32 | | -pub struct TraceContext { |
33 | | - id: u64, |
34 | | - version: u8, |
35 | | - trace_id: u128, |
36 | | - parent_id: Option<u64>, |
37 | | - flags: u8, |
38 | | -} |
39 | | - |
40 | | -impl TraceContext { |
41 | | - /// Create and return TraceContext object based on `traceparent` HTTP header. |
42 | | - /// |
43 | | - /// ## Examples |
44 | | - /// ``` |
45 | | - /// use http_types::trace::TraceContext; |
46 | | - /// |
47 | | - /// let mut res = http_types::Response::new(200); |
48 | | - /// res.insert_header( |
49 | | - /// "traceparent", |
50 | | - /// "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01" |
51 | | - /// ); |
52 | | - /// |
53 | | - /// let context = TraceContext::extract(&res).unwrap(); |
54 | | - /// |
55 | | - /// let trace_id = u128::from_str_radix("0af7651916cd43dd8448eb211c80319c", 16); |
56 | | - /// let parent_id = u64::from_str_radix("00f067aa0ba902b7", 16); |
57 | | - /// |
58 | | - /// assert_eq!(context.trace_id(), trace_id.unwrap()); |
59 | | - /// assert_eq!(context.parent_id(), parent_id.ok()); |
60 | | - /// assert_eq!(context.sampled(), true); |
61 | | - /// ``` |
62 | | - pub fn extract(headers: impl AsRef<Headers>) -> crate::Result<Self> { |
63 | | - let headers = headers.as_ref(); |
64 | | - let mut rng = rand::thread_rng(); |
65 | | - |
66 | | - let traceparent = match headers.get("traceparent") { |
67 | | - Some(header) => header.as_str(), |
68 | | - None => return Ok(Self::new_root()), |
69 | | - }; |
70 | | - |
71 | | - let parts: Vec<&str> = traceparent.split('-').collect(); |
72 | | - |
73 | | - Ok(Self { |
74 | | - id: rng.gen(), |
75 | | - version: u8::from_str_radix(parts[0], 16)?, |
76 | | - trace_id: u128::from_str_radix(parts[1], 16)?, |
77 | | - parent_id: Some(u64::from_str_radix(parts[2], 16)?), |
78 | | - flags: u8::from_str_radix(parts[3], 16)?, |
79 | | - }) |
80 | | - } |
81 | | - |
82 | | - /// Generate a new TraceContect object without a parent. |
83 | | - /// |
84 | | - /// By default root TraceContext objects are sampled. |
85 | | - /// To mark it unsampled, call `context.set_sampled(false)`. |
86 | | - /// |
87 | | - /// ## Examples |
88 | | - /// ``` |
89 | | - /// use http_types::trace::TraceContext; |
90 | | - /// |
91 | | - /// let context = TraceContext::new_root(); |
92 | | - /// |
93 | | - /// assert_eq!(context.parent_id(), None); |
94 | | - /// assert_eq!(context.sampled(), true); |
95 | | - /// ``` |
96 | | - pub fn new_root() -> Self { |
97 | | - let mut rng = rand::thread_rng(); |
98 | | - |
99 | | - Self { |
100 | | - id: rng.gen(), |
101 | | - version: 0, |
102 | | - trace_id: rng.gen(), |
103 | | - parent_id: None, |
104 | | - flags: 1, |
105 | | - } |
106 | | - } |
107 | | - |
108 | | - /// Add the traceparent header to the http headers |
109 | | - /// |
110 | | - /// ## Examples |
111 | | - /// ``` |
112 | | - /// use http_types::trace::TraceContext; |
113 | | - /// use http_types::{Request, Response, Url, Method}; |
114 | | - /// |
115 | | - /// let mut req = Request::new(Method::Get, Url::parse("https://example.com").unwrap()); |
116 | | - /// req.insert_header( |
117 | | - /// "traceparent", |
118 | | - /// "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01" |
119 | | - /// ); |
120 | | - /// |
121 | | - /// let parent = TraceContext::extract(&req).unwrap(); |
122 | | - /// |
123 | | - /// let mut res = Response::new(200); |
124 | | - /// parent.inject(&mut res); |
125 | | - /// |
126 | | - /// let child = TraceContext::extract(&res).unwrap(); |
127 | | - /// |
128 | | - /// assert_eq!(child.version(), parent.version()); |
129 | | - /// assert_eq!(child.trace_id(), parent.trace_id()); |
130 | | - /// assert_eq!(child.parent_id(), Some(parent.id())); |
131 | | - /// ``` |
132 | | - pub fn inject(&self, mut headers: impl AsMut<Headers>) { |
133 | | - let headers = headers.as_mut(); |
134 | | - headers.insert("traceparent", format!("{}", self)); |
135 | | - } |
136 | | - |
137 | | - /// Generate a child of the current TraceContext and return it. |
138 | | - /// |
139 | | - /// The child will have a new randomly genrated `id` and its `parent_id` will be set to the |
140 | | - /// `id` of this TraceContext. |
141 | | - pub fn child(&self) -> Self { |
142 | | - let mut rng = rand::thread_rng(); |
143 | | - |
144 | | - Self { |
145 | | - id: rng.gen(), |
146 | | - version: self.version, |
147 | | - trace_id: self.trace_id, |
148 | | - parent_id: Some(self.id), |
149 | | - flags: self.flags, |
150 | | - } |
151 | | - } |
152 | | - |
153 | | - /// Return the id of the TraceContext. |
154 | | - pub fn id(&self) -> u64 { |
155 | | - self.id |
156 | | - } |
157 | | - |
158 | | - /// Return the version of the TraceContext spec used. |
159 | | - /// |
160 | | - /// You probably don't need this. |
161 | | - pub fn version(&self) -> u8 { |
162 | | - self.version |
163 | | - } |
164 | | - |
165 | | - /// Return the trace id of the TraceContext. |
166 | | - /// |
167 | | - /// All children will have the same `trace_id`. |
168 | | - pub fn trace_id(&self) -> u128 { |
169 | | - self.trace_id |
170 | | - } |
171 | | - |
172 | | - /// Return the id of the parent TraceContext. |
173 | | - #[inline] |
174 | | - pub fn parent_id(&self) -> Option<u64> { |
175 | | - self.parent_id |
176 | | - } |
177 | | - |
178 | | - /// Returns true if the trace is sampled |
179 | | - /// |
180 | | - /// ## Examples |
181 | | - /// |
182 | | - /// ``` |
183 | | - /// use http_types::trace::TraceContext; |
184 | | - /// use http_types::Response; |
185 | | - /// |
186 | | - /// let mut res = Response::new(200); |
187 | | - /// res.insert_header("traceparent", "00-00000000000000000000000000000001-0000000000000002-01"); |
188 | | - /// let context = TraceContext::extract(&res).unwrap(); |
189 | | - /// assert_eq!(context.sampled(), true); |
190 | | - /// ``` |
191 | | - pub fn sampled(&self) -> bool { |
192 | | - (self.flags & 0b00000001) == 1 |
193 | | - } |
194 | | - |
195 | | - /// Change sampled flag |
196 | | - /// |
197 | | - /// ## Examples |
198 | | - /// |
199 | | - /// ``` |
200 | | - /// use http_types::trace::TraceContext; |
201 | | - /// |
202 | | - /// let mut context = TraceContext::new_root(); |
203 | | - /// assert_eq!(context.sampled(), true); |
204 | | - /// context.set_sampled(false); |
205 | | - /// assert_eq!(context.sampled(), false); |
206 | | - /// ``` |
207 | | - pub fn set_sampled(&mut self, sampled: bool) { |
208 | | - let x = sampled as u8; |
209 | | - self.flags ^= (x ^ self.flags) & (1 << 0); |
210 | | - } |
211 | | -} |
212 | | - |
213 | | -impl fmt::Display for TraceContext { |
214 | | - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
215 | | - write!( |
216 | | - f, |
217 | | - "{:02x}-{:032x}-{:016x}-{:02x}", |
218 | | - self.version, self.trace_id, self.id, self.flags |
219 | | - ) |
220 | | - } |
221 | | -} |
222 | | - |
223 | | -#[cfg(test)] |
224 | | -mod test { |
225 | | - use super::*; |
226 | | - |
227 | | - #[test] |
228 | | - fn default() -> crate::Result<()> { |
229 | | - let mut headers = crate::Headers::new(); |
230 | | - headers.insert("traceparent", "00-01-deadbeef-00"); |
231 | | - let context = TraceContext::extract(&mut headers)?; |
232 | | - assert_eq!(context.version(), 0); |
233 | | - assert_eq!(context.trace_id(), 1); |
234 | | - assert_eq!(context.parent_id().unwrap(), 3735928559); |
235 | | - assert_eq!(context.flags, 0); |
236 | | - assert_eq!(context.sampled(), false); |
237 | | - Ok(()) |
238 | | - } |
239 | | - |
240 | | - #[test] |
241 | | - fn no_header() -> crate::Result<()> { |
242 | | - let mut headers = crate::Headers::new(); |
243 | | - let context = TraceContext::extract(&mut headers)?; |
244 | | - assert_eq!(context.version(), 0); |
245 | | - assert_eq!(context.parent_id(), None); |
246 | | - assert_eq!(context.flags, 1); |
247 | | - assert_eq!(context.sampled(), true); |
248 | | - Ok(()) |
249 | | - } |
| 9 | +//! - [W3C Trace-Context headers](https://w3c.github.io/trace-context/) |
| 10 | +//! - [W3C Server-Timing headers](https://w3c.github.io/server-timing/#the-server-timing-header-field) |
250 | 11 |
|
251 | | - #[test] |
252 | | - fn not_sampled() -> crate::Result<()> { |
253 | | - let mut headers = crate::Headers::new(); |
254 | | - headers.insert("traceparent", "00-01-02-00"); |
255 | | - let context = TraceContext::extract(&mut headers)?; |
256 | | - assert_eq!(context.sampled(), false); |
257 | | - Ok(()) |
258 | | - } |
| 12 | +pub mod server_timing; |
| 13 | +mod trace_context; |
259 | 14 |
|
260 | | - #[test] |
261 | | - fn sampled() -> crate::Result<()> { |
262 | | - let mut headers = crate::Headers::new(); |
263 | | - headers.insert("traceparent", "00-01-02-01"); |
264 | | - let context = TraceContext::extract(&mut headers)?; |
265 | | - assert_eq!(context.sampled(), true); |
266 | | - Ok(()) |
267 | | - } |
268 | | -} |
| 15 | +#[doc(inline)] |
| 16 | +pub use server_timing::{Metric, ServerTiming}; |
| 17 | +pub use trace_context::TraceContext; |
0 commit comments