40tracing_state g_tracing_state;
42std::mutex g_console_mutex;
45auto should_sample(
const trace_context& ctx,
double sample_rate) ->
bool
47 if (sample_rate >= 1.0)
51 if (sample_rate <= 0.0)
57 const auto&
trace_id = ctx.trace_id();
59 for (
size_t i = 0; i < 8 && i <
trace_id.size(); ++i)
65 double normalized =
static_cast<double>(hash) /
static_cast<double>(UINT64_MAX);
66 return normalized < sample_rate;
73 [](
auto&& arg) -> std::string
75 using T = std::decay_t<
decltype(arg)>;
76 if constexpr (std::is_same_v<T, std::string>)
78 return "\"" + arg +
"\"";
80 else if constexpr (std::is_same_v<T, bool>)
82 return arg ?
"true" :
"false";
84 else if constexpr (std::is_same_v<T, int64_t>)
86 return std::to_string(arg);
88 else if constexpr (std::is_same_v<T, double>)
90 std::ostringstream oss;
91 oss << std::fixed << std::setprecision(3) << arg;
103auto span_kind_to_string(
span_kind kind) -> std::string_view
122auto span_status_to_string(
span_status status) -> std::string_view
137void export_to_console(
const span& s)
139 const auto& ctx = s.context();
140 auto duration_ns = s.duration().count();
141 double duration_ms =
static_cast<double>(duration_ns) / 1'000'000.0;
143 std::ostringstream oss;
145 oss <<
"=== SPAN ===\n";
146 oss <<
"Name: " << s.name() <<
"\n";
147 oss <<
"Trace ID: " << ctx.trace_id_hex() <<
"\n";
148 oss <<
"Span ID: " << ctx.span_id_hex() <<
"\n";
150 if (ctx.parent_span_id().has_value())
152 oss <<
"Parent ID: " <<
bytes_to_hex(ctx.parent_span_id()->data(), 8) <<
"\n";
155 oss <<
"Kind: " << span_kind_to_string(s.kind()) <<
"\n";
156 oss <<
"Status: " << span_status_to_string(s.status());
158 if (!s.status_description().empty())
160 oss <<
" (" << s.status_description() <<
")";
164 oss << std::fixed << std::setprecision(3);
165 oss <<
"Duration: " << duration_ms <<
" ms\n";
168 const auto& attrs = s.attributes();
171 oss <<
"Attributes:\n";
172 for (
const auto& [key, value] : attrs)
174 oss <<
" " << key <<
": " << attribute_to_string(value) <<
"\n";
179 const auto& events = s.events();
183 for (
const auto& event : events)
185 oss <<
" - " <<
event.name;
186 if (!event.attributes.empty())
190 for (
const auto& [key, value] : event.attributes)
196 oss << key <<
": " << attribute_to_string(value);
205 oss <<
"============\n";
207 std::lock_guard<std::mutex> lock(g_console_mutex);
208 std::cout << oss.str() << std::flush;
212auto json_escape(
const std::string& s) -> std::string
214 std::ostringstream oss;
241 if (
static_cast<unsigned char>(c) < 0x20)
243 oss <<
"\\u" << std::hex << std::setfill(
'0') << std::setw(4)
244 <<
static_cast<int>(c);
259 [](
auto&& arg) -> std::string
261 using T = std::decay_t<
decltype(arg)>;
262 if constexpr (std::is_same_v<T, std::string>)
264 return "{\"stringValue\":\"" + json_escape(arg) +
"\"}";
266 else if constexpr (std::is_same_v<T, bool>)
268 return std::string(
"{\"boolValue\":") + (arg ?
"true" :
"false") +
"}";
270 else if constexpr (std::is_same_v<T, int64_t>)
272 return "{\"intValue\":\"" + std::to_string(arg) +
"\"}";
274 else if constexpr (std::is_same_v<T, double>)
276 std::ostringstream oss;
277 oss << std::fixed << std::setprecision(6) << arg;
278 return "{\"doubleValue\":" + oss.str() +
"}";
282 return "{\"stringValue\":\"unknown\"}";
289auto span_to_otlp_json(
const span& s) -> std::string
291 const auto& ctx = s.context();
292 std::ostringstream oss;
295 auto start_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
296 s.start_time().time_since_epoch())
298 auto end_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
299 s.end_time().time_since_epoch())
303 oss <<
"\"traceId\":\"" << ctx.trace_id_hex() <<
"\",";
304 oss <<
"\"spanId\":\"" << ctx.span_id_hex() <<
"\",";
306 if (ctx.parent_span_id().has_value())
308 oss <<
"\"parentSpanId\":\""
309 <<
bytes_to_hex(ctx.parent_span_id()->data(), 8) <<
"\",";
312 oss <<
"\"name\":\"" << json_escape(s.name()) <<
"\",";
313 oss <<
"\"kind\":" << (
static_cast<int>(s.kind()) + 1) <<
",";
314 oss <<
"\"startTimeUnixNano\":\"" << start_ns <<
"\",";
315 oss <<
"\"endTimeUnixNano\":\"" << end_ns <<
"\",";
318 oss <<
"\"attributes\":[";
319 const auto& attrs = s.attributes();
321 for (
const auto& [key, value] : attrs)
327 oss <<
"{\"key\":\"" << json_escape(key)
328 <<
"\",\"value\":" << attribute_to_json(value) <<
"}";
334 oss <<
"\"events\":[";
335 const auto& events = s.events();
337 for (
const auto& event : events)
343 auto event_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
344 event.timestamp.time_since_epoch())
346 oss <<
"{\"name\":\"" << json_escape(event.name) <<
"\",";
347 oss <<
"\"timeUnixNano\":\"" << event_ns <<
"\",";
348 oss <<
"\"attributes\":[";
349 bool attr_first =
true;
350 for (
const auto& [key, value] : event.attributes)
356 oss <<
"{\"key\":\"" << json_escape(key)
357 <<
"\",\"value\":" << attribute_to_json(value) <<
"}";
366 oss <<
"\"status\":{";
370 if (!s.status_description().empty())
372 oss <<
",\"message\":\"" << json_escape(s.status_description()) <<
"\"";
391auto build_otlp_request(
const std::vector<std::string>& spans_json,
392 const tracing_config& config) -> std::string
394 std::ostringstream oss;
396 oss <<
"{\"resourceSpans\":[{";
399 oss <<
"\"resource\":{\"attributes\":[";
400 oss <<
"{\"key\":\"service.name\",\"value\":{\"stringValue\":\""
405 oss <<
",{\"key\":\"service.version\",\"value\":{\"stringValue\":\""
411 oss <<
",{\"key\":\"service.namespace\",\"value\":{\"stringValue\":\""
417 oss <<
",{\"key\":\"service.instance.id\",\"value\":{\"stringValue\":\""
423 oss <<
",{\"key\":\"" << json_escape(key)
424 <<
"\",\"value\":{\"stringValue\":\"" << json_escape(value) <<
"\"}}";
430 oss <<
"\"scopeSpans\":[{";
431 oss <<
"\"scope\":{\"name\":\"network_system.tracing\",\"version\":\"1.0.0\"},";
432 oss <<
"\"spans\":[";
435 for (
const auto& span_json : spans_json)
451void export_otlp_http(
const std::vector<std::string>& spans_json)
453 if (spans_json.empty())
458 const auto&
config = g_tracing_state.config;
459 std::string body = build_otlp_request(spans_json, config);
463 std::lock_guard<std::mutex> lock(g_console_mutex);
464 std::cout <<
"[TRACING] Exporting " << spans_json.size()
466 std::cout <<
"[TRACING] Request body: " << body <<
"\n";
475void process_span(
const span& s)
477 if (!g_tracing_state.enabled.load(std::memory_order_acquire))
482 const auto&
config = g_tracing_state.config;
499 sampled = s.context().is_sampled();
512 export_to_console(s);
518 std::string span_json = span_to_otlp_json(s);
520 std::lock_guard<std::mutex> lock(g_tracing_state.batch_mutex);
521 g_tracing_state.batch_queue.push_back(std::move(span_json));
522 g_tracing_state.queued_count.fetch_add(1, std::memory_order_relaxed);
526 if (g_tracing_state.queued_count.load(std::memory_order_relaxed) >=
529 std::vector<std::string> batch;
531 std::lock_guard<std::mutex> lock(g_tracing_state.batch_mutex);
532 batch = std::move(g_tracing_state.batch_queue);
533 g_tracing_state.batch_queue.clear();
534 g_tracing_state.queued_count.store(0, std::memory_order_relaxed);
536 export_otlp_http(batch);
546 std::lock_guard<std::mutex> lock(g_console_mutex);
547 std::cout <<
"[TRACING] OTLP gRPC export not implemented, "
548 <<
"use otlp_http instead\n";
550 export_to_console(s);
559 std::lock_guard<std::mutex> lock(g_console_mutex);
560 std::cout <<
"[TRACING] Jaeger export not implemented, "
561 <<
"use otlp_http with Jaeger OTLP receiver\n";
563 export_to_console(s);
572 std::lock_guard<std::mutex> lock(g_console_mutex);
573 std::cout <<
"[TRACING] Zipkin export not implemented, "
574 <<
"use otlp_http with Zipkin OTLP receiver\n";
576 export_to_console(s);
587 std::lock_guard<std::mutex> lock(g_tracing_state.mutex);
588 for (
const auto& processor : g_tracing_state.processors)
604 std::lock_guard<std::mutex> lock(g_tracing_state.mutex);
606 g_tracing_state.config =
config;
608 std::memory_order_release);
613 std::lock_guard<std::mutex> lock(g_console_mutex);
614 std::cout <<
"[TRACING] Configured with exporter: ";
618 std::cout <<
"console";
646 std::lock_guard<std::mutex> lock(g_tracing_state.mutex);
648 g_tracing_state.enabled.store(
false, std::memory_order_release);
649 g_tracing_state.processors.clear();
654 std::lock_guard<std::mutex> batch_lock(g_tracing_state.batch_mutex);
655 g_tracing_state.batch_queue.clear();
656 g_tracing_state.queued_count.store(0, std::memory_order_relaxed);
663 std::vector<std::string> batch;
665 std::lock_guard<std::mutex> lock(g_tracing_state.batch_mutex);
666 if (!g_tracing_state.batch_queue.empty())
668 batch = std::move(g_tracing_state.batch_queue);
669 g_tracing_state.batch_queue.clear();
670 g_tracing_state.queued_count.store(0, std::memory_order_relaxed);
676 export_otlp_http(batch);
680 std::lock_guard<std::mutex> lock(g_console_mutex);
681 std::cout << std::flush;
687 return g_tracing_state.enabled.load(std::memory_order_acquire);
697 std::lock_guard<std::mutex> lock(g_tracing_state.mutex);
698 g_tracing_state.processors.push_back(std::move(callback));
RAII span for distributed tracing.
std::vector< std::string > batch_queue
std::atomic< bool > enabled
std::vector< span_processor_callback > processors
std::atomic< size_t > queued_count
@ otlp_http
OTLP over HTTP (OpenTelemetry Collector)
@ otlp_grpc
OTLP over gRPC (OpenTelemetry Collector)
@ console
Console/stdout output (for debugging)
@ jaeger
Jaeger native format.
void flush_tracing()
Force flush all pending spans.
std::variant< std::string, int64_t, double, bool > attribute_value
Attribute value type (supports string, int64, double, bool)
span_kind
Span kind following OpenTelemetry conventions.
@ consumer
Message consumer (e.g., queue subscriber)
@ client
Client-side request.
@ server
Server-side handling of a request.
@ internal
Default, represents internal operations.
@ producer
Message producer (e.g., queue publisher)
auto bytes_to_hex(const uint8_t *data, size_t size) -> std::string
Convert bytes to lowercase hex string.
auto is_tracing_enabled() -> bool
Check if tracing is enabled.
span_status
Span status codes following OpenTelemetry conventions.
@ ok
Operation completed successfully.
@ unset
Default status, span completed without explicit status.
void export_span(const span &s)
Export a completed span.
void register_span_processor(span_processor_callback callback)
Register a custom span processor.
@ trace_id
Sample based on trace ID ratio.
@ parent_based
Sample based on parent span's sampling decision.
@ always_off
Sample no traces.
@ always_on
Sample all traces.
void configure_tracing(const tracing_config &config)
Initialize the tracing system with configuration.
std::function< void(const span &)> span_processor_callback
Span processor callback type.
void shutdown_tracing()
Shutdown the tracing system.
RAII span implementation for distributed tracing.
size_t max_export_batch_size
Maximum batch size for a single export @default 512.
std::string endpoint
Endpoint URL for OTLP exporter @default "http://localhost:4317" for gRPC, "http://localhost:4318" for...
Main configuration structure for tracing.
std::string zipkin_endpoint
Zipkin exporter endpoint @default "http://localhost:9411/api/v2/spans".
exporter_type exporter
Exporter type to use @default exporter_type::none.
std::string service_instance_id
Service instance ID (unique per instance) @default "" (auto-generated if empty)
sampler_type sampler
Sampler type to use @default sampler_type::always_on.
std::string service_version
Service version @default "".
std::string service_namespace
Service namespace @default "".
std::string jaeger_endpoint
Jaeger exporter endpoint @default "http://localhost:14268/api/traces".
double sample_rate
Sampling rate (0.0 to 1.0)
std::map< std::string, std::string > resource_attributes
Additional resource attributes.
otlp_config otlp
OTLP exporter configuration.
std::string service_name
Service name for trace identification @default "network_system".
batch_config batch
Batch export configuration.
bool debug
Enable debug output @default false.
Distributed tracing context for OpenTelemetry-compatible tracing.
Configuration structures for OpenTelemetry tracing.