Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_distributed_tracing.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#include <gtest/gtest.h>
11#include <thread>
12#include <chrono>
13#include <unordered_map>
16
17using namespace kcenon::monitoring;
18
19class DistributedTracingTest : public ::testing::Test {
20protected:
22
23 void SetUp() override {
24 // Reset global tracer state if needed
25 }
26
27 void TearDown() override {
28 // Cleanup
29 }
30};
31
33 auto span_result = tracer.start_span("test_operation", "test_service");
34 ASSERT_TRUE(span_result.is_ok());
35
36 auto span = span_result.value();
37 EXPECT_FALSE(span->trace_id.empty());
38 EXPECT_FALSE(span->span_id.empty());
39 EXPECT_TRUE(span->parent_span_id.empty());
40 EXPECT_EQ(span->operation_name, "test_operation");
41 EXPECT_EQ(span->service_name, "test_service");
42 EXPECT_FALSE(span->is_finished());
43}
44
45TEST_F(DistributedTracingTest, CreateChildSpan) {
46 auto parent_result = tracer.start_span("parent_operation");
47 ASSERT_TRUE(parent_result.is_ok());
48 auto parent = parent_result.value();
49
50 auto child_result = tracer.start_child_span(*parent, "child_operation");
51 ASSERT_TRUE(child_result.is_ok());
52 auto child = child_result.value();
53
54 EXPECT_EQ(child->trace_id, parent->trace_id);
55 EXPECT_NE(child->span_id, parent->span_id);
56 EXPECT_EQ(child->parent_span_id, parent->span_id);
57 EXPECT_EQ(child->operation_name, "child_operation");
58}
59
61 auto span_result = tracer.start_span("test_operation");
62 ASSERT_TRUE(span_result.is_ok());
63 auto span = span_result.value();
64
65 // Add some delay to have measurable duration
66 std::this_thread::sleep_for(std::chrono::milliseconds(10));
67
68 auto finish_result = tracer.finish_span(span);
69 ASSERT_TRUE(finish_result.is_ok());
70
71 EXPECT_TRUE(span->is_finished());
72 EXPECT_GT(span->duration.count(), 0);
73 EXPECT_EQ(span->status, trace_span::status_code::ok);
74}
75
76TEST_F(DistributedTracingTest, CannotFinishSpanTwice) {
77 auto span_result = tracer.start_span("test_operation");
78 ASSERT_TRUE(span_result.is_ok());
79 auto span = span_result.value();
80
81 auto first_finish = tracer.finish_span(span);
82 ASSERT_TRUE(first_finish.is_ok());
83
84 auto second_finish = tracer.finish_span(span);
85 ASSERT_TRUE(second_finish.is_err());
86}
87
88TEST_F(DistributedTracingTest, TraceContextPropagation) {
89 auto span_result = tracer.start_span("test_operation");
90 ASSERT_TRUE(span_result.is_ok());
91 auto span = span_result.value();
92
93 // Add baggage
94 span->baggage["user_id"] = "12345";
95 span->baggage["request_type"] = "api";
96
97 // Extract context - explicitly use const trace_span&
98 const trace_span& span_ref = *span;
99 auto context = tracer.extract_context(span_ref);
100 EXPECT_EQ(context.trace_id, span->trace_id);
101 EXPECT_EQ(context.span_id, span->span_id);
102 EXPECT_EQ(context.baggage["user_id"], "12345");
103 EXPECT_EQ(context.baggage["request_type"], "api");
104}
105
106TEST_F(DistributedTracingTest, W3CTraceContextFormat) {
107 trace_context ctx;
108 ctx.trace_id = "0af7651916cd43dd8448eb211c80319c";
109 ctx.span_id = "b7ad6b7169203331";
110 ctx.trace_flags = "01";
111
112 auto header = ctx.to_w3c_traceparent();
113 EXPECT_EQ(header, "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01");
114
115 auto parsed_result = trace_context::from_w3c_traceparent(header);
116 ASSERT_TRUE(parsed_result.is_ok());
117 auto parsed = parsed_result.value();
118
119 EXPECT_EQ(parsed.trace_id, ctx.trace_id);
120 EXPECT_EQ(parsed.span_id, ctx.span_id);
121 EXPECT_EQ(parsed.trace_flags, ctx.trace_flags);
122}
123
124TEST_F(DistributedTracingTest, InjectExtractContext) {
125 auto span_result = tracer.start_span("test_operation");
126 ASSERT_TRUE(span_result.is_ok());
127 auto span = span_result.value();
128
129 span->baggage["test_key"] = "test_value";
130
131 // Inject into carrier (simulating HTTP headers)
132 std::unordered_map<std::string, std::string> headers;
133 auto context = tracer.extract_context(*span);
134 tracer.inject_context(context, headers);
135
136 EXPECT_TRUE(headers.contains("traceparent"));
137 EXPECT_TRUE(headers.contains("baggage-test_key"));
138
139 // Verify traceparent header format (00-traceid-spanid-flags)
140 auto traceparent = headers["traceparent"];
141 EXPECT_EQ(traceparent.substr(0, 3), "00-");
142 EXPECT_FALSE(traceparent.empty());
143
144 // Extract from carrier
145 auto extracted_result = tracer.extract_context_from_carrier(headers);
146 ASSERT_TRUE(extracted_result.is_ok());
147 auto extracted = extracted_result.value();
148
149 // Verify baggage is preserved through inject/extract cycle
150 EXPECT_EQ(extracted.baggage["test_key"], "test_value");
151
152 // Verify trace_id and span_id are extracted (may differ from original due to W3C format)
153 EXPECT_FALSE(extracted.trace_id.empty());
154 EXPECT_FALSE(extracted.span_id.empty());
155}
156
157TEST_F(DistributedTracingTest, StartSpanFromContext) {
158 // Simulate incoming request with trace context
159 trace_context incoming_ctx;
160 incoming_ctx.trace_id = "0af7651916cd43dd8448eb211c80319c";
161 incoming_ctx.span_id = "b7ad6b7169203331";
162 incoming_ctx.baggage["user_id"] = "67890";
163
164 auto span_result = tracer.start_span_from_context(incoming_ctx, "handle_request");
165 ASSERT_TRUE(span_result.is_ok());
166 auto span = span_result.value();
167
168 EXPECT_EQ(span->trace_id, incoming_ctx.trace_id);
169 EXPECT_NE(span->span_id, incoming_ctx.span_id); // New span ID
170 EXPECT_EQ(span->parent_span_id, incoming_ctx.span_id);
171 EXPECT_EQ(span->baggage["user_id"], "67890");
172}
173
174TEST_F(DistributedTracingTest, CurrentSpanManagement) {
175 EXPECT_EQ(tracer.get_current_span(), nullptr);
176
177 auto span_result = tracer.start_span("test_operation");
178 ASSERT_TRUE(span_result.is_ok());
179 auto span = span_result.value();
180
181 tracer.set_current_span(span);
182 EXPECT_EQ(tracer.get_current_span(), span);
183
184 // Different thread should have different current span
185 std::thread other_thread([&]() {
186 EXPECT_EQ(tracer.get_current_span(), nullptr);
187
188 auto other_span_result = tracer.start_span("other_operation");
189 ASSERT_TRUE(other_span_result.is_ok());
190 auto other_span = other_span_result.value();
191
192 tracer.set_current_span(other_span);
193 EXPECT_EQ(tracer.get_current_span(), other_span);
194 });
195 other_thread.join();
196
197 // Original thread should still have its span
198 EXPECT_EQ(tracer.get_current_span(), span);
199}
200
202 {
203 auto span_result = tracer.start_span("scoped_operation");
204 ASSERT_TRUE(span_result.is_ok());
205 scoped_span scoped(span_result.value(), &tracer);
206
207 EXPECT_EQ(tracer.get_current_span(), span_result.value());
208 EXPECT_FALSE(scoped->is_finished());
209
210 // Span should be accessible via scoped object
211 scoped->tags["custom_tag"] = "custom_value";
212 }
213 // Span should be finished after leaving scope
214 // Note: We can't directly check as the span is finished and stored
215}
216
218 auto span1_result = tracer.start_span("operation1");
219 ASSERT_TRUE(span1_result.is_ok());
220 auto span1 = span1_result.value();
221
222 auto span2_result = tracer.start_child_span(*span1, "operation2");
223 ASSERT_TRUE(span2_result.is_ok());
224 auto span2 = span2_result.value();
225
226 auto span3_result = tracer.start_child_span(*span2, "operation3");
227 ASSERT_TRUE(span3_result.is_ok());
228 auto span3 = span3_result.value();
229
230 // Finish all spans
231 tracer.finish_span(span1);
232 tracer.finish_span(span2);
233 tracer.finish_span(span3);
234
235 // Get all spans for the trace
236 auto trace_result = tracer.get_trace(span1->trace_id);
237 ASSERT_TRUE(trace_result.is_ok());
238 auto trace = trace_result.value();
239
240 EXPECT_EQ(trace.size(), 3);
241
242 // All spans should have the same trace ID
243 for (const auto& span : trace) {
244 EXPECT_EQ(span.trace_id, span1->trace_id);
245 EXPECT_TRUE(span.is_finished());
246 }
247}
248
250 auto span_result = tracer.start_span("tagged_operation", "my_service");
251 ASSERT_TRUE(span_result.is_ok());
252 auto span = span_result.value();
253
254 // Check default tags
255 EXPECT_EQ(span->tags["span.kind"], "internal");
256 EXPECT_EQ(span->tags["service.name"], "my_service");
257
258 // Add custom tags
259 span->tags["http.method"] = "GET";
260 span->tags["http.status_code"] = "200";
261 span->tags["user.id"] = "user123";
262
263 EXPECT_EQ(span->tags["http.method"], "GET");
264 EXPECT_EQ(span->tags["http.status_code"], "200");
265 EXPECT_EQ(span->tags["user.id"], "user123");
266}
267
269 auto span_result = tracer.start_span("status_operation");
270 ASSERT_TRUE(span_result.is_ok());
271 auto span = span_result.value();
272
273 EXPECT_EQ(span->status, trace_span::status_code::unset);
274
275 // Set error status
276 span->status = trace_span::status_code::error;
277 span->status_message = "Operation failed due to timeout";
278
279 tracer.finish_span(span);
280
281 EXPECT_EQ(span->status, trace_span::status_code::error);
282 EXPECT_EQ(span->status_message, "Operation failed due to timeout");
283}
284
285TEST_F(DistributedTracingTest, BaggagePropagation) {
286 auto parent_result = tracer.start_span("parent");
287 ASSERT_TRUE(parent_result.is_ok());
288 auto parent = parent_result.value();
289
290 parent->baggage["session_id"] = "abc123";
291 parent->baggage["feature_flag"] = "enabled";
292
293 auto child_result = tracer.start_child_span(*parent, "child");
294 ASSERT_TRUE(child_result.is_ok());
295 auto child = child_result.value();
296
297 // Child should inherit baggage
298 EXPECT_EQ(child->baggage["session_id"], "abc123");
299 EXPECT_EQ(child->baggage["feature_flag"], "enabled");
300
301 // Child can add its own baggage
302 child->baggage["child_data"] = "xyz";
303
304 auto grandchild_result = tracer.start_child_span(*child, "grandchild");
305 ASSERT_TRUE(grandchild_result.is_ok());
306 auto grandchild = grandchild_result.value();
307
308 // Grandchild should have all baggage
309 EXPECT_EQ(grandchild->baggage["session_id"], "abc123");
310 EXPECT_EQ(grandchild->baggage["feature_flag"], "enabled");
311 EXPECT_EQ(grandchild->baggage["child_data"], "xyz");
312}
313
315 std::vector<trace_span> spans_to_export;
316
317 // Create and finish spans
318 for (int i = 0; i < 5; ++i) {
319 auto span_result = tracer.start_span("operation_" + std::to_string(i));
320 ASSERT_TRUE(span_result.is_ok());
321 auto span = span_result.value();
322
323 tracer.finish_span(span);
324 spans_to_export.push_back(*span);
325 }
326
327 auto export_result = tracer.export_spans(spans_to_export);
328 ASSERT_TRUE(export_result.is_ok());
329
330 // Verify spans were stored
331 auto trace_result = tracer.get_trace(spans_to_export[0].trace_id);
332 ASSERT_TRUE(trace_result.is_ok());
333 auto trace = trace_result.value();
334
335 // Note: In this test, all spans have different trace IDs
336 // In a real scenario, they might be part of the same trace
337}
338
339// Test with macros
341 {
342 TRACE_SPAN("macro_operation");
343
344 // The span should be active
345 auto current = global_tracer().get_current_span();
346 ASSERT_NE(current, nullptr);
347 EXPECT_EQ(current->operation_name, "macro_operation");
348
349 // Nested span
350 {
351 TRACE_CHILD_SPAN(*current, "nested_operation");
352 auto nested = global_tracer().get_current_span();
353 ASSERT_NE(nested, nullptr);
354 EXPECT_EQ(nested->operation_name, "nested_operation");
355 EXPECT_EQ(nested->parent_span_id, current->span_id);
356 }
357 }
358 // Spans should be finished after leaving scope
359}
360
361// Mock exporter for testing exporter integration
363public:
364 std::vector<trace_span> exported_spans;
365 std::atomic<std::size_t> export_count{0};
366 std::atomic<std::size_t> flush_count{0};
367 std::atomic<std::size_t> shutdown_count{0};
368 bool should_fail = false;
369
370 kcenon::common::VoidResult export_spans(const std::vector<trace_span>& spans) override {
371 if (should_fail) {
372 return kcenon::common::VoidResult::err(error_info(monitoring_error_code::operation_failed,
373 "Mock export failure", "test").to_common_error());
374 }
375 for (const auto& span : spans) {
376 exported_spans.push_back(span);
377 }
378 export_count++;
379 return kcenon::common::ok();
380 }
381
382 kcenon::common::VoidResult flush() override {
383 flush_count++;
384 return kcenon::common::ok();
385 }
386
387 kcenon::common::VoidResult shutdown() override {
389 return kcenon::common::ok();
390 }
391
392 std::unordered_map<std::string, std::size_t> get_stats() const override {
393 return {
394 {"exported_spans", exported_spans.size()},
395 {"export_count", export_count.load()},
396 {"flush_count", flush_count.load()},
397 {"shutdown_count", shutdown_count.load()}
398 };
399 }
400};
401
402class ExporterIntegrationTest : public ::testing::Test {
403protected:
405 std::shared_ptr<MockTraceExporter> mock_exporter;
406
407 void SetUp() override {
408 mock_exporter = std::make_shared<MockTraceExporter>();
409 }
410
411 void TearDown() override {
412 tracer.set_exporter(nullptr);
413 }
414};
415
416TEST_F(ExporterIntegrationTest, SetAndGetExporter) {
417 EXPECT_EQ(tracer.get_exporter(), nullptr);
418
419 tracer.set_exporter(mock_exporter);
420 EXPECT_EQ(tracer.get_exporter(), mock_exporter);
421
422 tracer.set_exporter(nullptr);
423 EXPECT_EQ(tracer.get_exporter(), nullptr);
424}
425
426TEST_F(ExporterIntegrationTest, ConfigureExportSettings) {
427 trace_export_settings settings;
428 settings.batch_size = 50;
429 settings.max_queue_size = 1000;
430 settings.export_on_finish = true;
431
432 tracer.configure_export(settings);
433
434 auto retrieved = tracer.get_export_settings();
435 EXPECT_EQ(retrieved.batch_size, 50);
436 EXPECT_EQ(retrieved.max_queue_size, 1000);
437 EXPECT_TRUE(retrieved.export_on_finish);
438}
439
440TEST_F(ExporterIntegrationTest, AutoExportWhenBatchSizeReached) {
441 tracer.set_exporter(mock_exporter);
442
443 trace_export_settings settings;
444 settings.batch_size = 5;
445 settings.export_on_finish = true;
446 tracer.configure_export(settings);
447
448 // Create and finish enough spans to trigger export
449 for (int i = 0; i < 5; ++i) {
450 auto span_result = tracer.start_span("operation_" + std::to_string(i));
451 ASSERT_TRUE(span_result.is_ok());
452 tracer.finish_span(span_result.value());
453 }
454
455 // Should have triggered one export
456 EXPECT_EQ(mock_exporter->export_count.load(), 1);
457 EXPECT_EQ(mock_exporter->exported_spans.size(), 5);
458}
459
461 tracer.set_exporter(mock_exporter);
462
463 trace_export_settings settings;
464 settings.batch_size = 100; // Large batch size to prevent auto-export
465 settings.export_on_finish = true;
466 tracer.configure_export(settings);
467
468 // Create a few spans (less than batch size)
469 for (int i = 0; i < 3; ++i) {
470 auto span_result = tracer.start_span("operation_" + std::to_string(i));
471 ASSERT_TRUE(span_result.is_ok());
472 tracer.finish_span(span_result.value());
473 }
474
475 // No auto-export should have happened
476 EXPECT_EQ(mock_exporter->export_count.load(), 0);
477
478 // Manual flush
479 auto flush_result = tracer.flush();
480 ASSERT_TRUE(flush_result.is_ok());
481
482 // Now spans should be exported
483 EXPECT_EQ(mock_exporter->export_count.load(), 1);
484 EXPECT_EQ(mock_exporter->exported_spans.size(), 3);
485}
486
487TEST_F(ExporterIntegrationTest, FlushWithNoExporter) {
488 // No exporter set
489 EXPECT_EQ(tracer.get_exporter(), nullptr);
490
491 // Create and finish some spans
492 for (int i = 0; i < 3; ++i) {
493 auto span_result = tracer.start_span("operation_" + std::to_string(i));
494 ASSERT_TRUE(span_result.is_ok());
495 tracer.finish_span(span_result.value());
496 }
497
498 // Flush should succeed (just clears the buffer)
499 auto flush_result = tracer.flush();
500 EXPECT_TRUE(flush_result.is_ok());
501}
502
503TEST_F(ExporterIntegrationTest, ExportFailureRetainsSpans) {
504 mock_exporter->should_fail = true;
505 tracer.set_exporter(mock_exporter);
506
507 trace_export_settings settings;
508 settings.batch_size = 100;
509 settings.max_queue_size = 200;
510 tracer.configure_export(settings);
511
512 // Create some spans
513 for (int i = 0; i < 3; ++i) {
514 auto span_result = tracer.start_span("operation_" + std::to_string(i));
515 ASSERT_TRUE(span_result.is_ok());
516 tracer.finish_span(span_result.value());
517 }
518
519 // Flush should fail
520 auto flush_result = tracer.flush();
521 EXPECT_TRUE(flush_result.is_err());
522
523 // Spans should still be in queue (we can verify via stats)
524 auto stats = tracer.get_export_stats();
525 EXPECT_EQ(stats["pending_spans"], 3);
526
527 // Now fix the exporter and try again
528 mock_exporter->should_fail = false;
529 flush_result = tracer.flush();
530 EXPECT_TRUE(flush_result.is_ok());
531
532 // Now spans should be exported
533 EXPECT_EQ(mock_exporter->exported_spans.size(), 3);
534}
535
537 tracer.set_exporter(mock_exporter);
538
539 trace_export_settings settings;
540 settings.batch_size = 5;
541 tracer.configure_export(settings);
542
543 // Create and finish spans
544 for (int i = 0; i < 10; ++i) {
545 auto span_result = tracer.start_span("operation_" + std::to_string(i));
546 ASSERT_TRUE(span_result.is_ok());
547 tracer.finish_span(span_result.value());
548 }
549
550 auto stats = tracer.get_export_stats();
551 EXPECT_EQ(stats["exported_spans"], 10);
552 EXPECT_EQ(stats["failed_exports"], 0);
553 EXPECT_EQ(stats["dropped_spans"], 0);
554}
555
556TEST_F(ExporterIntegrationTest, QueueSizeLimitEnforced) {
557 tracer.set_exporter(mock_exporter);
558 mock_exporter->should_fail = true; // Make exports fail to fill queue
559
560 trace_export_settings settings;
561 settings.batch_size = 100; // Large batch size
562 settings.max_queue_size = 5; // Small queue
563 settings.export_on_finish = false; // Disable auto-export
564 tracer.configure_export(settings);
565
566 // Create more spans than queue can hold
567 for (int i = 0; i < 10; ++i) {
568 auto span_result = tracer.start_span("operation_" + std::to_string(i));
569 ASSERT_TRUE(span_result.is_ok());
570 tracer.finish_span(span_result.value());
571 }
572
573 // Stats should show dropped spans
574 auto stats = tracer.get_export_stats();
575 EXPECT_GT(stats["dropped_spans"], 0);
576 EXPECT_LE(stats["pending_spans"], 5);
577}
578
579TEST_F(ExporterIntegrationTest, ExportedSpansContainCorrectData) {
580 tracer.set_exporter(mock_exporter);
581
582 trace_export_settings settings;
583 settings.batch_size = 1; // Export immediately
584 tracer.configure_export(settings);
585
586 auto span_result = tracer.start_span("test_operation", "test_service");
587 ASSERT_TRUE(span_result.is_ok());
588 auto span = span_result.value();
589 span->tags["custom_tag"] = "custom_value";
590
591 tracer.finish_span(span);
592
593 ASSERT_EQ(mock_exporter->exported_spans.size(), 1);
594 const auto& exported = mock_exporter->exported_spans[0];
595
596 EXPECT_EQ(exported.operation_name, "test_operation");
597 EXPECT_EQ(exported.service_name, "test_service");
598 EXPECT_EQ(exported.trace_id, span->trace_id);
599 EXPECT_EQ(exported.span_id, span->span_id);
600 auto tag_it = exported.tags.find("custom_tag");
601 ASSERT_NE(tag_it, exported.tags.end());
602 EXPECT_EQ(tag_it->second, "custom_value");
603 EXPECT_TRUE(exported.is_finished());
604}
std::shared_ptr< MockTraceExporter > mock_exporter
kcenon::common::VoidResult export_spans(const std::vector< trace_span > &spans) override
Export a batch of spans.
std::atomic< std::size_t > export_count
std::atomic< std::size_t > flush_count
kcenon::common::VoidResult shutdown() override
Shutdown the exporter.
kcenon::common::VoidResult flush() override
Flush any pending spans.
std::atomic< std::size_t > shutdown_count
std::vector< trace_span > exported_spans
std::unordered_map< std::string, std::size_t > get_stats() const override
Get exporter statistics.
Distributed tracer for managing spans and traces.
std::shared_ptr< trace_span > get_current_span() const
Get current active span for this thread.
void set_exporter(std::shared_ptr< trace_exporter_interface > exporter)
Set the trace exporter for span export.
Scoped span for RAII-style span management.
Abstract interface for trace exporters.
Distributed tracing implementation for monitoring system.
#define TRACE_CHILD_SPAN(parent, operation_name)
#define TRACE_SPAN(operation_name)
Helper macro for creating a scoped span.
distributed_tracer & global_tracer()
Global tracer instance.
Extended error information with context.
Trace context for propagation across service boundaries.
std::string to_w3c_traceparent() const
Serialize to W3C Trace Context format.
std::unordered_map< std::string, std::string > baggage
static common::Result< trace_context > from_w3c_traceparent(const std::string &header)
Parse from W3C Trace Context format.
Configuration settings for trace export behavior.
std::size_t max_queue_size
Maximum spans in queue before dropping.
bool export_on_finish
Export when batch is full.
std::size_t batch_size
Number of spans to batch before export.
Trace span representing a unit of work in distributed tracing.
std::unordered_map< std::string, std::string > tags
bool is_finished() const
Check if span has finished.
TEST_F(DistributedTracingTest, CreateRootSpan)
Trace data exporters for various distributed tracing systems.