6#include <gtest/gtest.h>
27 std::vector<trace_span> spans;
35 root_span.
start_time = std::chrono::system_clock::now();
37 root_span.
tags[
"http.method"] =
"GET";
38 root_span.
tags[
"http.url"] =
"/api/users";
39 root_span.
tags[
"span.kind"] =
"server";
40 spans.push_back(root_span);
51 child_span.
tags[
"db.statement"] =
"SELECT * FROM users WHERE id = ?";
52 child_span.
tags[
"db.type"] =
"postgresql";
53 child_span.
tags[
"span.kind"] =
"client";
54 spans.push_back(child_span);
66 valid_config.
endpoint =
"http://jaeger:14268/api/traces";
67 valid_config.
format = trace_export_format::jaeger_thrift;
68 valid_config.
timeout = std::chrono::milliseconds(5000);
72 auto validation = valid_config.
validate();
73 EXPECT_TRUE(validation.is_ok());
78 auto endpoint_validation = invalid_endpoint.
validate();
79 EXPECT_TRUE(endpoint_validation.is_err());
80 EXPECT_EQ(endpoint_validation.error().code,
static_cast<int>(monitoring_error_code::invalid_configuration));
84 invalid_timeout.
endpoint =
"http://test";
85 invalid_timeout.
timeout = std::chrono::milliseconds(0);
86 auto timeout_validation = invalid_timeout.
validate();
87 EXPECT_TRUE(timeout_validation.is_err());
91 invalid_batch.
endpoint =
"http://test";
93 auto batch_validation = invalid_batch.
validate();
94 EXPECT_TRUE(batch_validation.is_err());
98 invalid_queue.
endpoint =
"http://test";
101 auto queue_validation = invalid_queue.
validate();
102 EXPECT_TRUE(queue_validation.is_err());
107 config.
endpoint =
"http://jaeger:14268/api/traces";
108 config.
format = trace_export_format::jaeger_thrift;
113 const auto& span = test_spans_[0];
116 EXPECT_EQ(jaeger_span.trace_id, span.trace_id);
117 EXPECT_EQ(jaeger_span.span_id, span.span_id);
118 EXPECT_EQ(jaeger_span.operation_name, span.operation_name);
119 EXPECT_EQ(jaeger_span.service_name,
"test_service");
122 bool found_http_method =
false;
123 bool found_http_url =
false;
124 for (
const auto& [key, value] : jaeger_span.tags) {
125 if (key ==
"http.method" && value ==
"GET") {
126 found_http_method =
true;
128 if (key ==
"http.url" && value ==
"/api/users") {
129 found_http_url =
true;
132 EXPECT_TRUE(found_http_method);
133 EXPECT_TRUE(found_http_url);
136 bool found_service_name =
false;
137 for (
const auto& [key, value] : jaeger_span.process_tags) {
138 if (key ==
"service.name" && value ==
"test_service") {
139 found_service_name =
true;
142 EXPECT_TRUE(found_service_name);
147 config.
endpoint =
"http://jaeger:14268/api/traces";
148 config.
format = trace_export_format::jaeger_thrift;
153 auto export_result = exporter.
export_spans(test_spans_);
154 EXPECT_TRUE(export_result.is_err());
158 EXPECT_EQ(stats[
"failed_exports"], 1);
161 auto flush_result = exporter.
flush();
162 EXPECT_TRUE(flush_result.is_ok());
164 auto shutdown_result = exporter.
shutdown();
165 EXPECT_TRUE(shutdown_result.is_ok());
170 config.
endpoint =
"http://zipkin:9411/api/v2/spans";
171 config.
format = trace_export_format::zipkin_json;
176 const auto& span = test_spans_[0];
179 EXPECT_EQ(zipkin_span.trace_id, span.trace_id);
180 EXPECT_EQ(zipkin_span.span_id, span.span_id);
181 EXPECT_EQ(zipkin_span.name, span.operation_name);
182 EXPECT_EQ(zipkin_span.local_endpoint_service_name,
"test_service");
183 EXPECT_EQ(zipkin_span.kind,
"server");
186 EXPECT_EQ(zipkin_span.tags.count(
"span.kind"), 0);
187 EXPECT_EQ(zipkin_span.tags.count(
"http.method"), 1);
188 EXPECT_EQ(zipkin_span.tags[
"http.method"],
"GET");
193 config.
endpoint =
"http://zipkin:9411/api/v2/spans";
194 config.
format = trace_export_format::zipkin_json;
199 auto export_result = exporter.
export_spans(test_spans_);
200 EXPECT_TRUE(export_result.is_err());
204 EXPECT_EQ(stats[
"failed_exports"], 1);
207 auto flush_result = exporter.
flush();
208 EXPECT_TRUE(flush_result.is_ok());
210 auto shutdown_result = exporter.
shutdown();
211 EXPECT_TRUE(shutdown_result.is_ok());
216 config.
endpoint =
"http://otlp-collector:4317";
217 config.
format = trace_export_format::otlp_grpc;
222 auto export_result = exporter.
export_spans(test_spans_);
223 EXPECT_TRUE(export_result.is_ok());
227 EXPECT_EQ(stats[
"exported_spans"], test_spans_.size());
228 EXPECT_EQ(stats[
"failed_exports"], 0);
231 auto flush_result = exporter.
flush();
232 EXPECT_TRUE(flush_result.is_ok());
234 auto shutdown_result = exporter.
shutdown();
235 EXPECT_TRUE(shutdown_result.is_ok());
241 jaeger_config.
endpoint =
"http://jaeger:14268";
242 jaeger_config.
format = trace_export_format::jaeger_grpc;
249 zipkin_config.
endpoint =
"http://zipkin:9411";
250 zipkin_config.
format = trace_export_format::zipkin_json;
257 otlp_config.
endpoint =
"http://otlp-collector:4317";
258 otlp_config.
format = trace_export_format::otlp_grpc;
266 EXPECT_EQ(jaeger_formats.size(), 2);
267 EXPECT_TRUE(std::find(jaeger_formats.begin(), jaeger_formats.end(),
268 trace_export_format::jaeger_thrift) != jaeger_formats.end());
269 EXPECT_TRUE(std::find(jaeger_formats.begin(), jaeger_formats.end(),
270 trace_export_format::jaeger_grpc) != jaeger_formats.end());
273 EXPECT_EQ(zipkin_formats.size(), 2);
274 EXPECT_TRUE(std::find(zipkin_formats.begin(), zipkin_formats.end(),
275 trace_export_format::zipkin_json) != zipkin_formats.end());
276 EXPECT_TRUE(std::find(zipkin_formats.begin(), zipkin_formats.end(),
277 trace_export_format::zipkin_protobuf) != zipkin_formats.end());
280 EXPECT_EQ(otlp_formats.size(), 3);
281 EXPECT_TRUE(std::find(otlp_formats.begin(), otlp_formats.end(),
282 trace_export_format::otlp_grpc) != otlp_formats.end());
285 EXPECT_EQ(unknown_formats.size(), 0);
291 trace_export_format::jaeger_thrift);
296 trace_export_format::zipkin_protobuf);
301 trace_export_format::otlp_http_json);
307 invalid_jaeger_config.
endpoint =
"http://jaeger:14268";
308 invalid_jaeger_config.
format = trace_export_format::zipkin_json;
311 auto jaeger_result = jaeger_exp.
export_spans(test_spans_);
312 EXPECT_TRUE(jaeger_result.is_err());
313 EXPECT_EQ(jaeger_result.error().code,
static_cast<int>(monitoring_error_code::invalid_configuration));
316 invalid_zipkin_config.
endpoint =
"http://zipkin:9411";
317 invalid_zipkin_config.
format = trace_export_format::jaeger_grpc;
320 auto zipkin_result = zipkin_exp.
export_spans(test_spans_);
321 EXPECT_TRUE(zipkin_result.is_err());
322 EXPECT_EQ(zipkin_result.error().code,
static_cast<int>(monitoring_error_code::invalid_configuration));
325 invalid_otlp_config.
endpoint =
"http://otlp:4317";
326 invalid_otlp_config.
format = trace_export_format::jaeger_thrift;
330 EXPECT_TRUE(otlp_result.is_err());
331 EXPECT_EQ(otlp_result.error().code,
static_cast<int>(monitoring_error_code::invalid_configuration));
335 std::vector<trace_span> empty_spans;
338 config.
endpoint =
"http://test:1234";
339 config.
format = trace_export_format::jaeger_grpc;
346 EXPECT_EQ(stats[
"exported_spans"], 0);
351 std::vector<trace_span> large_batch;
352 for (
int i = 0; i < 1000; ++i) {
354 span.
trace_id =
"trace" + std::to_string(i);
355 span.
span_id =
"span" + std::to_string(i);
358 span.
start_time = std::chrono::system_clock::now();
360 large_batch.push_back(span);
364 config.
endpoint =
"http://test:1234";
365 config.
format = trace_export_format::otlp_grpc;
370 EXPECT_TRUE(result.is_ok());
373 EXPECT_EQ(stats[
"exported_spans"],
static_cast<std::size_t
>(1000));
383TEST(ProtobufWireTest, VarintRoundTrip) {
385 const std::uint64_t values[] = {
386 0, 1, 127, 128, 255, 16383, 16384,
387 1000000000ULL, (std::uint64_t{1} << 63)
389 for (std::uint64_t v : values) {
390 std::vector<std::uint8_t> buf;
391 encode_varint(buf, v);
392 reader r(buf.data(), buf.size());
394 ASSERT_TRUE(got.has_value()) <<
"failed for value " << v;
396 EXPECT_TRUE(r.
eof());
400TEST(ProtobufWireTest, HexBytesRoundTrip) {
402 const std::string hex =
"0123456789abcdef";
403 auto bytes = hex_to_bytes(hex);
404 EXPECT_EQ(bytes.size(), 8u);
405 EXPECT_EQ(bytes_to_hex(bytes), hex);
408 EXPECT_TRUE(hex_to_bytes(
"xyz").empty());
411 auto padded = left_pad(bytes, 16);
412 EXPECT_EQ(padded.size(), 16u);
413 for (std::size_t i = 0; i < 8; ++i) EXPECT_EQ(padded[i], 0u);
414 for (std::size_t i = 0; i < 8; ++i) EXPECT_EQ(padded[8 + i], bytes[i]);
419 config.
endpoint =
"http://jaeger:14268";
420 config.
format = trace_export_format::jaeger_grpc;
424 auto jaeger_span = exporter.
convert_span(test_spans_[0]);
426 ASSERT_FALSE(wire.empty());
430 wire.data(), wire.size(), decoded));
433 EXPECT_EQ(decoded.
trace_id.size(), 16u);
434 EXPECT_EQ(decoded.
span_id.size(), 8u);
438 bool found_method =
false, found_url =
false, found_kind =
false;
439 for (
const auto& kv : decoded.
tags) {
440 if (kv.key ==
"http.method") {
441 EXPECT_EQ(kv.v_str,
"GET");
442 EXPECT_EQ(
static_cast<int>(kv.v_type), 0);
444 }
else if (kv.key ==
"http.url") {
445 EXPECT_EQ(kv.v_str,
"/api/users");
447 }
else if (kv.key ==
"span.kind") {
448 EXPECT_EQ(kv.v_str,
"server");
452 EXPECT_TRUE(found_method);
453 EXPECT_TRUE(found_url);
454 EXPECT_TRUE(found_kind);
462 config.
endpoint =
"http://jaeger:14268";
463 config.
format = trace_export_format::jaeger_grpc;
467 auto jaeger_span = exporter.
convert_span(test_spans_[1]);
472 wire.data(), wire.size(), decoded));
476 EXPECT_EQ(decoded.
references[0].span_id.size(), 8u);
481 config.
endpoint =
"http://jaeger:14268";
482 config.
format = trace_export_format::jaeger_grpc;
486 std::vector<jaeger_span_data> jaeger_spans;
487 for (
const auto& s : test_spans_) {
491 ASSERT_FALSE(wire.empty());
495 wire.data(), wire.size(), decoded));
496 EXPECT_EQ(decoded.
spans.size(), test_spans_.size());
503 config.
endpoint =
"http://zipkin:9411";
504 config.
format = trace_export_format::zipkin_protobuf;
508 auto zipkin_span = exporter.
convert_span(test_spans_[0]);
510 ASSERT_FALSE(wire.empty());
514 wire.data(), wire.size(), decoded));
517 EXPECT_TRUE(decoded.
trace_id.size() == 8u || decoded.
trace_id.size() == 16u);
518 EXPECT_EQ(decoded.
id.size(), 8u);
519 EXPECT_EQ(decoded.
name,
"http_request");
524 bool found_method =
false, found_url =
false, found_kind =
false;
525 for (
const auto& [k, v] : decoded.
tags) {
526 if (k ==
"http.method") { EXPECT_EQ(v,
"GET"); found_method =
true; }
527 if (k ==
"http.url") { EXPECT_EQ(v,
"/api/users"); found_url =
true; }
528 if (k ==
"span.kind") { found_kind =
true; }
530 EXPECT_TRUE(found_method);
531 EXPECT_TRUE(found_url);
532 EXPECT_FALSE(found_kind);
537 config.
endpoint =
"http://zipkin:9411";
538 config.
format = trace_export_format::zipkin_protobuf;
541 auto zipkin_span = exporter.
convert_span(test_spans_[1]);
546 wire.data(), wire.size(), decoded));
553 config.
endpoint =
"http://zipkin:9411";
554 config.
format = trace_export_format::zipkin_protobuf;
558 std::vector<zipkin_span_data> zipkin_spans;
559 for (
const auto& s : test_spans_) {
563 ASSERT_FALSE(wire.empty());
567 wire.data(), wire.size(), decoded));
568 EXPECT_EQ(decoded.
spans.size(), test_spans_.size());
571TEST(ZipkinProtoTest, SpanKindParsing) {
574 EXPECT_EQ(parse_kind(
"CLIENT"), span_kind::client);
575 EXPECT_EQ(parse_kind(
"server"), span_kind::server);
576 EXPECT_EQ(parse_kind(
"Producer"), span_kind::producer);
577 EXPECT_EQ(parse_kind(
"CONSUMER"), span_kind::consumer);
578 EXPECT_EQ(parse_kind(
""), span_kind::unspecified);
579 EXPECT_EQ(parse_kind(
"INTERNAL"), span_kind::unspecified);
598 valid_config.
endpoint =
"localhost:4317";
599 valid_config.
timeout = std::chrono::milliseconds(5000);
602 auto validation = valid_config.
validate();
603 EXPECT_TRUE(validation.is_ok());
608 auto endpoint_validation = invalid_endpoint.
validate();
609 EXPECT_TRUE(endpoint_validation.is_err());
613 invalid_timeout.
endpoint =
"localhost:4317";
614 invalid_timeout.
timeout = std::chrono::milliseconds(0);
615 auto timeout_validation = invalid_timeout.
validate();
616 EXPECT_TRUE(timeout_validation.is_err());
620 invalid_batch.
endpoint =
"localhost:4317";
622 auto batch_validation = invalid_batch.
validate();
623 EXPECT_TRUE(batch_validation.is_err());
632 {{
"environment",
"test"}});
635 EXPECT_GT(payload.size(), 0u);
639 EXPECT_EQ(payload[0], 0x0A);
643 std::vector<trace_span> empty_spans;
652 EXPECT_GT(payload.size(), 0u);
663 auto* stub_ptr = stub_transport.get();
668 auto start_result = exporter.
start();
669 EXPECT_TRUE(start_result.is_ok());
673 auto export_result = exporter.
export_spans(test_spans_);
674 EXPECT_TRUE(export_result.is_ok());
678 EXPECT_EQ(stats[
"exported_spans"], test_spans_.size());
679 EXPECT_EQ(stats[
"dropped_spans"], 0u);
680 EXPECT_EQ(stats[
"failed_exports"], 0u);
683 auto transport_stats = stub_ptr->get_statistics();
684 EXPECT_EQ(transport_stats.requests_sent, 1u);
685 EXPECT_GT(transport_stats.bytes_sent, 0u);
688 auto shutdown_result = exporter.
shutdown();
689 EXPECT_TRUE(shutdown_result.is_ok());
699 stub_transport->set_simulate_success(
false);
704 auto start_result = exporter.
start();
705 EXPECT_TRUE(start_result.is_err());
718 stub_transport->set_response_handler([&call_count](
const grpc_request&) {
721 if (call_count < 3) {
735 auto start_result = exporter.
start();
736 EXPECT_TRUE(start_result.is_ok());
738 auto export_result = exporter.
export_spans(test_spans_);
739 EXPECT_TRUE(export_result.is_ok());
742 EXPECT_EQ(call_count, 3);
745 EXPECT_EQ(stats[
"retries"], 2u);
746 EXPECT_EQ(stats[
"exported_spans"], test_spans_.size());
755 stub_transport->set_response_handler([](
const grpc_request&) {
757 std::this_thread::sleep_for(std::chrono::microseconds(100));
765 auto start_result = exporter.
start();
766 EXPECT_TRUE(start_result.is_ok());
769 for (
int i = 0; i < 3; ++i) {
771 EXPECT_TRUE(result.is_ok());
775 EXPECT_EQ(detailed_stats.spans_exported, test_spans_.size() * 3);
776 EXPECT_EQ(detailed_stats.spans_dropped, 0u);
777 EXPECT_EQ(detailed_stats.export_failures, 0u);
778 EXPECT_GT(detailed_stats.total_export_time.count(), 0);
784 EXPECT_TRUE(exporter1 !=
nullptr);
785 EXPECT_EQ(exporter1->config().endpoint,
"localhost:4317");
789 EXPECT_TRUE(exporter2 !=
nullptr);
790 EXPECT_EQ(exporter2->config().endpoint,
"collector:4317");
794 config.
endpoint =
"otlp.example.com:443";
799 EXPECT_TRUE(exporter3 !=
nullptr);
800 EXPECT_EQ(exporter3->config().endpoint,
"otlp.example.com:443");
801 EXPECT_TRUE(exporter3->config().use_tls);
802 EXPECT_EQ(exporter3->config().service_name,
"my_service");
812 auto start_result = exporter.
start();
813 EXPECT_TRUE(start_result.is_ok());
816 std::vector<trace_span> empty_spans;
817 auto export_result = exporter.
export_spans(empty_spans);
818 EXPECT_TRUE(export_result.is_ok());
821 EXPECT_EQ(stats[
"exported_spans"], 0u);
otel_resource otel_resource_
std::vector< trace_span > create_test_spans()
std::vector< trace_span > test_spans_
Jaeger trace exporter implementation.
std::unordered_map< std::string, std::size_t > get_stats() const override
Get exporter statistics.
common::VoidResult flush() override
Flush any pending spans.
jaeger_span_data convert_span(const trace_span &span) const
Convert internal span to Jaeger format.
common::VoidResult shutdown() override
Shutdown the exporter.
common::VoidResult export_spans(const std::vector< trace_span > &spans) override
Export a batch of spans.
OpenTelemetry Protocol (OTLP) trace exporter implementation.
common::VoidResult shutdown() override
Shutdown the exporter.
common::VoidResult flush() override
Flush any pending spans.
common::VoidResult export_spans(const std::vector< trace_span > &spans) override
Export a batch of spans.
std::unordered_map< std::string, std::size_t > get_stats() const override
Get exporter statistics.
OTLP gRPC trace exporter.
common::VoidResult shutdown() override
Shutdown the exporter.
otlp_exporter_stats get_detailed_stats() const
Get detailed statistics.
common::VoidResult start()
Start the exporter.
bool is_running() const
Check if exporter is running.
std::unordered_map< std::string, std::size_t > get_stats() const override
Get exporter statistics.
common::VoidResult export_spans(const std::vector< trace_span > &spans) override
Export a batch of spans.
static std::vector< uint8_t > convert_to_otlp(const std::vector< trace_span > &spans, const std::string &service_name, const std::string &service_version, const std::unordered_map< std::string, std::string > &resource_attributes)
Convert spans to OTLP protobuf format.
Minimal protobuf wire reader used for round-trip tests.
std::optional< std::uint64_t > read_varint()
static std::vector< trace_export_format > get_supported_formats(const std::string &backend)
Get supported formats for a specific backend.
static std::unique_ptr< trace_exporter_interface > create_exporter(const trace_export_config &config, const otel_resource &resource=create_service_resource("monitoring_system", "2.0.0"))
Create a trace exporter based on format.
Zipkin trace exporter implementation.
common::VoidResult export_spans(const std::vector< trace_span > &spans) override
Export a batch of spans.
std::unordered_map< std::string, std::size_t > get_stats() const override
Get exporter statistics.
common::VoidResult shutdown() override
Shutdown the exporter.
common::VoidResult flush() override
Flush any pending spans.
zipkin_span_data convert_span(const trace_span &span) const
Convert internal span to Zipkin format.
Serialization/deserialization of Jaeger api_v2 model.proto messages.
bool decode_span(const std::uint8_t *data, std::size_t size, span &out)
bool decode_batch(const std::uint8_t *data, std::size_t size, batch &out)
bool decode_span(const std::uint8_t *data, std::size_t size, span &out)
span_kind parse_kind(const std::string &value)
Convert a textual Zipkin kind (e.g. "CLIENT") to its enum value.
bool decode_list_of_spans(const std::uint8_t *data, std::size_t size, list_of_spans &out)
std::unique_ptr< stub_grpc_transport > create_stub_grpc_transport()
Create stub gRPC transport for testing.
std::vector< uint8_t > encode_zipkin_list_of_spans(const std::vector< zipkin_span_data > &spans)
Encode a batch of Zipkin spans into a ListOfSpans protobuf message suitable for POST /api/v2/spans wi...
std::unique_ptr< zipkin_exporter > create_zipkin_exporter(const std::string &endpoint, trace_export_format format=trace_export_format::zipkin_json)
Helper function to create a Zipkin exporter.
std::unique_ptr< otlp_grpc_exporter > create_otlp_grpc_exporter(const std::string &endpoint="localhost:4317")
Create OTLP gRPC exporter with default configuration.
otel_resource create_service_resource(const std::string &service_name, const std::string &service_version="1.0.0", const std::string &service_namespace="")
Create OpenTelemetry resource with service information.
std::unique_ptr< otlp_exporter > create_otlp_exporter(const std::string &endpoint, const otel_resource &resource, trace_export_format format=trace_export_format::otlp_grpc)
Helper function to create an OTLP exporter.
std::vector< uint8_t > encode_jaeger_batch(const std::vector< jaeger_span_data > &spans, const std::string &service_name)
Encode a batch of Jaeger spans (with shared process) into a Jaeger api_v2 Batch protobuf message.
std::unique_ptr< jaeger_exporter > create_jaeger_exporter(const std::string &endpoint, trace_export_format format=trace_export_format::jaeger_grpc)
Helper function to create a Jaeger exporter.
OpenTelemetry compatibility layer for monitoring system integration.
OTLP gRPC trace exporter implementation.
gRPC request configuration
std::string status_message
std::vector< span > spans
std::vector< key_value > tags
std::vector< span_ref > references
std::vector< std::uint8_t > trace_id
std::vector< std::uint8_t > span_id
std::string operation_name
std::vector< uint8_t > to_protobuf() const
Convert this span to a Jaeger api_v2 Span protobuf message.
OpenTelemetry resource representation.
Configuration for OTLP gRPC exporter.
std::string service_version
Service version.
std::string endpoint
OTLP receiver endpoint.
std::size_t max_retry_attempts
Maximum retry attempts.
std::string service_name
Service name.
common::VoidResult validate() const
Validate configuration.
std::size_t max_batch_size
Maximum spans per batch.
std::chrono::milliseconds initial_backoff
Initial retry backoff.
std::chrono::milliseconds timeout
Request timeout.
Configuration for trace exporters.
std::chrono::milliseconds timeout
Request timeout.
std::string endpoint
Endpoint URL.
trace_export_format format
std::size_t max_queue_size
Maximum queued spans.
std::optional< std::string > service_name
Override service name.
common::VoidResult validate() const
Validate export configuration.
std::size_t max_batch_size
Maximum spans per batch.
Trace span representing a unit of work in distributed tracing.
std::string parent_span_id
std::unordered_map< std::string, std::string > tags
std::chrono::system_clock::time_point end_time
std::string operation_name
std::chrono::system_clock::time_point start_time
std::vector< span > spans
std::vector< std::uint8_t > trace_id
std::vector< std::uint8_t > id
std::vector< std::uint8_t > parent_id
std::vector< std::pair< std::string, std::string > > tags
std::vector< uint8_t > to_protobuf() const
Convert this span to a Zipkin Span protobuf message.
TEST(ProtobufWireTest, VarintRoundTrip)
TEST_F(TraceExportersTest, TraceExportConfigValidation)
Trace data exporters for various distributed tracing systems.
Serialization/deserialization of Zipkin zipkin.proto messages.