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

w3c trace context propagation support #160

Closed
DavitSo opened this issue Jun 26, 2019 · 3 comments · Fixed by #255
Closed

w3c trace context propagation support #160

DavitSo opened this issue Jun 26, 2019 · 3 comments · Fixed by #255
Labels
enhancement good first issue Easy problem to tackle for first-time contributors help wanted

Comments

@DavitSo
Copy link

DavitSo commented Jun 26, 2019

Hi,
In client lib futures page is "Coming", so when do you planing to add this future?
Sorry if issue inappropriate for this.

Thanks in advance.

@ecourreges-orange
Copy link
Contributor

In the mean time, you can use the following as a sample code for the extraction/injection of the traceparent header.
I do not care about tracestate/baggage so did not code it.
Sorry, no time for me to make a PR for this.

Extractor:

    /// Partially copied from  jaeger-client-cpp/Propagagator.h
    /// Except we don't handle baggage
    virtual jaegertracing::SpanContext ExtractW3CTraceContext(
        const std::string& str) const
    {
        std::string safeValue = jaegertracing::net::URI::queryUnescape(str);
        std::istringstream iss(safeValue);
        return fromW3CStream(iss);
    }

// Code mostly copied from jaegertracing::SpanContext::fromStream
// Parses a traceparent header
// Example: 00-00000000000000000000deadbeef1234-1234deadbeef1234-01
SpanContext fromW3CStream(std::istream& in)
{
    // Read version
    auto buffer = jaegertracing::utils::HexParsing::readSegment(in, 2, '-');
    unsigned char traceparentVersion =
        jaegertracing::utils::HexParsing::decodeHex<unsigned char>(buffer);
    if (traceparentVersion != 0)
    {
        std::cout << "Unsupported traceparent version" << std::endl;
        return jaegertracing::SpanContext();
    }

    char ch = '\0';
    if (!in.get(ch) || ch != '-')
    {
        std::cout << "Bad separator after version" << std::endl;
        return jaegertracing::SpanContext();
    }

    constexpr auto kMaxChars = static_cast<size_t>(32);
    buffer = jaegertracing::utils::HexParsing::readSegment(in, kMaxChars, '-');

    if (buffer.empty())
    {
        std::cout << "Failed traceparent at traceID" << std::endl;
        return jaegertracing::SpanContext();
    }
    auto beginLowStr = std::end(buffer) - kMaxChars / 2;
    const std::string highStr(std::begin(buffer), beginLowStr);
    const std::string lowStr(beginLowStr, std::end(buffer));
    jaegertracing::TraceID traceID(
        jaegertracing::utils::HexParsing::decodeHex<uint64_t>(highStr),
        jaegertracing::utils::HexParsing::decodeHex<uint64_t>(lowStr));
    if (!traceID.isValid())
    {
        std::cout << "Invalid traceparent traceID" << std::endl;
        return jaegertracing::SpanContext();
    }

    ch = '\0';
    if (!in.get(ch) || ch != '-')
    {
        std::cout << "Bad separator after traceid" << std::endl;
        return jaegertracing::SpanContext();
    }

    constexpr auto kMaxUInt64Chars = static_cast<size_t>(16);
    buffer =
        jaegertracing::utils::HexParsing::readSegment(in, kMaxUInt64Chars, '-');
    if (buffer.empty())
    {
        std::cout << "no span id" << std::endl;
        return jaegertracing::SpanContext();
    }
    uint64_t spanID =
        jaegertracing::utils::HexParsing::decodeHex<uint64_t>(buffer);
    if (!in.get(ch) || ch != '-')
    {
        std::cout << "bad separator after span id" << std::endl;
        return jaegertracing::SpanContext();
    }

    constexpr auto kMaxByteChars = static_cast<size_t>(2);
    buffer =
        jaegertracing::utils::HexParsing::readSegment(in, kMaxByteChars, '-');
    if (buffer.empty())
    {
        std::cout << "empty traceparent flags" << std::endl;
        return jaegertracing::SpanContext();
    }
    unsigned char flags =
        jaegertracing::utils::HexParsing::decodeHex<unsigned char>(buffer);

    in.clear();
    jaegertracing::SpanContext::StrMap baggage;
    std::string debugID;
    return jaegertracing::SpanContext(
        traceID, spanID, 0ULL, flags, baggage, debugID);
}

