dd-trace
Version:
Datadog APM tracing client for JavaScript
185 lines (157 loc) • 6.04 kB
JavaScript
const api = require('@opentelemetry/api')
const { sanitizeAttributes } = require('../../../../vendor/dist/@opentelemetry/core')
const id = require('../id')
const log = require('../log')
const TextMapPropagator = require('../opentracing/propagation/text_map')
const TraceState = require('../opentracing/propagation/tracestate')
const SpanContext = require('./span_context')
const Span = require('./span')
const Sampler = require('./sampler')
class Tracer {
constructor (library, config, tracerProvider) {
this._sampler = new Sampler()
this._config = config
this._tracerProvider = tracerProvider
// Is there a reason this is public?
this.instrumentationLibrary = library
this._isOtelLibrary = library?.name?.startsWith('@opentelemetry/instrumentation-')
this._spanLimits = {}
}
get resource () {
return this._tracerProvider.resource
}
_createSpanContextFromParent (parentSpanContext) {
return new SpanContext({
traceId: parentSpanContext._traceId,
spanId: id(),
parentId: parentSpanContext._spanId,
sampling: parentSpanContext._sampling,
baggageItems: { ...parentSpanContext._baggageItems },
trace: parentSpanContext._trace,
tracestate: parentSpanContext._tracestate
})
}
// Extracted method to create span context for a new span
_createSpanContextForNewSpan (context) {
const { traceId, spanId, traceFlags, traceState } = context
return this._convertOtelContextToDatadog(traceId, spanId, traceFlags, traceState)
}
_convertOtelContextToDatadog (traceId, spanId, traceFlag, ts, meta = {}) {
const origin = null
let samplingPriority = traceFlag
ts = ts?.traceparent || null
if (ts) {
// Use TraceState.fromString to parse the tracestate header
const traceState = TraceState.fromString(ts)
let ddTraceStateData = null
// Extract Datadog specific trace state data
traceState.forVendor('dd', (state) => {
ddTraceStateData = state
return state // You might need to adjust this part based on actual logic needed
})
if (ddTraceStateData) {
// Assuming ddTraceStateData is now a Map or similar structure containing Datadog trace state data
// Extract values as needed, similar to the original logic
const samplingPriorityTs = ddTraceStateData.get('s')
const origin = ddTraceStateData.get('o')
// Convert Map to object for meta
const otherPropagatedTags = Object.fromEntries(ddTraceStateData.entries())
// Update meta and samplingPriority based on extracted values
Object.assign(meta, otherPropagatedTags)
samplingPriority = TextMapPropagator._getSamplingPriority(
traceFlag,
Number.parseInt(samplingPriorityTs, 10),
origin
)
} else {
log.debug('no dd list member in tracestate from incoming request:', ts)
}
}
const spanContext = new SpanContext({
traceId: id(traceId, 16), spanId: id(), tags: meta, parentId: id(spanId, 16)
})
spanContext._sampling = { priority: samplingPriority }
spanContext._trace = { origin }
return spanContext
}
startSpan (name, options = {}, context = api.context.active()) {
// remove span from context in case a root span is requested via options
if (options.root) {
context = api.trace.deleteSpan(context)
}
const parentSpan = api.trace.getSpan(context)
const parentSpanContext = parentSpan && parentSpan.spanContext()
let spanContext
if (parentSpanContext && api.trace.isSpanContextValid(parentSpanContext)) {
spanContext = parentSpanContext._ddContext
? this._createSpanContextFromParent(parentSpanContext._ddContext)
: this._createSpanContextForNewSpan(parentSpanContext)
} else {
spanContext = new SpanContext()
}
const spanKind = options.kind || api.SpanKind.INTERNAL
const links = (options.links || []).map(link => {
return {
context: link.context,
attributes: sanitizeAttributes(link.attributes)
}
})
const attributes = sanitizeAttributes(options.attributes)
// TODO: sampling API is not yet supported
// // make sampling decision
// const samplingResult = this._sampler.shouldSample(
// context,
// spanContext.traceId,
// name,
// spanKind,
// attributes,
// links
// )
// // Should use new span context
// spanContext._ddContext._sampling.priority =
// samplingResult.decision === api.SamplingDecision.RECORD_AND_SAMPLED
// ? AUTO_KEEP
// : AUTO_REJECT
// if (samplingResult.decision === api.SamplingDecision.NOT_RECORD) {
// api.diag.debug('Recording is off, propagating context in a non-recording span')
// return api.trace.wrapSpanContext(spanContext)
// }
return new Span(
this,
context,
name,
spanContext,
spanKind,
links,
options.startTime,
// Set initial span attributes. The attributes object may have been mutated
// by the sampler, so we sanitize the merged attributes before setting them.
sanitizeAttributes(attributes)
)
}
startActiveSpan (name, options, context, fn) {
if (arguments.length === 2) {
fn = options
context = undefined
options = undefined
} else if (arguments.length === 3) {
fn = context
context = undefined
} else if (arguments.length !== 4) {
return
}
const parentContext = context || api.context.active()
const span = this.startSpan(name, options, parentContext)
const contextWithSpanSet = api.trace.setSpan(parentContext, span)
return api.context.with(contextWithSpanSet, fn, undefined, span)
}
getActiveSpanProcessor () {
return this._tracerProvider.getActiveSpanProcessor()
}
// not used in our codebase but needed for compatibility. See issue #1244
getSpanLimits () {
return this._spanLimits
}
}
module.exports = Tracer