Skip to content
Merged
3 changes: 3 additions & 0 deletions api/envoy/config/trace/v2/trace.proto
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ message OpenCensusConfig {

// "X-Cloud-Trace-Context:" header.
CLOUD_TRACE_CONTEXT = 3;

// X-B3-* headers.
B3 = 4;
}

// List of incoming trace context headers we will accept. First one found
Expand Down
4 changes: 4 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,10 @@ def _io_opencensus_cpp():
name = "opencensus_trace",
actual = "@io_opencensus_cpp//opencensus/trace",
)
native.bind(
name = "opencensus_trace_b3",
actual = "@io_opencensus_cpp//opencensus/trace:b3",
)
native.bind(
name = "opencensus_trace_cloud_trace_context",
actual = "@io_opencensus_cpp//opencensus/trace:cloud_trace_context",
Expand Down
1 change: 1 addition & 0 deletions source/extensions/tracers/opencensus/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ envoy_cc_library(
copts = ["-Wno-unused-parameter"],
external_deps = [
"opencensus_trace",
"opencensus_trace_b3",
"opencensus_trace_cloud_trace_context",
"opencensus_trace_grpc_trace_bin",
"opencensus_trace_trace_context",
Expand Down
55 changes: 50 additions & 5 deletions source/extensions/tracers/opencensus/opencensus_tracer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "opencensus/exporters/trace/stackdriver/stackdriver_exporter.h"
#include "opencensus/exporters/trace/stdout/stdout_exporter.h"
#include "opencensus/exporters/trace/zipkin/zipkin_exporter.h"
#include "opencensus/trace/propagation/b3.h"
#include "opencensus/trace/propagation/cloud_trace_context.h"
#include "opencensus/trace/propagation/grpc_trace_bin.h"
#include "opencensus/trace/propagation/trace_context.h"
Expand All @@ -32,6 +33,10 @@ class ConstantValues {
const Http::LowerCaseString TRACEPARENT{"traceparent"};
const Http::LowerCaseString GRPC_TRACE_BIN{"grpc-trace-bin"};
const Http::LowerCaseString X_CLOUD_TRACE_CONTEXT{"x-cloud-trace-context"};
const Http::LowerCaseString X_B3_TRACEID{"x-b3-traceid"};
const Http::LowerCaseString X_B3_SPANID{"x-b3-spanid"};
const Http::LowerCaseString X_B3_SAMPLED{"x-b3-sampled"};
const Http::LowerCaseString X_B3_FLAGS{"x-b3-flags"};
};

using Constants = ConstSingleton<ConstantValues>;
Expand Down Expand Up @@ -101,6 +106,35 @@ startSpanHelper(const std::string& name, bool traced, const Http::HeaderMap& req
}
break;
}

case OpenCensusConfig::B3: {
absl::string_view b3_trace_id;
absl::string_view b3_span_id;
absl::string_view b3_sampled;
absl::string_view b3_flags;
const Http::HeaderEntry* h_b3_trace_id = request_headers.get(Constants::get().X_B3_TRACEID);
if (h_b3_trace_id != nullptr) {
b3_trace_id = h_b3_trace_id->value().getStringView();
}
const Http::HeaderEntry* h_b3_span_id = request_headers.get(Constants::get().X_B3_SPANID);
if (h_b3_span_id != nullptr) {
b3_span_id = h_b3_span_id->value().getStringView();
}
const Http::HeaderEntry* h_b3_sampled = request_headers.get(Constants::get().X_B3_SAMPLED);
if (h_b3_sampled != nullptr) {
b3_sampled = h_b3_sampled->value().getStringView();
}
const Http::HeaderEntry* h_b3_flags = request_headers.get(Constants::get().X_B3_FLAGS);
if (h_b3_flags != nullptr) {
b3_flags = h_b3_flags->value().getStringView();
}
if (h_b3_trace_id != nullptr && h_b3_span_id != nullptr) {
found = true;
parent_ctx = ::opencensus::trace::propagation::FromB3Headers(b3_trace_id, b3_span_id,
b3_sampled, b3_flags);
}
break;
}
}
// First header found wins.
if (found) {
Expand Down Expand Up @@ -153,16 +187,16 @@ void Span::finishSpan() { span_.End(); }

void Span::injectContext(Http::HeaderMap& request_headers) {
using OpenCensusConfig = envoy::config::trace::v2::OpenCensusConfig;
const auto& ctx = span_.context();
for (const auto& outgoing : oc_config_.outgoing_trace_context()) {
switch (outgoing) {
case OpenCensusConfig::TRACE_CONTEXT:
request_headers.setReferenceKey(
Constants::get().TRACEPARENT,
::opencensus::trace::propagation::ToTraceParentHeader(span_.context()));
request_headers.setReferenceKey(Constants::get().TRACEPARENT,
::opencensus::trace::propagation::ToTraceParentHeader(ctx));
break;

case OpenCensusConfig::GRPC_TRACE_BIN: {
std::string val = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(span_.context());
std::string val = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(ctx);
val = Base64::encode(val.data(), val.size(), /*add_padding=*/false);
request_headers.setReferenceKey(Constants::get().GRPC_TRACE_BIN, val);
break;
Expand All @@ -171,7 +205,18 @@ void Span::injectContext(Http::HeaderMap& request_headers) {
case OpenCensusConfig::CLOUD_TRACE_CONTEXT:
request_headers.setReferenceKey(
Constants::get().X_CLOUD_TRACE_CONTEXT,
::opencensus::trace::propagation::ToCloudTraceContextHeader(span_.context()));
::opencensus::trace::propagation::ToCloudTraceContextHeader(ctx));
break;

case OpenCensusConfig::B3:
request_headers.setReferenceKey(Constants::get().X_B3_TRACEID,
::opencensus::trace::propagation::ToB3TraceIdHeader(ctx));
request_headers.setReferenceKey(Constants::get().X_B3_SPANID,
::opencensus::trace::propagation::ToB3SpanIdHeader(ctx));
request_headers.setReferenceKey(Constants::get().X_B3_SAMPLED,
::opencensus::trace::propagation::ToB3SampledHeader(ctx));
// OpenCensus's trace context propagation doesn't produce the
// "X-B3-Flags:" header.
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions test/extensions/tracers/opencensus/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) {
stackdriver_project_id: test_project_id
zipkin_exporter_enabled: true
zipkin_url: http://127.0.0.1:9411/api/v2/spans
incoming_trace_context: b3
incoming_trace_context: trace_context
incoming_trace_context: grpc_trace_bin
incoming_trace_context: cloud_trace_context
Expand Down
174 changes: 106 additions & 68 deletions test/extensions/tracers/opencensus/tracer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "gtest/gtest.h"
#include "opencensus/trace/exporter/span_data.h"
#include "opencensus/trace/exporter/span_exporter.h"
#include "opencensus/trace/propagation/b3.h"
#include "opencensus/trace/propagation/cloud_trace_context.h"
#include "opencensus/trace/propagation/grpc_trace_bin.h"
#include "opencensus/trace/propagation/trace_context.h"
Expand Down Expand Up @@ -161,78 +162,115 @@ TEST(OpenCensusTracerTest, Span) {
}
}

// Test that trace context propagation works.
TEST(OpenCensusTracerTest, PropagateTraceContext) {
registerSpanCatcher();
// The test calls the helper with each kind of incoming context in turn.
auto helper = [](const std::string& header, const std::string& value) {
OpenCensusConfig oc_config;
NiceMock<LocalInfo::MockLocalInfo> local_info;
oc_config.add_incoming_trace_context(OpenCensusConfig::NONE);
oc_config.add_incoming_trace_context(OpenCensusConfig::TRACE_CONTEXT);
oc_config.add_incoming_trace_context(OpenCensusConfig::GRPC_TRACE_BIN);
oc_config.add_incoming_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT);
oc_config.add_outgoing_trace_context(OpenCensusConfig::NONE);
oc_config.add_outgoing_trace_context(OpenCensusConfig::TRACE_CONTEXT);
oc_config.add_outgoing_trace_context(OpenCensusConfig::GRPC_TRACE_BIN);
oc_config.add_outgoing_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT);
std::unique_ptr<Tracing::Driver> driver(new OpenCensus::Driver(oc_config, local_info));
NiceMock<Tracing::MockConfig> config;
Http::TestHeaderMapImpl request_headers{
{":path", "/"},
{":method", "GET"},
{"x-request-id", "foo"},
{header, value},
};
const std::string operation_name{"my_operation_2"};
SystemTime start_time;
Http::TestHeaderMapImpl injected_headers;
{
Tracing::SpanPtr span = driver->startSpan(config, request_headers, operation_name, start_time,
{Tracing::Reason::Sampling, false});
span->injectContext(injected_headers);
span->finishSpan();
}
namespace {

// Retrieve SpanData from the OpenCensus trace exporter.
std::vector<SpanData> spans = getSpanCatcher()->catchSpans();
ASSERT_EQ(1, spans.size());
const auto& sd = spans[0];
ENVOY_LOG_MISC(debug, "{}", sd.DebugString());
using testing::PrintToString;

// Check contents.
EXPECT_TRUE(sd.has_remote_parent());
EXPECT_EQ("6162636465666768", sd.parent_span_id().ToHex());
EXPECT_EQ("404142434445464748494a4b4c4d4e4f", sd.context().trace_id().ToHex());
EXPECT_TRUE(sd.context().trace_options().IsSampled())
<< "parent was sampled, child should be also";

// Check injectContext.
using Envoy::Http::LowerCaseString;
{
auto val = injected_headers.get(LowerCaseString("traceparent"));
ASSERT_NE(nullptr, val);
EXPECT_EQ(::opencensus::trace::propagation::ToTraceParentHeader(sd.context()),
val->value().getStringView());
}
{
auto val = injected_headers.get(LowerCaseString("grpc-trace-bin"));
ASSERT_NE(nullptr, val);
std::string expected = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(sd.context());
expected = Base64::encode(expected.data(), expected.size(), /*add_padding=*/false);
EXPECT_EQ(expected, val->value().getStringView());
}
{
auto val = injected_headers.get(LowerCaseString("x-cloud-trace-context"));
ASSERT_NE(nullptr, val);
EXPECT_EQ(::opencensus::trace::propagation::ToCloudTraceContextHeader(sd.context()),
val->value().getStringView());
}
MATCHER_P2(ContainHeader, header, expected_value,
"contains the header " + PrintToString(header) + " with value " +
PrintToString(expected_value)) {
const auto found_value = arg.get(Http::LowerCaseString(header));
if (found_value == nullptr) {
return false;
}
return found_value->value().getStringView() == expected_value;
}

// Given incoming headers, test that trace context propagation works and generates all the expected
// outgoing headers.
void testIncomingHeaders(
const std::initializer_list<std::pair<const char*, const char*>>& headers) {
registerSpanCatcher();
OpenCensusConfig oc_config;
NiceMock<LocalInfo::MockLocalInfo> local_info;
oc_config.add_incoming_trace_context(OpenCensusConfig::NONE);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably this could be set in the TEST and be a bit narrower.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to leave this as-is:

  • Reduces code duplication in TESTs.
  • Acts as a smoke-test for incoming headers that aren't found.

As an alternative: how about if I make TEST pass the incoming trace context type to testIncomingHeaders but still keep this code and all the outgoing contexts (because I want to test translation) in the latter?

oc_config.add_incoming_trace_context(OpenCensusConfig::B3);
oc_config.add_incoming_trace_context(OpenCensusConfig::TRACE_CONTEXT);
oc_config.add_incoming_trace_context(OpenCensusConfig::GRPC_TRACE_BIN);
oc_config.add_incoming_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT);
oc_config.add_outgoing_trace_context(OpenCensusConfig::NONE);
oc_config.add_outgoing_trace_context(OpenCensusConfig::B3);
oc_config.add_outgoing_trace_context(OpenCensusConfig::TRACE_CONTEXT);
oc_config.add_outgoing_trace_context(OpenCensusConfig::GRPC_TRACE_BIN);
oc_config.add_outgoing_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT);
std::unique_ptr<Tracing::Driver> driver(new OpenCensus::Driver(oc_config, local_info));
NiceMock<Tracing::MockConfig> config;
Http::TestHeaderMapImpl request_headers{
{":path", "/"},
{":method", "GET"},
{"x-request-id", "foo"},
};
for (const auto& kv : headers) {
request_headers.addCopy(Http::LowerCaseString(kv.first), kv.second);
}

const std::string operation_name{"my_operation_2"};
SystemTime start_time;
Http::TestHeaderMapImpl injected_headers;
{
Tracing::SpanPtr span = driver->startSpan(config, request_headers, operation_name, start_time,
{Tracing::Reason::Sampling, false});
span->injectContext(injected_headers);
span->finishSpan();
}

// Retrieve SpanData from the OpenCensus trace exporter.
std::vector<SpanData> spans = getSpanCatcher()->catchSpans();
ASSERT_EQ(1, spans.size());
const auto& sd = spans[0];
ENVOY_LOG_MISC(debug, "{}", sd.DebugString());

// Check contents.
EXPECT_TRUE(sd.has_remote_parent());
EXPECT_EQ("6162636465666768", sd.parent_span_id().ToHex());
EXPECT_EQ("404142434445464748494a4b4c4d4e4f", sd.context().trace_id().ToHex());
EXPECT_TRUE(sd.context().trace_options().IsSampled())
<< "parent was sampled, child should be also";

// Check injectContext.
// The SpanID is unpredictable so re-serialize context to check it.
const auto& ctx = sd.context();
const auto& hdrs = injected_headers;
EXPECT_THAT(hdrs, ContainHeader("traceparent",
::opencensus::trace::propagation::ToTraceParentHeader(ctx)));
{
std::string expected = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(ctx);
expected = Base64::encode(expected.data(), expected.size(), /*add_padding=*/false);
EXPECT_THAT(hdrs, ContainHeader("grpc-trace-bin", expected));
}
EXPECT_THAT(hdrs,
ContainHeader("x-cloud-trace-context",
::opencensus::trace::propagation::ToCloudTraceContextHeader(ctx)));
EXPECT_THAT(hdrs, ContainHeader("x-b3-traceid", "404142434445464748494a4b4c4d4e4f"));
EXPECT_THAT(
hdrs, ContainHeader("x-b3-spanid", ::opencensus::trace::propagation::ToB3SpanIdHeader(ctx)));
EXPECT_THAT(hdrs, ContainHeader("x-b3-sampled", "1"));
}
} // namespace

TEST(OpenCensusTracerTest, PropagateTraceParentContext) {
testIncomingHeaders({{"traceparent", "00-404142434445464748494a4b4c4d4e4f-6162636465666768-01"}});
}

TEST(OpenCensusTracerTest, PropagateGrpcTraceBinContext) {
testIncomingHeaders({{"grpc-trace-bin", "AABAQUJDREVGR0hJSktMTU5PAWFiY2RlZmdoAgE"}});
}

TEST(OpenCensusTracerTest, PropagateCloudTraceContext) {
testIncomingHeaders(
{{"x-cloud-trace-context", "404142434445464748494a4b4c4d4e4f/7017280452245743464;o=1"}});
}

TEST(OpenCensusTracerTest, PropagateB3Context) {
testIncomingHeaders({{"x-b3-traceid", "404142434445464748494a4b4c4d4e4f"},
{"x-b3-spanid", "6162636465666768"},
{"x-b3-sampled", "1"}});
}

helper("traceparent", "00-404142434445464748494a4b4c4d4e4f-6162636465666768-01");
helper("grpc-trace-bin", "AABAQUJDREVGR0hJSktMTU5PAWFiY2RlZmdoAgE");
helper("x-cloud-trace-context", "404142434445464748494a4b4c4d4e4f/7017280452245743464;o=1");
TEST(OpenCensusTracerTest, PropagateB3ContextWithDebugFlag) {
testIncomingHeaders({{"x-b3-traceid", "404142434445464748494a4b4c4d4e4f"},
{"x-b3-spanid", "6162636465666768"},
{"x-b3-flags", "1"}}); // Debug flag causes sampling.
}

namespace {
Expand Down