Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Added Support for W3C traceparent/tracestate propagation #255

Merged
merged 14 commits into from
Feb 26, 2021
Merged

Added Support for W3C traceparent/tracestate propagation #255

merged 14 commits into from
Feb 26, 2021

Conversation

tobiasstadler
Copy link
Contributor

@tobiasstadler tobiasstadler commented Nov 30, 2020

Which problem is this PR solving?

The cpp client does not support injection/extracting the W3C trace parent header. I implemented support for it.

Fixes #160

@AppVeyorBot
Copy link

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

Is there support for environment variable JAEGER_PROPAGATION? Per https://www.jaegertracing.io/docs/1.21/client-features/, "w3c" lowercase value is how this propagator can be selected.

jaegerBaggageHeader,
traceContextHeaderName,
traceBaggageHeaderPrefix,
traceContextHeaderFormat == "W3C" ? Format::W3C : Format::JAEGER);
Copy link
Member

Choose a reason for hiding this comment

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

Q: why is this uppercase "W3C"? Where would the value come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Via the configuration file, e.g.

headers:
    TraceContextHeaderFormat: w3c

@@ -31,6 +31,7 @@ static constexpr auto kSamplerTypeTagKey = "sampler.type";
static constexpr auto kSamplerParamTagKey = "sampler.param";
static constexpr auto kTraceContextHeaderName = "uber-trace-id";
static constexpr auto kTracerStateHeaderName = kTraceContextHeaderName;
static constexpr auto kW3CTraceContextHeaderName = "traceparent";
Copy link
Member

Choose a reason for hiding this comment

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

I take it you're only supporting traceparent, but not tracestate. This makes it non-compliant propagator. At minimum it should pass on the tracestate unchanged, as was done in the Java client https://github.com/jaegertracing/jaeger-client-java/blob/5831d9ed7b465971d6c5211e3f56371cc083541a/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/TraceContextCodec.java

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will look into it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@tobiasstadler
Copy link
Contributor Author

Is there support for environment variable JAEGER_PROPAGATION? Per https://www.jaegertracing.io/docs/1.21/client-features/, "w3c" lowercase value is how this propagator can be selected.

I implemented support for it

@AppVeyorBot
Copy link

@tobiasstadler tobiasstadler changed the title Added Support for W3C traceparent propagation Added Support for W3C traceparent/tracestate propagation Dec 2, 2020
@AppVeyorBot
Copy link

const std::string& jaegerBaggageHeader,
const std::string& traceContextHeaderName,
const std::string& traceBaggageHeaderPrefix,
const std::string& traceStateHeaderName,
Copy link
Member

Choose a reason for hiding this comment

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

I find this rather confusing. The traceContextHeaderName setting is already somewhat dubious as it allows to change uber-trace-id header (which is a header defined by the Jaeger propagation spec) to something completely custom that is not covered by any spec (but still uses Jaeger header syntax - ¯\_(ツ)_/¯). Extending the same override capability to tracestate header, which is the official header of W3C spec, is in the same dubious category. I would rather not do that.

Note that it is a coincidence that "traceState" in traceContextHeaderName is the same term as W3C Trace Context - they are actually unrelated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Inalso find it kind of dubiose but tried to stick with the existing design. It is fine for me to „hardcore“ the name

