dd-trace
Version:
Datadog APM tracing client for JavaScript
289 lines (228 loc) • 7.54 kB
JavaScript
'use strict'
const pick = require('lodash.pick')
const id = require('../../id')
const DatadogSpanContext = require('../span_context')
const log = require('../../log')
const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
const traceKey = 'x-datadog-trace-id'
const spanKey = 'x-datadog-parent-id'
const originKey = 'x-datadog-origin'
const samplingKey = 'x-datadog-sampling-priority'
const tagsKey = 'x-datadog-tags'
const baggagePrefix = 'ot-baggage-'
const b3TraceKey = 'x-b3-traceid'
const b3TraceExpr = /^([0-9a-f]{16}){1,2}$/i
const b3SpanKey = 'x-b3-spanid'
const b3SpanExpr = /^[0-9a-f]{16}$/i
const b3ParentKey = 'x-b3-parentspanid'
const b3SampledKey = 'x-b3-sampled'
const b3FlagsKey = 'x-b3-flags'
const b3HeaderKey = 'b3'
const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog'
const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i
const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
const ddKeys = [traceKey, spanKey, samplingKey, originKey]
const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
const logKeys = ddKeys.concat(b3Keys)
class TextMapPropagator {
constructor (config) {
this._config = config
}
inject (spanContext, carrier) {
carrier[traceKey] = spanContext.toTraceId()
carrier[spanKey] = spanContext.toSpanId()
this._injectOrigin(spanContext, carrier)
this._injectSamplingPriority(spanContext, carrier)
this._injectBaggageItems(spanContext, carrier)
this._injectB3(spanContext, carrier)
this._injectTags(spanContext, carrier)
log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
}
extract (carrier) {
const spanContext = this._extractSpanContext(carrier)
if (!spanContext) return spanContext
log.debug(() => `Extract from carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
return spanContext
}
_injectOrigin (spanContext, carrier) {
const origin = spanContext._trace.origin
if (origin) {
carrier[originKey] = origin
}
}
_injectSamplingPriority (spanContext, carrier) {
const priority = spanContext._sampling.priority
if (Number.isInteger(priority)) {
carrier[samplingKey] = priority.toString()
}
}
_injectBaggageItems (spanContext, carrier) {
spanContext._baggageItems && Object.keys(spanContext._baggageItems).forEach(key => {
carrier[baggagePrefix + key] = String(spanContext._baggageItems[key])
})
}
_injectTags (spanContext, carrier) {
const trace = spanContext._trace
const tags = []
for (const key in trace.tags) {
if (!key.startsWith('_dd.p.')) continue
tags.push(`${key}=${trace.tags[key]}`)
}
const header = tags.join(',')
if (header.length <= 512) {
carrier[tagsKey] = header
} else {
trace.tags['_dd.propagation_error:max_size'] = 1
}
}
_injectB3 (spanContext, carrier) {
if (!this._config.experimental.b3) return
carrier[b3TraceKey] = spanContext._traceId.toString('hex')
carrier[b3SpanKey] = spanContext._spanId.toString('hex')
carrier[b3SampledKey] = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
if (spanContext._sampling.priority > AUTO_KEEP) {
carrier[b3FlagsKey] = '1'
}
if (spanContext._parentId) {
carrier[b3ParentKey] = spanContext._parentId.toString('hex')
}
}
_extractSpanContext (carrier) {
return this._extractDatadogContext(carrier) || this._extractB3Context(carrier) || this._extractSqsdContext(carrier)
}
_extractDatadogContext (carrier) {
const spanContext = this._extractGenericContext(carrier, traceKey, spanKey, 10)
if (spanContext) {
this._extractOrigin(carrier, spanContext)
this._extractBaggageItems(carrier, spanContext)
this._extractSamplingPriority(carrier, spanContext)
this._extractTags(carrier, spanContext)
}
return spanContext
}
_extractB3Context (carrier) {
if (!this._config.experimental.b3) return null
const b3 = this._extractB3Headers(carrier)
const debug = b3[b3FlagsKey] === '1'
const priority = this._getPriority(b3[b3SampledKey], debug)
const spanContext = this._extractGenericContext(b3, b3TraceKey, b3SpanKey)
if (priority !== undefined) {
if (!spanContext) {
// B3 can force a sampling decision without providing IDs
return new DatadogSpanContext({
traceId: id(),
spanId: null,
sampling: { priority }
})
}
spanContext._sampling.priority = priority
}
return spanContext
}
_extractSqsdContext (carrier) {
const headerValue = carrier[sqsdHeaderHey]
if (!headerValue) {
return null
}
let parsed
try {
parsed = JSON.parse(headerValue)
} catch (e) {
return null
}
return this._extractDatadogContext(parsed)
}
_extractGenericContext (carrier, traceKey, spanKey, radix) {
if (carrier[traceKey] && carrier[spanKey]) {
return new DatadogSpanContext({
traceId: id(carrier[traceKey], radix),
spanId: id(carrier[spanKey], radix)
})
}
return null
}
_extractB3Headers (carrier) {
if (b3HeaderExpr.test(carrier[b3HeaderKey])) {
return this._extractB3SingleHeader(carrier)
} else {
return this._extractB3MultipleHeaders(carrier)
}
}
_extractB3MultipleHeaders (carrier) {
const b3 = {}
if (b3TraceExpr.test(carrier[b3TraceKey]) && b3SpanExpr.test(carrier[b3SpanKey])) {
b3[b3TraceKey] = carrier[b3TraceKey]
b3[b3SpanKey] = carrier[b3SpanKey]
}
if (carrier[b3SampledKey]) {
b3[b3SampledKey] = carrier[b3SampledKey]
}
if (carrier[b3FlagsKey]) {
b3[b3FlagsKey] = carrier[b3FlagsKey]
}
return b3
}
_extractB3SingleHeader (carrier) {
const parts = carrier[b3HeaderKey].split('-')
if (parts[0] === 'd') {
return {
[b3SampledKey]: '1',
[b3FlagsKey]: '1'
}
} else if (parts.length === 1) {
return {
[b3SampledKey]: parts[0]
}
} else {
const b3 = {
[b3TraceKey]: parts[0],
[b3SpanKey]: parts[1]
}
if (parts[2]) {
b3[b3SampledKey] = parts[2] !== '0' ? '1' : '0'
if (parts[2] === 'd') {
b3[b3FlagsKey] = '1'
}
}
return b3
}
}
_extractOrigin (carrier, spanContext) {
const origin = carrier[originKey]
if (typeof carrier[originKey] === 'string') {
spanContext._trace.origin = origin
}
}
_extractBaggageItems (carrier, spanContext) {
Object.keys(carrier).forEach(key => {
const match = key.match(baggageExpr)
if (match) {
spanContext._baggageItems[match[1]] = carrier[key]
}
})
}
_extractSamplingPriority (carrier, spanContext) {
const priority = parseInt(carrier[samplingKey], 10)
if (Number.isInteger(priority)) {
spanContext._sampling.priority = parseInt(carrier[samplingKey], 10)
}
}
_extractTags (carrier, spanContext) {
if (!carrier[tagsKey]) return
const pairs = carrier[tagsKey].split(',')
for (const pair of pairs) {
const [key, value] = pair.split('=')
spanContext._trace.tags[key] = value
}
}
_getPriority (sampled, debug) {
if (debug) {
return USER_KEEP
} else if (sampled === '1') {
return AUTO_KEEP
} else if (sampled === '0') {
return AUTO_REJECT
}
}
}
module.exports = TextMapPropagator