Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
trace_context.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
12
13#include <algorithm>
14#include <array>
15#include <cctype>
16#include <cstring>
17#include <random>
18#include <sstream>
19
21
22namespace {
23
24// Thread-local storage for current trace context
25thread_local trace_context tls_current_context;
26
27// Hex character lookup table
28constexpr std::array<char, 16> hex_chars = {'0', '1', '2', '3', '4', '5', '6', '7',
29 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
30
31// Convert single hex character to value
32auto hex_char_to_value(char c) -> int
33{
34 if (c >= '0' && c <= '9')
35 {
36 return c - '0';
37 }
38 if (c >= 'a' && c <= 'f')
39 {
40 return c - 'a' + 10;
41 }
42 if (c >= 'A' && c <= 'F')
43 {
44 return c - 'A' + 10;
45 }
46 return -1;
47}
48
49// Check if trace ID is all zeros (invalid)
50auto is_zero_trace_id(const trace_id_t& id) -> bool
51{
52 return std::all_of(id.begin(), id.end(), [](uint8_t b) { return b == 0; });
53}
54
55// Check if span ID is all zeros (invalid)
56auto is_zero_span_id(const span_id_t& id) -> bool
57{
58 return std::all_of(id.begin(), id.end(), [](uint8_t b) { return b == 0; });
59}
60
61// Thread-safe random number generator
62auto get_random_bytes(uint8_t* buffer, size_t size) -> void
63{
64 static thread_local std::random_device rd;
65 static thread_local std::mt19937_64 gen(rd());
66 static thread_local std::uniform_int_distribution<uint64_t> dist;
67
68 size_t offset = 0;
69 while (offset < size)
70 {
71 uint64_t value = dist(gen);
72 size_t to_copy = std::min(sizeof(value), size - offset);
73 std::memcpy(buffer + offset, &value, to_copy);
74 offset += to_copy;
75 }
76}
77
78} // anonymous namespace
79
81 std::optional<span_id_t> parent_span_id)
82 : trace_id_(trace_id)
83 , span_id_(span_id)
84 , parent_span_id_(std::move(parent_span_id))
85 , flags_(flags)
86 , valid_(!is_zero_trace_id(trace_id) && !is_zero_span_id(span_id))
87{
88}
89
91{
92 return tls_current_context;
93}
94
96{
97 tls_current_context = ctx;
98}
99
101{
102 tls_current_context = trace_context{};
103}
104
105auto trace_context::create_span(std::string_view name) -> span
106{
107 // If there's a current context, create a child span
108 if (tls_current_context.is_valid())
109 {
110 return tls_current_context.create_child_span(name);
111 }
112
113 // Create a new root span with a fresh trace context
114 auto new_trace_id = generate_trace_id();
115 auto new_span_id = generate_span_id();
116
117 trace_context new_ctx(new_trace_id, new_span_id, trace_flags::sampled);
118 return span(name, new_ctx);
119}
120
121auto trace_context::create_child_span(std::string_view name) const -> span
122{
123 if (!is_valid())
124 {
125 // If parent context is invalid, create a new root span
126 auto new_trace_id = generate_trace_id();
127 auto new_span_id = generate_span_id();
128
129 trace_context new_ctx(new_trace_id, new_span_id, trace_flags::sampled);
130 return span(name, new_ctx);
131 }
132
133 // Create child span with same trace ID but new span ID
134 auto new_span_id = generate_span_id();
135 trace_context child_ctx(trace_id_, new_span_id, flags_, span_id_);
136
137 return span(name, child_ctx);
138}
139
140auto trace_context::to_traceparent() const -> std::string
141{
142 if (!is_valid())
143 {
144 return {};
145 }
146
147 // Format: {version}-{trace-id}-{parent-id}-{trace-flags}
148 // Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
149 std::ostringstream oss;
150 oss << "00-";
151 oss << trace_id_hex();
152 oss << "-";
153 oss << span_id_hex();
154 oss << "-";
155 oss << (is_sampled() ? "01" : "00");
156
157 return oss.str();
158}
159
161 -> std::vector<std::pair<std::string, std::string>>
162{
163 std::vector<std::pair<std::string, std::string>> headers;
164
165 if (is_valid())
166 {
167 headers.emplace_back("traceparent", to_traceparent());
168 }
169
170 return headers;
171}
172
173auto trace_context::from_traceparent(std::string_view traceparent) -> trace_context
174{
175 // Minimum length check: 00-{32}-{16}-{2} = 55 characters
176 if (traceparent.size() < 55)
177 {
178 return trace_context{};
179 }
180
181 // Parse version
182 if (traceparent[0] != '0' || traceparent[1] != '0' || traceparent[2] != '-')
183 {
184 return trace_context{};
185 }
186
187 // Parse trace ID (32 hex chars)
189 if (!hex_to_bytes(traceparent.substr(3, 32), trace_id.data(), 16))
190 {
191 return trace_context{};
192 }
193
194 // Check separator
195 if (traceparent[35] != '-')
196 {
197 return trace_context{};
198 }
199
200 // Parse span ID (16 hex chars)
201 span_id_t span_id{};
202 if (!hex_to_bytes(traceparent.substr(36, 16), span_id.data(), 8))
203 {
204 return trace_context{};
205 }
206
207 // Check separator
208 if (traceparent[52] != '-')
209 {
210 return trace_context{};
211 }
212
213 // Parse flags (2 hex chars)
214 int high = hex_char_to_value(traceparent[53]);
215 int low = hex_char_to_value(traceparent[54]);
216 if (high < 0 || low < 0)
217 {
218 return trace_context{};
219 }
220
221 auto flags = static_cast<trace_flags>((high << 4) | low);
222
223 return trace_context(trace_id, span_id, flags);
224}
225
227 const std::vector<std::pair<std::string, std::string>>& headers) -> trace_context
228{
229 for (const auto& [name, value] : headers)
230 {
231 // Case-insensitive comparison
232 std::string lower_name = name;
233 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
234 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
235
236 if (lower_name == "traceparent")
237 {
238 auto ctx = from_traceparent(value);
239 if (ctx.is_valid())
240 {
241 return ctx;
242 }
243 }
244 }
245
246 return trace_context{};
247}
248
249auto trace_context::is_valid() const noexcept -> bool
250{
251 return valid_;
252}
253
254auto trace_context::is_sampled() const noexcept -> bool
255{
256 return (static_cast<uint8_t>(flags_) & static_cast<uint8_t>(trace_flags::sampled)) != 0;
257}
258
259auto trace_context::trace_id() const noexcept -> const trace_id_t&
260{
261 return trace_id_;
262}
263
264auto trace_context::span_id() const noexcept -> const span_id_t&
265{
266 return span_id_;
267}
268
269auto trace_context::parent_span_id() const noexcept -> const std::optional<span_id_t>&
270{
271 return parent_span_id_;
272}
273
274auto trace_context::flags() const noexcept -> trace_flags
275{
276 return flags_;
277}
278
279auto trace_context::trace_id_hex() const -> std::string
280{
281 return bytes_to_hex(trace_id_.data(), trace_id_.size());
282}
283
284auto trace_context::span_id_hex() const -> std::string
285{
286 return bytes_to_hex(span_id_.data(), span_id_.size());
287}
288
289auto trace_context::operator==(const trace_context& other) const noexcept -> bool
290{
291 return trace_id_ == other.trace_id_ && span_id_ == other.span_id_ &&
292 parent_span_id_ == other.parent_span_id_ && flags_ == other.flags_ &&
293 valid_ == other.valid_;
294}
295
296auto trace_context::operator!=(const trace_context& other) const noexcept -> bool
297{
298 return !(*this == other);
299}
300
302{
303 trace_id_t id{};
304 get_random_bytes(id.data(), id.size());
305 return id;
306}
307
309{
310 span_id_t id{};
311 get_random_bytes(id.data(), id.size());
312 return id;
313}
314
315auto bytes_to_hex(const uint8_t* data, size_t size) -> std::string
316{
317 std::string result;
318 result.reserve(size * 2);
319
320 for (size_t i = 0; i < size; ++i)
321 {
322 result.push_back(hex_chars[(data[i] >> 4) & 0x0F]);
323 result.push_back(hex_chars[data[i] & 0x0F]);
324 }
325
326 return result;
327}
328
329auto hex_to_bytes(std::string_view hex, uint8_t* out, size_t size) -> bool
330{
331 if (hex.size() != size * 2)
332 {
333 return false;
334 }
335
336 for (size_t i = 0; i < size; ++i)
337 {
338 int high = hex_char_to_value(hex[i * 2]);
339 int low = hex_char_to_value(hex[i * 2 + 1]);
340
341 if (high < 0 || low < 0)
342 {
343 return false;
344 }
345
346 out[i] = static_cast<uint8_t>((high << 4) | low);
347 }
348
349 return true;
350}
351
352} // namespace kcenon::network::tracing
RAII span for distributed tracing.
Definition span.h:103
Immutable trace context for distributed tracing.
static auto create_span(std::string_view name) -> span
Create a new root span with a new trace context.
auto trace_id() const noexcept -> const trace_id_t &
Get the trace ID.
auto span_id_hex() const -> std::string
Convert span ID to hex string.
static auto current() -> trace_context
Get the current trace context from thread-local storage.
static void clear_current()
Clear the current thread-local trace context.
auto trace_id_hex() const -> std::string
Convert trace ID to hex string.
static auto from_headers(const std::vector< std::pair< std::string, std::string > > &headers) -> trace_context
Parse trace context from HTTP headers.
auto to_headers() const -> std::vector< std::pair< std::string, std::string > >
Convert context to HTTP headers for propagation.
static auto from_traceparent(std::string_view traceparent) -> trace_context
Parse trace context from W3C traceparent header.
auto parent_span_id() const noexcept -> const std::optional< span_id_t > &
Get the parent span ID.
auto is_sampled() const noexcept -> bool
Check if this trace is sampled.
auto flags() const noexcept -> trace_flags
Get the trace flags.
auto operator==(const trace_context &other) const noexcept -> bool
Equality comparison.
auto create_child_span(std::string_view name) const -> span
Create a child span inheriting this context.
auto is_valid() const noexcept -> bool
Check if this context is valid.
std::optional< span_id_t > parent_span_id_
static void set_current(const trace_context &ctx)
Set the current thread-local trace context.
auto span_id() const noexcept -> const span_id_t &
Get the span ID.
trace_context()=default
Default constructor creates an invalid context.
auto operator!=(const trace_context &other) const noexcept -> bool
Inequality comparison.
auto to_traceparent() const -> std::string
Convert context to W3C traceparent header value.
auto bytes_to_hex(const uint8_t *data, size_t size) -> std::string
Convert bytes to lowercase hex string.
trace_flags
Trace flags (8-bit)
auto generate_span_id() -> span_id_t
Generate a random span ID.
std::array< uint8_t, 8 > span_id_t
Span ID type (64-bit identifier)
auto generate_trace_id() -> trace_id_t
Generate a random trace ID.
auto hex_to_bytes(std::string_view hex, uint8_t *out, size_t size) -> bool
Parse hex string to bytes.
@ trace_id
Sample based on trace ID ratio.
std::array< uint8_t, 16 > trace_id_t
Trace ID type (128-bit identifier)
RAII span implementation for distributed tracing.
Distributed tracing context for OpenTelemetry-compatible tracing.