newrelic
Version:
New Relic agent
158 lines (134 loc) • 3.96 kB
JavaScript
/*
* Copyright 2025 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
const invalidTraceId = '0'.repeat(32)
const invalidParentId = '0'.repeat(16)
/**
* Parses a W3C traceparent header into an object representation.
*
* @link https://www.w3.org/TR/trace-context/#traceparent-header
*
* @property {string} version Traceparent spec version parsed from the header.
* @property {string} traceId Trace identifier from the header.
* @property {string} parentId Span identifier from the header.
* @property {string} flags Flags as parsed from the header.
*/
class Traceparent {
static W3C_TRACEPARENT_VERSION = '00'
static FLAG_SAMPLED = 0x00000001
#flagBits = 0x00000000
constructor({ version = Traceparent.W3C_TRACEPARENT_VERSION, traceId, parentId, flags }) {
if (version !== Traceparent.W3C_TRACEPARENT_VERSION || version.toLowerCase() === 'ff') {
throw Error(`only w3c version ${Traceparent.W3C_TRACEPARENT_VERSION} is supported, found ${version}`)
}
if (this.#isValidTraceId(traceId) === false) {
throw Error(`received invalid trace id: ${traceId}`)
}
if (this.#isValidParentId(parentId) === false) {
throw Error(`received invalid parent id: ${parentId}`)
}
if (this.#isValidFlags(flags) === false) {
throw Error(`received invalid flags: ${flags}`)
}
Object.defineProperties(this, {
version: {
enumerable: true,
value: version
},
traceId: {
enumerable: true,
value: traceId
},
parentId: {
enumerable: true,
value: parentId
},
flags: {
enumerable: true,
value: flags
}
})
this.#flagBits = parseInt(flags, 16)
}
get [Symbol.toStringTag]() {
return 'Traceparent'
}
/**
* Constructs a new instance from a header value.
*
* @param {string} header The traceparent header value to parse.
*
* @throws {Error} if any part of the header is invalid
*/
static fromHeader(header) {
if (typeof header !== 'string') {
throw Error('header value must be a string')
}
const parts = header.trim().split('-')
if (parts.length !== 4) {
throw Error(`traceparent header should have 4 parts, found ${parts.length}`)
}
const [version, traceId, parentId, flags] = parts
return new Traceparent({ version, traceId, parentId, flags })
}
/**
* Construct a new instance from an OTEL span context.
*
* @link https://opentelemetry.io/docs/concepts/signals/traces/#span-context
*
* @param {object} spanContext
*
* @returns {Traceparent}
*/
static fromSpanContext(spanContext) {
return new Traceparent({
traceId: spanContext.traceId,
parentId: spanContext.spanId,
// Our span context implementation does not implement the flags as a bit
// field. So we will just concatenate. See the span implementation in
// the OTEL bridge code.
flags: `0${Number(spanContext.traceFlags || 0)}`
})
}
/**
* Whether or not the header indicated the trace was sampled by the sending
* system.
*
* @returns {boolean}
*/
get isSampled() {
return (this.#flagBits & Traceparent.FLAG_SAMPLED) === 1
}
toString() {
return `${this.version}-${this.traceId}-${this.parentId}-${this.flags}`
}
#isValidFlags(flags) {
return /^[a-f0-9]{2}$/.test(flags)
}
#isValidParentId(id) {
if (id === invalidParentId) {
return false
}
return /^[a-f0-9]{16}$/.test(id)
}
#isValidTraceId(id) {
if (id === invalidTraceId) {
return false
}
return /^[a-f0-9]{32}$/.test(id)
}
// begin: accessors for cross agent tests
get trace_flags() {
return this.flags
}
get parent_id() {
return this.parentId
}
get trace_id() {
return this.traceId
}
// end: accessors for cross agent tests
}
module.exports = Traceparent