Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement "b3 single" header format for zipkin #4712

Merged
merged 9 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/root/configuration/http_conn_man/headers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,15 @@ The encode one or more options. For example, Debug is encoded as
``X-B3-Flags: 1``. See more on zipkin tracing
`here <https://github.com/openzipkin/b3-propagation>`.

.. _config_http_conn_man_headers_b3:
zyfjeff marked this conversation as resolved.
Show resolved Hide resolved

b3
----------

The *b3* HTTP header is used by the Zipkin tracer in Envoy.
Is a more compressed header format. See more on zipkin tracing
`here <https://github.com/openzipkin/b3-propagation#single-header>`.

.. _config_http_conn_man_headers_custom_request_headers:

Custom request/response headers
Expand Down
3 changes: 2 additions & 1 deletion docs/root/intro/arch_overview/tracing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ Alternatively the trace context can be manually propagated by the service:
:ref:`config_http_conn_man_headers_x-b3-sampled`, and
:ref:`config_http_conn_man_headers_x-b3-flags`). The :ref:`config_http_conn_man_headers_x-b3-sampled`
header can also be supplied by an external client to either enable or disable tracing for a particular
request.
request. In addition to supporting single :ref:`config_http_conn_man_headers_b3` header to propagate,
zyfjeff marked this conversation as resolved.
Show resolved Hide resolved
it is a more compressed header format.

What data each trace contains
-----------------------------
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Version history
its behaviour within TCP and HTTP implementations.
* stream: renamed `perRequestState` to `filterState` in `StreamInfo`.
* thrift_proxy: introduced thrift rate limiter filter
* tracing: added support for Zipkin tracer of b3 single header format.
zyfjeff marked this conversation as resolved.
Show resolved Hide resolved
* upstream: changed how load calculation for :ref:`priority levels<arch_overview_load_balancing_priority_levels>` and :ref:`panic thresholds<arch_overview_load_balancing_panic_threshold>` interact. As long as normalized total health is 100% panic thresholds are disregarded.

