newrelic
Version:
New Relic agent
114 lines (97 loc) • 3.75 kB
JavaScript
/*
* Copyright 2025 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
const { trace, isSpanContextValid } = require('@opentelemetry/api')
const { isTracingSuppressed } = require('@opentelemetry/core')
const TRACE_PARENT_HEADER = 'traceparent'
const TRACE_STATE_HEADER = 'tracestate'
const VERSION_PART = '(?!ff)[\\da-f]{2}'
const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}'
const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}'
const FLAGS_PART = '[\\da-f]{2}'
const TRACE_PARENT_REGEX = new RegExp(
`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`
)
class TraceState {
constructor(state) {
this.state = state
}
}
/**
* Parses information from the [traceparent] span tag and converts it into {@link SpanContext}
* @param traceParent - A meta property that comes from server.
* It should be dynamically generated server side to have the server's request trace Id,
* a parent span Id that was set on the server's request span,
* and the trace flags to indicate the server's sampling decision
* (01 = sampled, 00 = not sampled).
* for example: '{version}-{traceId}-{spanId}-{sampleDecision}'
* For more information see {@link https://www.w3.org/TR/trace-context/}
*/
function parseTraceParent(traceParent) {
const match = TRACE_PARENT_REGEX.exec(traceParent)
if (!match) return null
// According to the specification the implementation should be compatible
// with future versions. If there are more parts, we only reject it if it's using version 00
// See https://www.w3.org/TR/trace-context/#versioning-of-traceparent
if (match[1] === '00' && match[5]) return null
return {
traceId: match[2],
spanId: match[3],
traceFlags: parseInt(match[4], 16),
}
}
module.exports = class NewRelicTracePropagator {
constructor(agent) {
this.agent = agent
}
inject(context, carrier, setter) {
if (this.agent.config.distributed_tracing.enabled !== true) {
return
}
if (context.constructor.name === 'BaseContext') {
context = this.agent.tracer._contextManager.getContext()
}
const spanContext = trace.getSpanContext(context)
if (
!spanContext ||
isTracingSuppressed(context) ||
!isSpanContextValid(spanContext)
) { return }
context?.transaction?.insertDistributedTraceHeaders(carrier, setter, spanContext)
}
extract(context, carrier, getter) {
if (context.constructor.name === 'BaseContext') {
context = this.agent.tracer._contextManager.getContext()
}
if (this.agent.config.distributed_tracing.enabled !== true) {
return context
}
const traceParentHeader = getter.get(carrier, TRACE_PARENT_HEADER)
if (!traceParentHeader) return context
const traceParent = Array.isArray(traceParentHeader)
? traceParentHeader[0]
: traceParentHeader
if (typeof traceParent !== 'string') return context
// TODO: we parse it as well but the keys we return are different, should we layer this on?
const spanContext = parseTraceParent(traceParent)
if (!spanContext) return context
spanContext.isRemote = true
const traceStateHeader = getter.get(carrier, TRACE_STATE_HEADER)
if (traceStateHeader) {
// If more than one `tracestate` header is found, we merge them into a
// single header.
const state = Array.isArray(traceStateHeader)
? traceStateHeader.join(',')
: traceStateHeader
spanContext.traceState = new TraceState(
typeof state === 'string' ? state : undefined
)
}
return trace.setSpanContext(context, spanContext)
}
fields() {
return [TRACE_PARENT_HEADER, TRACE_STATE_HEADER]
}
}