[this, &ctx, &debugID, &baggage](const std::string& rawKey,
const std::string& value) {
[this, &ctx, &debugID, &baggage, &traceState](
const std::string& rawKey, const std::string& value) {
const auto key = normalizeKey(rawKey);
if (key == _headerKeys.traceContextHeaderName()) {
Copy link
Member

@yurishkuro yurishkuro Dec 2, 2020

Choose a reason for hiding this comment

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

could we split the default Jaeger propagation header handling from traceparent handling? I.e. do not overload overridable traceContextHeaderName() use case with W3C propagation at all.

In all other Jaeger SDKs, the default Jaeger propagator and W3C propagator are implemented as independent classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will have a look.

To be sure, if a user selects the w3c format but not jaeger format, should only the two w3c headers be supported or also e.g. jaeger-debug-id?

Copy link
Member

Choose a reason for hiding this comment

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

It's kind of up to you. Strictly speaking, the two extra headers jaeger-debug-id and jaeger-baggage are features of the default Jaeger propagator, so if you instead configure the B3 propagator they might be either supported on not, depending on the Jaeger client implementation. But I think it's a nice-to-have to support jaeger-debug-id in the W3C propagator since it provides an additional debugging capability to the users.

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

{
{
constexpr auto kConfigYAML = R"cfg(
propagation_format: w3c
Copy link
Member

Choose a reason for hiding this comment

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

Is the format of the config file documented somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because I don't know where. Should I put it in examples/config.yml.

Copy link
Member

Choose a reason for hiding this comment

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

I don't know either. Looks like it's not documented in the README, would be good to have a reference to examples/, and then include all properties in that sample config (comment them out if necessary). But this could be done via separate PR.

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

@mdouaihy mind taking a look?

HTTPHeaderPropagator* httpHeaderPropagator;
if (config.propagationFormat() == propagation::Format::W3C) {
textPropagator =
new propagation::W3CTextMapPropagator(config.headers(), metrics);
Copy link
Member

Choose a reason for hiding this comment

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

Are headers() needed for W3C? If different headers are used it ceases to be W3C format.

Copy link
Contributor Author

@tobiasstadler tobiasstadler Dec 7, 2020

Choose a reason for hiding this comment

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

They are needed for the jaeger-debug-id header, but they are not used for the trace parent and tracestate headers. These two headers are constants.

Tracer(const std::string& serviceName,
const std::shared_ptr<samplers::Sampler>& sampler,
const std::shared_ptr<reporters::Reporter>& reporter,
const std::shared_ptr<logging::Logger>& logger,
const std::shared_ptr<metrics::Metrics>& metrics,
const propagation::HeadersConfig& headersConfig,
const TextMapPropagator* textPropagator,
Copy link
Member

Choose a reason for hiding this comment

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

my C++ is quite rusty, but why is it that the other composable objects above are captured via shared_ptr and propagators via raw pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mine is rusty too, but I changed it to shared_ptr.

Comment on lines 1 to 4

/*
* Copyright (c) 2020 Uber Technologies, Inc.
*
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/*
* Copyright (c) 2020 Uber Technologies, Inc.
*
/*
* Copyright (c) 2020 The Jaeger Authors
*

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2020 Uber Technologies, Inc.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Copyright (c) 2020 Uber Technologies, Inc.
* Copyright (c) 2020 The Jaeger Authors

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2020 Uber Technologies, Inc.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Copyright (c) 2020 Uber Technologies, Inc.
* Copyright (c) 2020 The Jaeger Authors

@@ -0,0 +1,158 @@
/*
* Copyright (c) 2020 Uber Technologies, Inc.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Copyright (c) 2020 Uber Technologies, Inc.
* Copyright (c) 2020 The Jaeger Authors

@miry
Copy link

miry commented Dec 7, 2020

Hi @tobiasstadler ,

Thank you for this work. I tried to use your PR with nginx-opentracing. here is changes.

  1. I am not sure how I can set propagation through config. Pls help me
  2. I set through ENV and tried different values: w3c or W3C. During my tests I dont have any headers generated.

Can you help me share how can I debug the issue (I am new for CPP world)?

@tobiasstadler
Copy link
Contributor Author

Hi @tobiasstadler ,

Thank you for this work. I tried to use your PR with nginx-opentracing. here is changes.

  1. I am not sure how I can set propagation through config. Pls help me
  2. I set through ENV and tried different values: w3c or W3C. During my tests I dont have any headers generated.

Can you help me share how can I debug the issue (I am new for CPP world)?

Hi,
By default nginx removes all env variables from the worker process (see http://nginx.org/en/docs/ngx_core_module.html). You can either add env JAEGER_PROPAGATION; to your nginx.conf or add "propagation_format": "w3c" to your jaeger-config.json

@miry
Copy link

miry commented Dec 7, 2020

@tobiasstadler thank you. Now I see the headers. Waiting when it could be merged :)

std::ostringstream oss;
oss << "00";
oss << '-';
oss << std::setw(16) << std::setfill('0') << std::hex
Copy link

Choose a reason for hiding this comment

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

@tobiasstadler Can you help me understand why there are 0 in the first half of the trace id? Is there limitations of Jaeger?

Copy link

Choose a reason for hiding this comment

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

Nevermind, I found PR #254

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the jaeger client only generates 64bit trace ids. See #254.

Signed-off-by: Tobias Stadler <[email protected]>
@AppVeyorBot
Copy link

@miry
Copy link

miry commented Dec 15, 2020

Found in opentelemetry-go by default server expected HTTP header traceparent in downcase. Here is the related PR.

I checked W3C doc. It has next:

In order to increase interoperability across multiple protocols and encourage successful integration, by default vendors SHOULD keep the header name lowercase. The header name is a single word without any delimiters, for example, a hyphen (-).

Currently Nginx + Jaeger generates header Traceparent. Can you help me to understand where it should be fixed in nginx-opentracing side or in jaeger-cpp?

UPDATE: I found that it is Golang library capitalize all incoming HTTP headers.

@seabaylea
Copy link

@tobiasstadler thanks for your hard work! Is there anything stopping this from being merged?

@tobiasstadler
Copy link
Contributor Author

@seabaylea The PR needs to be reviewed by one of the maintainers

@seabaylea
Copy link

@miry @yurishkuro would it be possible to get this reviewed?

@miry
Copy link

miry commented Jan 27, 2021

@tobiasstadler I am not part of the core contributors.

But I am testing your changes with ingress-nginx in our clusters.
I also waiting when this PR could be merged.

@yurishkuro
Copy link
Member

@miry if you're actually using this, could you provide some feedback please, and if possible some testing results? We do not have active maintainers of this library anymore, and my C++ is very rusty.

@miry
Copy link

miry commented Jan 27, 2021

could you provide some feedback please, and if possible some testing results?

@yurishkuro Good point. We have in pipeline to run load tests and compare with original ingress-nginx.

my C++ is very rusty

I have exactly the same situation.

@tobiasstadler
Copy link
Contributor Author

I did limited testing on my local machine. real world testing is appreciated.

@@ -104,6 +104,7 @@ JAEGER_DISABLED _(not recommended)_ | Instructs the Configuration to return a no
JAEGER_AGENT_HOST | The hostname for communicating with agent via UDP
JAEGER_AGENT_PORT | The port for communicating with agent via UDP
JAEGER_ENDPOINT | The traces endpoint, in case the client should connect directly to the Collector, like http://jaeger-collector:14268/api/traces
JAEGER_PROPAGATION | The propagation format used by the tracer. Supported values are jaeger and w3c
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can align the documentation across programming languages. Can you use instead:
Comma separated list of formats to use for propagating the trace context. Defaults to the standard Jaeger format. Valid values are jaeger, and w3c

Copy link
Member

Choose a reason for hiding this comment

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

I would suggest not doing this in this PR, because supporting more than one format simultaneously is actually non-trivial, and not even clearly defined semantically, e.g.

  • do we include one or both formats in the outgoing requests?
  • what to do if incoming request has both formats? Especially if they are conflicting?

_propagationFormat = propagation::Format::W3C;
}
else {
_propagationFormat = propagation::Format::JAEGER;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should validate that propagationFormat contains the expected value (jaeger). Otherwise, either throw an exception, log or do nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yurishkuro What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

baggageRestrictions,
serviceName,
std::vector<Tag>(),
propagationFormat == "w3c" ? propagation::Format::W3C
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to validate that propagationFormat contains the expected format (jaeger).

Copy link
Contributor

Choose a reason for hiding this comment

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

otherwise, we could send TOTO and the system will accept it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should at least fall back to jaeger format if none was provided.

@yurishkuro What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

fallback on empty value is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

Comment on lines 60 to 72
propagation::Format propagationFormat;
if (strPropagationFormat == "w3c") {
propagationFormat = propagation::Format::W3C;
}
else if (strPropagationFormat == "jaeger") {
propagationFormat = propagation::Format::JAEGER;
}
else {
std::cerr << "ERROR: unknown propagation format '"
<< strPropagationFormat
<< "', falling back to jaeger propagation format";
propagationFormat = propagation::Format::JAEGER;
}
Copy link
Member

Choose a reason for hiding this comment

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

seems like this could be simplified a bit

Suggested change
propagation::Format propagationFormat;
if (strPropagationFormat == "w3c") {
propagationFormat = propagation::Format::W3C;
}
else if (strPropagationFormat == "jaeger") {
propagationFormat = propagation::Format::JAEGER;
}
else {
std::cerr << "ERROR: unknown propagation format '"
<< strPropagationFormat
<< "', falling back to jaeger propagation format";
propagationFormat = propagation::Format::JAEGER;
}
auto propagationFormat = propagation::Format::JAEGER;
if (strPropagationFormat == "w3c") {
propagationFormat = propagation::Format::W3C;
}
else if (strPropagationFormat != "jaeger") {
std::cerr << "ERROR: unknown propagation format '"
<< strPropagationFormat
<< "', falling back to jaeger propagation format";
}

Copy link
Member

Choose a reason for hiding this comment

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

also, the same logic is repeated in Config::fromEnv(), can we combine them into a shared util function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

oss << std::setw(16) << std::setfill('0') << std::hex
<< spanContext.spanID();
oss << '-';
oss << (spanContext.isSampled() ? "01" : "00");
Copy link
Member

Choose a reason for hiding this comment

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

I have a conceptual problem with the above code - it is essentially testing production code with a copy of the same production code. A preferred test for these is to instantiate SpanContext with known IDs and then compare its serialized form with a known string. It makes the test both simpler and more reliable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -1,94 +1,102 @@
/*
Copy link
Member

Choose a reason for hiding this comment

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

NB: this diff is due to fixing line endings from Win to Unix (+1)

@AppVeyorBot
Copy link

@yurishkuro yurishkuro merged commit c1d5795 into jaegertracing:master Feb 26, 2021
@yurishkuro
Copy link
Member

🎉

@tobiasstadler tobiasstadler deleted the w3c-traceparent branch February 26, 2021 07:36
@miry
Copy link

miry commented Feb 26, 2021

👍

@tobiasstadler
Copy link
Contributor Author

Thank You @yurishkuro!

@timmysilv
Copy link

This is awesome! Can we get a new version (like 0.6.1) released with the latest changes?

@yurishkuro
Copy link
Member

released v0.7.0

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

w3c trace context propagation support
7 participants