Unit test with cppunit

void testOpenTracingCarrier::testExtractW3C()
{
    ParamsMap lParamMap;
    PnSHTTPCarrierReader carrier(lParamMap);

    jaegertracing::SpanContext ctx = carrier.ExtractW3CTraceContext(
        "00-00000000000000000000deadbeef1234-1234deadbeef1234-01");
    CPPUNIT_ASSERT(ctx.debugID().empty());
    CPPUNIT_ASSERT(ctx.isValid());
    CPPUNIT_ASSERT_EQUAL(jaegertracing::TraceID(0, 244837814047284),
                         ctx.traceID());
    CPPUNIT_ASSERT_EQUAL(uint64_t(1311918229285704244), ctx.spanID());
    CPPUNIT_ASSERT_EQUAL(uint64_t(0), ctx.parentID());
    CPPUNIT_ASSERT_EQUAL(true, ctx.isSampled());

    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235-1234deadbeef1235-00");
    CPPUNIT_ASSERT(ctx.debugID().empty());
    CPPUNIT_ASSERT(ctx.isValid());
    CPPUNIT_ASSERT_EQUAL(
        jaegertracing::TraceID(244837814047284, 244837814047285),
        ctx.traceID());
    CPPUNIT_ASSERT_EQUAL(uint64_t(1311918229285704245), ctx.spanID());
    CPPUNIT_ASSERT_EQUAL(uint64_t(0), ctx.parentID());
    CPPUNIT_ASSERT_EQUAL(false, ctx.isSampled());

    // invalid version
    ctx = carrier.ExtractW3CTraceContext("01-");
    CPPUNIT_ASSERT(!ctx.isValid());
    ctx = carrier.ExtractW3CTraceContext("000-");
    CPPUNIT_ASSERT(!ctx.isValid());
    ctx = carrier.ExtractW3CTraceContext("00:");
    CPPUNIT_ASSERT(!ctx.isValid());

    // valid traceid 30 hex instead of 32
    ctx = carrier.ExtractW3CTraceContext(
        "00-00deadbeef12340000deadbeef1235-1234deadbeef1235-00");
    CPPUNIT_ASSERT(ctx.isValid());
    // invalid traceid 34 hex instead of 32
    ctx = carrier.ExtractW3CTraceContext(
        "00-000000deadbeef12340000deadbeef1235-1234deadbeef1235-00");
    CPPUNIT_ASSERT(!ctx.isValid());

    // invalid traceid empty
    ctx = carrier.ExtractW3CTraceContext("00--1234deadbeef1235-00");

    // invalid separator
    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235:1234deadbeef1235:00");
    CPPUNIT_ASSERT(!ctx.isValid());

    // invalid span empty
    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235--00");

    // invalid traceid
    ctx = carrier.ExtractW3CTraceContext("0:deadbeef1234:deadbeef1234:1");
    CPPUNIT_ASSERT(!ctx.isValid());

    // invalid span id
    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235-98765431234dead1235-00");
    CPPUNIT_ASSERT(!ctx.isValid());

    // invalid separator after span id
    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235-1234deadbeef1235:00");
    CPPUNIT_ASSERT(!ctx.isValid());

    // empty flags
    ctx = carrier.ExtractW3CTraceContext(
        "00-0000deadbeef12340000deadbeef1235-1234deadbeef1235-");
    CPPUNIT_ASSERT(!ctx.isValid());
}

For the injection, this is what I use, you can see that I switched from uber-trace-id to W3C in my output injection