1.8.0 (Oct 4, 2018)
Expand Down
7 changes: 1 addition & 6 deletions include/envoy/http/header_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,7 @@ class HeaderEntry {
HEADER_FUNC(Upgrade) \
HEADER_FUNC(UserAgent) \
HEADER_FUNC(Vary) \
HEADER_FUNC(Via) \
HEADER_FUNC(XB3TraceId) \
HEADER_FUNC(XB3SpanId) \
HEADER_FUNC(XB3ParentSpanId) \
HEADER_FUNC(XB3Sampled) \
HEADER_FUNC(XB3Flags)
HEADER_FUNC(Via)

/**
* The following functions are defined for each inline header above. E.g., for ContentLength we
Expand Down
5 changes: 0 additions & 5 deletions source/common/http/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,6 @@ class HeaderValues {
const LowerCaseString UserAgent{"user-agent"};
const LowerCaseString Vary{"vary"};
const LowerCaseString Via{"via"};
const LowerCaseString XB3TraceId{"x-b3-traceid"};
const LowerCaseString XB3SpanId{"x-b3-spanid"};
const LowerCaseString XB3ParentSpanId{"x-b3-parentspanid"};
const LowerCaseString XB3Sampled{"x-b3-sampled"};
const LowerCaseString XB3Flags{"x-b3-flags"};
const LowerCaseString XContentTypeOptions{"x-content-type-options"};
const LowerCaseString XSquashDebug{"x-squash-debug"};

Expand Down
2 changes: 2 additions & 0 deletions source/extensions/tracers/zipkin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ envoy_cc_library(
srcs = [
"span_buffer.cc",
"span_context.cc",
"span_context_extractor.cc",
"tracer.cc",
"util.cc",
"zipkin_core_types.cc",
Expand All @@ -23,6 +24,7 @@ envoy_cc_library(
hdrs = [
"span_buffer.h",
"span_context.h",
"span_context_extractor.h",
"tracer.h",
"tracer_interface.h",
"util.h",
Expand Down
230 changes: 230 additions & 0 deletions source/extensions/tracers/zipkin/span_context_extractor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include "extensions/tracers/zipkin/span_context_extractor.h"

#include "common/common/assert.h"
#include "common/common/utility.h"

#include "extensions/tracers/zipkin/span_context.h"
#include "extensions/tracers/zipkin/zipkin_core_constants.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace Zipkin {
namespace {
constexpr int FormatMaxLength = 32 + 1 + 16 + 3 + 16; // traceid128-spanid-1-parentid
bool validSamplingFlags(char c) {
if (c == '1' || c == '0' || c == 'd') {
return true;
}
return false;
}

bool getSamplingFlags(char c, const Tracing::Decision tracing_decision) {
if (validSamplingFlags(c)) {
return c == '0' ? false : true;
} else {
return tracing_decision.traced;
}
}

} // namespace

SpanContextExtractor::SpanContextExtractor(Http::HeaderMap& request_headers)
: request_headers_(request_headers) {}

SpanContextExtractor::~SpanContextExtractor() {}

bool SpanContextExtractor::extractSampled(const Tracing::Decision tracing_decision) {
bool sampled(false);
auto b3_header_entry = request_headers_.get(ZipkinCoreConstants::get().B3);
if (b3_header_entry) {
absl::string_view b3 = b3_header_entry->value().getStringView();
int sampled_pos = 0;
switch (b3.length()) {
case 1:
break;
case 35: // 16 + 1 + 16 + 2
sampled_pos = 34;
break;
case 51: // 32 + 1 + 16 + 2
sampled_pos = 50;
break;
case 52: // 16 + 1 + 16 + 2 + 1 + 16
sampled_pos = 34;
break;
case 68: // 32 + 1 + 16 + 2 + 1 + 16
sampled_pos = 50;
break;
default:
return tracing_decision.traced;
}
return getSamplingFlags(b3[sampled_pos], tracing_decision);
}

auto x_b3_sampled_entry = request_headers_.get(ZipkinCoreConstants::get().X_B3_SAMPLED);
if (!x_b3_sampled_entry) {
return tracing_decision.traced;
}
// Checking if sampled flag has been specified. Also checking for 'true' value, as some old
// zipkin tracers may still use that value, although should be 0 or 1.
absl::string_view xb3_sampled = x_b3_sampled_entry->value().getStringView();
sampled = xb3_sampled == ZipkinCoreConstants::get().SAMPLED || xb3_sampled == "true";
return sampled;
}

std::pair<SpanContext, bool> SpanContextExtractor::extractSpanContext(bool is_sampled) {
if (request_headers_.get(ZipkinCoreConstants::get().B3)) {
return extractSpanContextFromB3SingleFormat(is_sampled);
}
uint64_t trace_id(0);
uint64_t trace_id_high(0);
uint64_t span_id(0);
uint64_t parent_id(0);

auto b3_trace_id_entry = request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID);
auto b3_span_id_entry = request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID);
if (b3_span_id_entry && b3_trace_id_entry) {
// Extract trace id - which can either be 128 or 64 bit. For 128 bit,
// it needs to be divided into two 64 bit numbers (high and low).
const std::string tid = b3_trace_id_entry->value().c_str();
if (b3_trace_id_entry->value().size() == 32) {
const std::string high_tid = tid.substr(0, 16);
const std::string low_tid = tid.substr(16, 16);
if (!StringUtil::atoul(high_tid.c_str(), trace_id_high, 16) ||
!StringUtil::atoul(low_tid.c_str(), trace_id, 16)) {
throw ExtractorException(
zyfjeff marked this conversation as resolved.
Show resolved Hide resolved
fmt::format("Invalid traceid_high {} or tracid {}", high_tid.c_str(), low_tid.c_str()));
}
} else if (!StringUtil::atoul(tid.c_str(), trace_id, 16)) {
throw ExtractorException(fmt::format("Invalid trace_id {}", tid.c_str()));
}

const std::string spid = b3_span_id_entry->value().c_str();
if (!StringUtil::atoul(spid.c_str(), span_id, 16)) {
throw ExtractorException(fmt::format("Invalid span id {}", spid.c_str()));
}

auto b3_parent_id_entry = request_headers_.get(ZipkinCoreConstants::get().X_B3_PARENT_SPAN_ID);
if (b3_parent_id_entry) {
const std::string pspid = b3_parent_id_entry->value().c_str();
if (!StringUtil::atoul(pspid.c_str(), parent_id, 16)) {
throw ExtractorException(fmt::format("Invalid parent span id {}", pspid.c_str()));
}
}
} else {
return std::pair<SpanContext, bool>(SpanContext(), false);
}

return std::pair<SpanContext, bool>(
SpanContext(trace_id_high, trace_id, span_id, parent_id, is_sampled), true);
}

std::pair<SpanContext, bool>
SpanContextExtractor::extractSpanContextFromB3SingleFormat(bool is_sampled) {
auto b3_head_entry = request_headers_.get(ZipkinCoreConstants::get().B3);
ASSERT(b3_head_entry);
const std::string b3 = b3_head_entry->value().c_str();
if (!b3.length()) {
throw ExtractorException("Invalid input: empty");
}

if (b3.length() == 1) { // possibly sampling flags
if (validSamplingFlags(b3[0])) {
return std::pair<SpanContext, bool>(SpanContext(), false);
}
throw ExtractorException(fmt::format("Invalid input: invalid sampling flag {}", b3[0]));
}

if (b3.length() < 16 + 1 + 16 /* traceid64-spanid */) {
throw ExtractorException("Invalid input: truncated");
} else if (b3.length() > FormatMaxLength) {
throw ExtractorException("Invalid input: too long");
}

