dd-trace
Version:
Datadog APM tracing client for JavaScript
172 lines (140 loc) • 4.89 kB
JavaScript
'use strict'
const log = require('./log')
const spanFormat = require('./span_format')
const SpanSampler = require('./span_sampler')
const GitMetadataTagger = require('./git_metadata_tagger')
const { getEnvironmentVariable } = require('./config-helper')
const getProcessTags = require('./process-tags')
const startedSpans = new WeakSet()
const finishedSpans = new WeakSet()
class SpanProcessor {
constructor (exporter, prioritySampler, config) {
this._exporter = exporter
this._prioritySampler = prioritySampler
this._config = config
this._killAll = false
// TODO: This should already have been calculated in `config.js`.
if (config.stats?.enabled && !config.appsec?.standalone?.enabled) {
const { SpanStatsProcessor } = require('./span_stats')
this._stats = new SpanStatsProcessor(config)
}
this._spanSampler = new SpanSampler(config.sampler)
this._gitMetadataTagger = new GitMetadataTagger(config)
this._processTags = config.propagateProcessTags?.enabled
? getProcessTags().serialized
: false
}
sample (span) {
const spanContext = span.context()
this._prioritySampler.sample(spanContext)
this._spanSampler.sample(spanContext)
}
process (span) {
const spanContext = span.context()
const active = []
const formatted = []
const trace = spanContext._trace
const { flushMinSpans, tracing } = this._config
const { started, finished } = trace
if (trace.record === false) return
if (tracing === false) {
this._erase(trace, active)
return
}
if (started.length === finished.length || finished.length >= flushMinSpans) {
this.sample(span)
this._gitMetadataTagger.tagGitMetadata(spanContext)
let isFirstSpanInChunk = true
for (const span of started) {
if (span._duration === undefined) {
active.push(span)
} else {
const formattedSpan = spanFormat(span, isFirstSpanInChunk, this._processTags)
isFirstSpanInChunk = false
this._stats?.onSpanFinished(formattedSpan)
formatted.push(formattedSpan)
}
}
if (formatted.length !== 0 && trace.isRecording !== false) {
this._exporter.export(formatted)
}
this._erase(trace, active)
}
if (this._killAll) {
for (const startedSpan of started) {
if (!startedSpan._finished) {
startedSpan.finish()
}
}
}
}
killAll () {
this._killAll = true
}
_erase (trace, active) {
if (getEnvironmentVariable('DD_TRACE_EXPERIMENTAL_STATE_TRACKING') === 'true') {
const started = new Set()
const startedIds = new Set()
const finished = new Set()
const finishedIds = new Set()
for (const span of trace.finished) {
const context = span.context()
const id = context.toSpanId()
if (finished.has(span)) {
log.error('Span was already finished in the same trace: %s', span)
} else {
finished.add(span)
if (finishedIds.has(id)) {
log.error('Another span with the same ID was already finished in the same trace: %s', span)
} else {
finishedIds.add(id)
}
if (context._trace !== trace) {
log.error('A span was finished in the wrong trace: %s', span)
}
if (finishedSpans.has(span)) {
log.error('Span was already finished in a different trace: %s', span)
} else {
finishedSpans.add(span)
}
}
}
for (const span of trace.started) {
const context = span.context()
const id = context.toSpanId()
if (started.has(span)) {
log.error('Span was already started in the same trace: %s', span)
} else {
started.add(span)
if (startedIds.has(id)) {
log.error('Another span with the same ID was already started in the same trace: %s', span)
} else {
startedIds.add(id)
}
if (context._trace !== trace) {
log.error('A span was started in the wrong trace: %s', span)
}
if (startedSpans.has(span)) {
log.error('Span was already started in a different trace: %s', span)
} else {
startedSpans.add(span)
}
}
if (!finished.has(span)) {
log.error('Span started in one trace but was finished in another trace: %s', span)
}
}
for (const span of trace.finished) {
if (!started.has(span)) {
log.error('Span finished in one trace but was started in another trace: %s', span)
}
}
}
for (const span of trace.finished) {
span.context()._tags = {}
}
trace.started = active
trace.finished = []
}
}
module.exports = SpanProcessor