/// Get all the opentracing http headers corresponding to our span
/// For direct printing in the query lines
std::unordered_map<std::string, std::string> getOpenTracingHeadersMap(
    const std::unique_ptr<opentracing::Span>& span)
{
    // Get jaeger headers
    // A bit complicated to just get "uber-trace-id:
    // traceid:spanid:parentspanid:flags" !!!
    // But there can be other headers like debug or baggage
    std::unordered_map<std::string, std::string> text_map;
    /*
    HTTPHeaderCarrier carrier(text_map);
    auto tracer = opentracing::Tracer::Global();
    tracer->Inject(span.get()->context(), carrier);
    */

    // Generate the W3C Trace Context traceparent header
    jaegertracing::Span* jSpan = dynamic_cast<jaegertracing::Span*>(span.get());
    if (jSpan)
    {
        std::ostringstream oss;
        const jaegertracing::SpanContext& sc = jSpan->context();
        // Version 00
        oss << "00-";
        // trace-id 32 HEX DIGITS
        oss << std::setw(16) << std::setfill('0') << std::hex
            << sc.traceID().high();
        oss << std::setw(16) << std::setfill('0') << std::hex
            << sc.traceID().low();
        // parent-id 16 HEX DIGITS
        oss << '-' << std::setw(16) << std::setfill('0') << std::hex
            << sc.spanID() << '-';
        // trace-flags 2 HEX DIGITS only sampled or not is supported so far
        if (sc.isSampled())
        {
            oss << "01";
        }
        else
        {
            oss << "00";
        }
        Logger::Instance().debug(
            "[getOpenTracingHeadersMap] OpenTracingSpan jaeger span: %s",
            oss.str());
        text_map["traceparent"] = oss.str();
    }
    else
    {
        Logger::Instance().debug(
            "[getOpenTracingHeadersMap] Noop Span, jaeger is not initialized");
    }
    return text_map;
}

Unit test

void testOpenTracingSpan::testGetOpenTracingHeadersMap()
{
    std::unique_ptr<opentracing::Span> noopspan =
        opentracing::MakeNoopTracer()->StartSpan("noop");
    std::unordered_map<std::string, std::string> res =
        getOpenTracingHeadersMap(noopspan);
    CPPUNIT_ASSERT(res.empty());

    // Global Init is done by testCreateSpan
    auto tracer = opentracing::Tracer::Global();

    std::stringstream iss;
    iss << "deadbeef1234:dead1234beef:0:1";
    jaegertracing::SpanContext ctx;
    ctx = jaegertracing::SpanContext::fromStream(iss);

    std::unique_ptr<opentracing::Span> span = tracer->StartSpan(
        "testGetOpenTracingHeadersMap", {opentracing::ChildOf(&ctx)});
    jaegertracing::Span* jSpan = dynamic_cast<jaegertracing::Span*>(span.get());
    CPPUNIT_ASSERT(jSpan);

    res = getOpenTracingHeadersMap(span);
    CPPUNIT_ASSERT_EQUAL(size_t(1), res.size());
    // the spanId is random so check beginning, end and length of string
    CPPUNIT_ASSERT(res["traceparent"].rfind(
                       "00-00000000000000000000deadbeef1234-", 0) == 0);
    CPPUNIT_ASSERT(res["traceparent"].find("-01") != std::string::npos);
    CPPUNIT_ASSERT_EQUAL(size_t(55), res["traceparent"].length());
}

I also have code in my app which selects the header priority: traceparent if found, and uber-trace-id if not found, and create a new context if none of the two.
I may add also B3 management (last in the order of header priority) in a couple of months if really required.

@robermorales
Copy link

Is there any plan to include support for w3c trace context in the near future? thanks in advance,

@yurishkuro
Copy link
Member

It's being implemented in #255

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement good first issue Easy problem to tackle for first-time contributors help wanted
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants