Skip to content

Commit

Permalink
feat: accept both w3c and honeycomb propagation headers by default (#514
Browse files Browse the repository at this point in the history
)

* attempt to parse incoming header as w3c (even if no hook specified) if w3c header found - unless honeycomb header also found, then default to that. 

Co-authored-by: Robb Kidd <robbkidd@honeycomb.io>
  • Loading branch information
JamieDanielson and robbkidd authored Dec 20, 2021
1 parent 8d1d590 commit 07a2eac
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 65 deletions.
63 changes: 35 additions & 28 deletions lib/instrumentation/trace-util.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
/* eslint-env node */
const api = require("../api");
const propagation = require("../propagation");

// returns the header name/value for the first header with a value in the request.
const getValueFromHeaders = (requestHeaders, headers) => {
let value, header;

for (const h of headers) {
let headerValue = requestHeaders[h.toLowerCase()];
if (headerValue) {
header = h; // source = `${h} http header`;
value = headerValue;
break;
}
}

if (typeof value !== "undefined") {
return { value, header };
}

return undefined;
};
const api = require("../api"),
propagation = require("../propagation"),
pkg = require("../../package.json"),
debug = require("debug")(`${pkg.name}:event`);

// adds trace source field to the context and returns the updated context
function _includeSource(context, key) {
Expand Down Expand Up @@ -56,16 +38,35 @@ exports.propagateTraceHeader = () => {
// parse a trace header and return object used to populate args to startTrace
// deprecated: in the next major version this should get renamed to getSpanContext
exports.getTraceContext = (traceIdSource, req) => {
const { honeycomb, aws } = api;
const { honeycomb, w3c, aws } = api;
if (typeof traceIdSource === "undefined" || typeof traceIdSource === "string") {
let headers =
typeof traceIdSource === "undefined" ? [honeycomb.TRACE_HTTP_HEADER] : [traceIdSource];
let valueAndHeader = getValueFromHeaders(req.headers, headers);
let header, value;

if (typeof traceIdSource !== "undefined") {
header = traceIdSource;
value = req.headers[traceIdSource.toLowerCase()];
} else {
const honeycombHeaderValue = req.headers[honeycomb.TRACE_HTTP_HEADER.toLowerCase()];
const w3cHeaderValue = req.headers[w3c.TRACE_HTTP_HEADER.toLowerCase()];

if (honeycombHeaderValue && w3cHeaderValue) {
debug("warn: received both honeycomb and w3c propagation headers, preferring honeycomb.");
}

if (!valueAndHeader) {
if (honeycombHeaderValue) {
header = honeycomb.TRACE_HTTP_HEADER;
value = honeycombHeaderValue;
} else if (w3cHeaderValue) {
header = w3c.TRACE_HTTP_HEADER;
value = w3cHeaderValue;
}
}

if (!value) {
// couldn't find trace context
return {};
}
let { value, header } = valueAndHeader;

let parsed = {};
switch (header) {
//honeycomb trace header
Expand All @@ -75,6 +76,12 @@ exports.getTraceContext = (traceIdSource, req) => {
return _includeSource(parsed, header);
}

case w3c.TRACE_HTTP_HEADER: {
header = w3c.TRACE_HTTP_HEADER;
parsed = w3c.unmarshalTraceContext(value);
return _includeSource(parsed, header);
}

case aws.TRACE_HTTP_HEADER: {
header = aws.TRACE_HTTP_HEADER;
parsed = aws.unmarshalTraceContext(value);
Expand Down
91 changes: 54 additions & 37 deletions lib/instrumentation/trace-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,81 +10,98 @@ function getRequestWithHeader(name, value) {
return req;
}

function getRequestWithHeaders(headers) {
const req = new http.IncomingMessage();
headers.forEach((h) => (req.headers[h.name.toLowerCase()] = h.value));
return req;
}

describe("getTraceContext", () => {
cases(
"AWS X-Ray trace header",
"beeline trace header",
(opts) => {
expect(
traceUtil.getTraceContext(
"X-Amzn-Trace-Id",
getRequestWithHeader("X-Amzn-Trace-Id", opts.headerVal)
undefined,
getRequestWithHeader(api.honeycomb.TRACE_HTTP_HEADER, opts.headerVal)
)
).toEqual(opts.expectedContext);
},
[
{
name: "root / no parent",
headerVal: "Root=1-67891233-abcdef012345678912345678",
name: "v1 trace_id + parent_id, missing context",
headerVal: "1;trace_id=abcdef,parent_id=12345",
expectedContext: {
traceId: "1-67891233-abcdef012345678912345678",
parentSpanId: "1-67891233-abcdef012345678912345678",
source: "X-Amzn-Trace-Id http header",
traceId: "abcdef",
parentSpanId: "12345",
customContext: undefined,
dataset: undefined,
source: "X-Honeycomb-Trace http header",
},
},
{
name: "root / parent",
headerVal: "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8",
expectedContext: {
traceId: "1-5759e988-bd862e3fe1be46a994272793",
parentSpanId: "53995c3f42cd8ad8",
source: "X-Amzn-Trace-Id http header",
},
name: "v1, missing trace_id",
contextStr: "1;parent_id=12345",
expectedContext: {},
},
]
);
cases(
"w3c trace header fallback",
(opts) => {
expect(
traceUtil.getTraceContext(
undefined,
getRequestWithHeader(api.w3c.TRACE_HTTP_HEADER, opts.headerVal)
)
).toEqual(opts.expectedContext);
},
[
{
name: "self / root / no parent",
headerVal:
"Parent=1-5983f5c9-36d365bc453d28036a63032b;Root=1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
name: "w3c trace_id + parent_id, missing context",
headerVal: "00-7f042f75651d9782dcff93a45fa99be0-c998e73e5420f609-01",
expectedContext: {
parentSpanId: "1-5983f5c9-36d365bc453d28036a63032b",
traceId: "1-5983f5c9-56dcf0bc6d4d214d2dbbe8c6",
source: "X-Amzn-Trace-Id http header",
traceId: "7f042f75651d9782dcff93a45fa99be0",
parentSpanId: "c998e73e5420f609",
customContext: undefined,
dataset: undefined,
source: "traceparent http header",
},
},
{
// shouldn't happen at least with aws generated headers, but if we're missing a Root= clause, we aren't in a trace at all.
name: "no root / parent",
headerVal: "Parent=53995c3f42cd8ad8",
expectedContext: {},
},
]
);
cases(
"beeline trace header",
"if both honeycomb and w3c, choose honeycomb",
(opts) => {
expect(
traceUtil.getTraceContext(
undefined,
getRequestWithHeader(api.honeycomb.TRACE_HTTP_HEADER, opts.headerVal)
getRequestWithHeaders([
{
name: api.w3c.TRACE_HTTP_HEADER,
value: opts.w3cHeaderVal,
},
{
name: api.honeycomb.TRACE_HTTP_HEADER,
value: opts.beelineHeaderVal,
},
])
)
).toEqual(opts.expectedContext);
},
[
{
name: "v1 trace_id + parent_id, missing context",
headerVal: "1;trace_id=abcdef,parent_id=12345",
name: "we got both honeycomb and w3c, this should not happen",
w3cHeaderVal: "00-7f042f75651d9782dcff93a45fa99be0-c998e73e5420f609-01",
beelineHeaderVal: "1;trace_id=abcdefbeelineahh,parent_id=12345",
expectedContext: {
traceId: "abcdef",
traceId: "abcdefbeelineahh",
parentSpanId: "12345",
customContext: undefined,
dataset: undefined,
source: "X-Honeycomb-Trace http header",
},
},
{
name: "v1, missing trace_id",
contextStr: "1;parent_id=12345",
expectedContext: {},
},
]
);
});

0 comments on commit 07a2eac

Please sign in to comment.