uint64_t trace_id(0);
uint64_t trace_id_high(0);
uint64_t span_id(0);
uint64_t parent_id(0);

uint64_t pos = 0;

const std::string trace_id_str = b3.substr(pos, 16);
if (b3[pos + 32] == '-') {
if (!StringUtil::atoul(trace_id_str.c_str(), trace_id_high, 16)) {
throw ExtractorException(
fmt::format("Invalid input: invalid trace id high {}", trace_id_str.c_str()));
}
pos += 16;
const std::string trace_id_low_str = b3.substr(pos, 16);
if (!StringUtil::atoul(trace_id_low_str.c_str(), trace_id, 16)) {
throw ExtractorException(
fmt::format("Invalid input: invalid trace id {}", trace_id_low_str.c_str()));
}
} else {
if (!StringUtil::atoul(trace_id_str.c_str(), trace_id, 16)) {
throw ExtractorException(
fmt::format("Invalid input: invalid trace id {}", trace_id_str.c_str()));
}
}

pos += 16; // traceId ended
if (!(b3[pos++] == '-')) {
throw ExtractorException("Invalid input: not exists span id");
}

const std::string span_id_str = b3.substr(pos, 16);
if (!StringUtil::atoul(span_id_str.c_str(), span_id, 16)) {
throw ExtractorException(fmt::format("Invalid input: invalid span id {}", span_id_str.c_str()));
}
pos += 16; // spanId ended

