Skip to content

Commit f3711a8

Browse files
committed
Add trace submodule
1 parent 01d8181 commit f3711a8

File tree

4 files changed

+283
-3
lines changed

4 files changed

+283
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ url = "2.1.1"
3939
serde_json = "1.0.51"
4040
serde = { version = "1.0.106", features = ["derive"] }
4141
serde_urlencoded = "0.6.1"
42+
rand = "0.7.3"
4243

4344
[dev-dependencies]
4445
http = "0.2.0"

src/headers/headers.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ impl Debug for Headers {
183183
}
184184
}
185185

186+
impl AsRef<Headers> for Headers {
187+
fn as_ref(&self) -> &Headers {
188+
self
189+
}
190+
}
191+
192+
impl AsMut<Headers> for Headers {
193+
fn as_mut(&mut self) -> &mut Headers {
194+
self
195+
}
196+
}
197+
186198
#[cfg(test)]
187199
mod tests {
188200
use super::*;

src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ mod version;
132132

133133
cfg_unstable! {
134134
pub mod upgrade;
135+
pub mod trace;
135136

136137
mod client;
137138
mod server;
@@ -165,10 +166,8 @@ pub use crate::url::Url;
165166
#[doc(inline)]
166167
pub use crate::cookies::Cookie;
167168

168-
#[doc(inline)]
169-
pub mod trailers;
170-
171169
pub mod security;
170+
pub mod trailers;
172171

173172
#[cfg(feature = "hyperium_http")]
174173
mod hyperium_http;

src/trace/mod.rs

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
//! Extract and inject [trace context](https://w3c.github.io/trace-context/) headers.
2+
//!
3+
//! ## Examples
4+
//!
5+
//! ```
6+
//! use http_types::trace::TraceContext;
7+
//!
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+
}
250+
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+
}
259+
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+
}

0 commit comments

Comments
 (0)