if (b3.length() > pos) {
// If we are at this point, we have more than just traceId-spanId.
// If the sampling field is present, we'll have a delimiter 2 characters from now. Ex "-1"
// If it is absent, but a parent ID is (which is strange), we'll have at least 17 characters.
// Therefore, if we have less than two characters, the input is truncated.
if (b3.length() == (pos + 1)) {
throw ExtractorException("Invalid input: truncated");
}

if (!(b3[pos++] == '-')) {
throw ExtractorException("Invalid input: not exists sampling field");
}

// If our position is at the end of the string, or another delimiter is one character past our
// position, try to read sampled status.
if (b3.length() == pos + 1 || ((b3.length() >= pos + 2) && (b3[pos + 1] == '-'))) {
if (!validSamplingFlags(b3[pos])) {
throw ExtractorException(fmt::format("Invalid input: invalid sampling flag {}", b3[pos]));
}
pos++; // consume the sampled status
} else {
throw ExtractorException("Invalid input: truncated");
}

if (b3.length() > pos) {
std::cout << "pos:" << pos << "len: " << b3.length() << std::endl;
zyfjeff marked this conversation as resolved.
Show resolved Hide resolved
// If we are at this point, we should have a parent ID, encoded as "-[0-9a-f]{16}"
if (b3.length() != pos + 17) {
throw ExtractorException("Invalid input: truncated");
}

ASSERT(b3[pos] == '-');
pos++;

const std::string parent_id_str = b3.substr(pos, b3.length() - pos);
if (!StringUtil::atoul(parent_id_str.c_str(), parent_id, 16)) {
throw ExtractorException(
fmt::format("Invalid input: invalid parent id {}", parent_id_str.c_str()));
}
}
}

return std::pair<SpanContext, bool>(
SpanContext(trace_id_high, trace_id, span_id, parent_id, is_sampled), true);
}

} // namespace Zipkin
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
44 changes: 44 additions & 0 deletions source/extensions/tracers/zipkin/span_context_extractor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#include "envoy/common/exception.h"
#include "envoy/tracing/http_tracer.h"

#include "common/http/header_map_impl.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace Zipkin {

class SpanContext;

struct ExtractorException : public EnvoyException {
ExtractorException(const std::string& what) : EnvoyException(what) {}
ExtractorException(const ExtractorException& ex) : EnvoyException(ex.what()) {}
};

/**
* This class is used to SpanContext extracted from the Http header
*/
class SpanContextExtractor {
public:
SpanContextExtractor(Http::HeaderMap& request_headers);
~SpanContextExtractor();
bool extractSampled(const Tracing::Decision tracing_decision);
std::pair<SpanContext, bool> extractSpanContext(bool is_sampled);

private:
/*
* Use to SpanContext extraced from B3 single format Http header
* b3: {x-b3-traceid}-{x-b3-spanid}-{if x-b3-flags 'd' else x-b3-sampled}-{x-b3-parentspanid}
* See: "https://github.com/openzipkin/b3-propagation
*/
std::pair<SpanContext, bool> extractSpanContextFromB3SingleFormat(bool is_sampled);
bool tryExtractSampledFromB3SingleFormat();
const Http::HeaderMap& request_headers_;
};

} // namespace Zipkin
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
15 changes: 10 additions & 5 deletions source/extensions/tracers/zipkin/zipkin_core_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <string>

#include "envoy/http/header_map.h"

#include "common/singleton/const_singleton.h"

namespace Envoy {
Expand Down Expand Up @@ -30,11 +32,14 @@ class ZipkinCoreConstantValues {
const std::string SERVER_ADDR = "sa";

// Zipkin B3 headers
const std::string X_B3_TRACE_ID = "X-B3-TraceId";
const std::string X_B3_SPAN_ID = "X-B3-SpanId";
const std::string X_B3_PARENT_SPAN_ID = "X-B3-ParentSpanId";
const std::string X_B3_SAMPLED = "X-B3-Sampled";
const std::string X_B3_FLAGS = "X-B3-Flags";
const Http::LowerCaseString X_B3_TRACE_ID{"X-B3-TraceId"};
const Http::LowerCaseString X_B3_SPAN_ID{"X-B3-SpanId"};
const Http::LowerCaseString X_B3_PARENT_SPAN_ID{"X-B3-ParentSpanId"};
const Http::LowerCaseString X_B3_SAMPLED{"X-B3-Sampled"};
const Http::LowerCaseString X_B3_FLAGS{"X-B3-Flags"};

// Zipkin b3 single header
const Http::LowerCaseString B3{"b3"};

const std::string SAMPLED = "1";
const std::string NOT_SAMPLED = "0";
Expand Down
Loading