dd-trace
Version:
Datadog APM tracing client for JavaScript
721 lines (645 loc) • 27.5 kB
JavaScript
'use strict'
const fs = require('node:fs')
const os = require('node:os')
const { URL } = require('node:url')
const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
const uuid = require('../../../../vendor/dist/crypto-randomuuid') // we need to keep the old uuid dep because of cypress
const set = require('../../../datadog-core/src/utils/src/set')
const { DD_MAJOR } = require('../../../../version')
const log = require('../log')
const pkg = require('../pkg')
const { isTrue } = require('../util')
const telemetry = require('../telemetry')
const telemetryMetrics = require('../telemetry/metrics')
const {
IS_SERVERLESS,
getIsGCPFunction,
getIsAzureFunction,
} = require('../serverless')
const { ORIGIN_KEY, DATADOG_MINI_AGENT_PATH } = require('../constants')
const { appendRules } = require('../payload-tagging/config')
const ConfigBase = require('./config-base')
const {
getEnvironmentVariable,
getEnvironmentVariables,
getStableConfigSources,
} = require('./helper')
const {
defaults,
fallbackConfigurations,
configurationsTable,
optionsTable,
configWithOrigin,
parseErrors,
generateTelemetry,
} = require('./defaults')
const { normalizeService } = require('./normalize-service')
const { transformers } = require('./parsers')
const RUNTIME_ID = uuid()
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
/**
* @typedef {'default'
* | 'code'
* | 'remote_config'
* | 'calculated'
* | 'env_var'
* | 'local_stable_config'
* | 'fleet_stable_config'} TelemetrySource
* @typedef {'remote_config' | 'calculated'} RevertibleTelemetrySource
* @typedef {import('../../../../index').TracerOptions} TracerOptions
* @typedef {import('./config-types').ConfigKey} ConfigKey
* @typedef {import('./config-types').ConfigPath} ConfigPath
* @typedef {{
* value: import('./config-types').ConfigPathValue<ConfigPath>,
* source: TelemetrySource
* }} TrackedConfigEntry
* @typedef {{
* baseValuesByPath: Partial<Record<ConfigPath, TrackedConfigEntry>>,
* remote_config: Set<ConfigPath>,
* calculated: Set<ConfigPath>,
* }} ChangeTracker
*/
/** @type {Config | null} */
let configInstance = null
// An entry that is undefined means it is the default value.
/** @type {Map<ConfigPath, TelemetrySource>} */
const trackedConfigOrigins = new Map()
// ChangeTracker tracks the changes to the config up to programmatic options (code).
/** @type {ChangeTracker} */
const changeTracker = {
baseValuesByPath: {},
remote_config: new Set(),
calculated: new Set(),
}
/**
* @param {Config} config
* @param {RevertibleTelemetrySource} source
*/
function undo (config, source) {
for (const name of changeTracker[source]) {
const entry = changeTracker.baseValuesByPath[name] ?? { source: 'default', value: defaults[name] }
setAndTrack(config, name, entry.value, undefined, entry.source)
}
}
function get (object, path) {
// Fast path for simple property access.
if (object[path] !== undefined) {
return object[path]
}
let index = 0
while (true) {
const nextIndex = path.indexOf('.', index)
if (nextIndex === -1) {
return object[path.slice(index)]
}
object = object[path.slice(index, nextIndex)]
index = nextIndex + 1
}
}
/**
* @param {Config} config
* @template {ConfigPath} TPath
* @param {TPath} name
* @param {import('./config-types').ConfigPathValue<TPath>} value
* @param {unknown} [rawValue]
* @param {TelemetrySource} [source]
*/
function setAndTrack (config, name, value, rawValue = value, source = 'calculated') {
// envs can not be undefined
if (value == null) {
// TODO: This works as before while ignoring undefined programmatic options is not ideal.
if (source !== 'default') {
return
}
} else if (source === 'calculated' || source === 'remote_config') {
if (source === 'calculated' && value === get(config, name)) {
return
}
changeTracker[source].add(name)
} else {
// Programmatic-only options (e.g. logger, lookup, plugins) have no row in
// `configurationsTable` and hold opaque user-supplied references that may
// carry cycles or non-plain prototypes — for example a winston Logger
// extends a Transform stream. Use a reference for these instead of cloning.
const copy = typeof value === 'object' && value !== null && name in configurationsTable
? rfdc(value)
: value
changeTracker.baseValuesByPath[name] = { value: copy, source }
}
set(config, name, value)
generateTelemetry(rawValue, source, name)
if (source === 'default') {
trackedConfigOrigins.delete(name)
} else {
trackedConfigOrigins.set(name, source)
}
}
module.exports = getConfig
// We extend from ConfigBase to make our types work
class Config extends ConfigBase {
/**
* parsed DD_TAGS, usable as a standalone tag set across products
* @type {Record<string, string>}
*/
#parsedDdTags
/**
* @type {Record<string, string>}
*/
get parsedDdTags () {
return this.#parsedDdTags
}
/**
* @param {TracerOptions} [options]
*/
constructor (options = {}) {
super()
const configEnvSources = getStableConfigSources()
this.stableConfig = {
fleetEntries: configEnvSources.fleetStableConfig ?? {},
localEntries: configEnvSources.localStableConfig ?? {},
warnings: configEnvSources.stableConfigWarnings,
}
// Configure the logger first so it can be used to warn about other configs
// TODO: Implement auto buffering of inside of log module before first
// configure call. That way the logger is always available and the
// application doesn't need to configure it first and the configuration
// happens inside of config instead of inside of log module. If the logger
// is not deactivated, the buffered logs would be discarded. That way stable
// config warnings can also be logged directly and do not need special
// handling.
this.debug = log.configure(options)
// Process stable config warnings, if any
for (const warning of this.stableConfig?.warnings ?? []) {
log.warn(warning)
}
this.#applyDefaults()
// TODO: Update origin documentation to list all valid sources. Add local_stable_config and fleet_stable_config.
this.#applyEnvs(getEnvironmentVariables(this.stableConfig.localEntries, true), 'local_stable_config')
this.#applyEnvs(getEnvironmentVariables(undefined, true), 'env_var')
this.#applyEnvs(getEnvironmentVariables(this.stableConfig.fleetEntries, true), 'fleet_stable_config')
// Experimental options are applied first, so they can be overridden by non-experimental options.
// TODO: When using programmatic options, check if there is a higher
// priority name in the same options object. Use the highest priority name.
const { experimental, ...rest } = options
if (experimental) {
// @ts-expect-error - Difficult to type this correctly.
this.#applyOptions(experimental, 'code', 'experimental')
}
this.#applyOptions(rest, 'code')
this.#applyCalculated()
warnWrongOtelSettings()
parseErrors.clear()
}
#applyDefaults () {
for (const [name, value] of Object.entries(defaults)) {
set(this, name, value)
}
}
/**
* @param {import('./helper').TracerEnv} envs
* @param {'env_var' | 'local_stable_config' | 'fleet_stable_config'} source
*/
#applyEnvs (envs, source) {
for (const [name, value] of Object.entries(envs)) {
const entry = configurationsTable[name]
const parsed = entry.parser(value, name, source)
const transformed = parsed !== undefined && entry.transformer ? entry.transformer(parsed, name, source) : parsed
const rawValue = transformed !== null && typeof transformed === 'object' ? value : parsed
setAndTrack(this, entry.property ?? name, transformed, rawValue, source)
}
}
/**
* @param {TracerOptions} options
* @param {'code' | 'remote_config'} source
* @param {string} [root]
*/
#applyOptions (options, source, root = '') {
for (const [name, value] of Object.entries(options)) {
const fullName = root ? `${root}.${name}` : name
let entry = optionsTable[fullName]
if (!entry) {
// TODO: Fix this by by changing remote config to use env styles.
if (name !== 'tracing' || source !== 'remote_config') {
log.warn('Unknown option %s with value %o', fullName, value)
continue
}
// @ts-expect-error - The entry is defined in the configurationsTable.
entry = configurationsTable.tracing
}
if (entry.nestedProperties) {
let matched = false
if (typeof value === 'object' && value !== null) {
for (const nestedProperty of entry.nestedProperties) {
// WARNING: if the property name might be part of the value we look at, this could conflict!
// Defining an option that receives an object as value may not contain a property that is also
// potentially a nested property!
if (Object.hasOwn(value, nestedProperty)) {
this.#applyOptions(value, source, fullName)
matched = true
break
}
}
}
if (matched) {
continue
}
if (entry.option) {
entry = entry.option
} else {
if (fullName === 'tracePropagationStyle') {
// TracePropagationStyle is special. It is a single option that is used to set both inject and extract.
// TODO: Consider what to do with this later
// @ts-expect-error - Difficult to type this correctly.
this.#applyOptions({ inject: value, extract: value }, source, 'tracePropagationStyle')
} else {
log.warn('Unknown option %s with value %o', fullName, value)
}
continue
}
}
// TODO: Coerce mismatched types to the expected type, if possible. E.g., strings <> numbers
const transformed = value !== undefined && entry.transformer ? entry.transformer(value, fullName, source) : value
setAndTrack(this, entry.property, transformed, value, source)
}
}
/**
* Set the configuration with remote config settings.
* Applies remote configuration, recalculates derived values, and merges all configuration sources.
*
* @param {TracerOptions|null} options - Configurations received via Remote
* Config or null to reset all remote configuration
*/
setRemoteConfig (options) {
// Clear all RC-managed fields to ensure previous values don't persist.
// State is instead managed by the `RCClientLibConfigManager` class
undo(this, 'remote_config')
// Special case: if options is null, nothing to apply
// This happens when all remote configs are removed
if (options !== null) {
this.#applyOptions(options, 'remote_config')
}
this.#applyCalculated()
}
/**
* @param {ConfigPath} name
*/
getOrigin (name) {
return trackedConfigOrigins.get(name) ?? 'default'
}
// Handles values calculated from a mixture of options and env vars
#applyCalculated () {
undo(this, 'calculated')
if (this.DD_CIVISIBILITY_AGENTLESS_URL ||
this.url ||
os.type() !== 'Windows_NT' &&
!trackedConfigOrigins.has('hostname') &&
!trackedConfigOrigins.has('port') &&
!this.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
fs.existsSync('/var/run/datadog/apm.socket')) {
setAndTrack(
this,
'url',
new URL(this.DD_CIVISIBILITY_AGENTLESS_URL || this.url || 'unix:///var/run/datadog/apm.socket')
)
}
if (this.isCiVisibility) {
setAndTrack(this, 'isServiceUserProvided', trackedConfigOrigins.has('service'))
this.tags[ORIGIN_KEY] = 'ciapp-test'
}
// Compute OTLP logs and metrics URLs to send payloads to the active Datadog Agent
const agentHostname = this.hostname || /** @type {URL} */ (this.url).hostname
if (!trackedConfigOrigins.has('dogstatsd.hostname')) {
setAndTrack(this, 'dogstatsd.hostname', agentHostname)
}
// Disable log injection when OTEL logs are enabled
// OTEL logs and DD log injection are mutually exclusive
if (this.DD_LOGS_OTEL_ENABLED) {
setAndTrack(this, 'logInjection', false)
}
if (this.DD_METRICS_OTEL_ENABLED &&
trackedConfigOrigins.has('OTEL_METRICS_EXPORTER') &&
this.OTEL_METRICS_EXPORTER === 'none') {
setAndTrack(this, 'DD_METRICS_OTEL_ENABLED', false)
}
if (this.OTEL_TRACES_EXPORTER === 'otlp' && trackedConfigOrigins.has('protocolVersion')) {
log.warn('DD_TRACE_AGENT_PROTOCOL_VERSION is set, disabling OTLP traces export')
setAndTrack(this, 'OTEL_TRACES_EXPORTER', 'none')
}
if (this.telemetry.heartbeatInterval) {
setAndTrack(this, 'telemetry.heartbeatInterval', Math.floor(this.telemetry.heartbeatInterval * 1000))
}
if (this.telemetry.extendedHeartbeatInterval) {
setAndTrack(this, 'telemetry.extendedHeartbeatInterval',
Math.floor(this.telemetry.extendedHeartbeatInterval * 1000))
}
// Enable resourceRenamingEnabled when appsec is enabled and only
// if DD_TRACE_RESOURCE_RENAMING_ENABLED is not explicitly set
if (!trackedConfigOrigins.has('resourceRenamingEnabled')) {
setAndTrack(this, 'resourceRenamingEnabled', this.appsec.enabled ?? false)
}
if (!trackedConfigOrigins.has('spanComputePeerService') && this.spanAttributeSchema !== 'v0') {
setAndTrack(this, 'spanComputePeerService', true)
}
if (!this.apmTracingEnabled) {
setAndTrack(this, 'stats.enabled', false)
} else if (!trackedConfigOrigins.has('stats.enabled')) {
setAndTrack(this, 'stats.enabled', getIsGCPFunction() || getIsAzureFunction())
}
// TODO: Remove the experimental env vars as a major or deprecate the option?
if (this.experimental?.b3) {
if (!this.tracePropagationStyle.inject.includes('b3')) {
this.tracePropagationStyle.inject.push('b3')
}
if (!this.tracePropagationStyle.extract.includes('b3')) {
this.tracePropagationStyle.extract.push('b3')
}
if (!this.tracePropagationStyle.inject.includes('b3 single header')) {
this.tracePropagationStyle.inject.push('b3 single header')
}
if (!this.tracePropagationStyle.extract.includes('b3 single header')) {
this.tracePropagationStyle.extract.push('b3 single header')
}
setAndTrack(this, 'tracePropagationStyle.inject', this.tracePropagationStyle.inject)
setAndTrack(this, 'tracePropagationStyle.extract', this.tracePropagationStyle.extract)
}
if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') && !fs.existsSync(DATADOG_MINI_AGENT_PATH)) {
setAndTrack(this, 'flushInterval', 0)
}
if (!trackedConfigOrigins.has('apmTracingEnabled') &&
trackedConfigOrigins.has('experimental.appsec.standalone.enabled')) {
setAndTrack(this, 'apmTracingEnabled', !this.experimental.appsec.standalone.enabled)
}
if (this.cloudPayloadTagging?.request || this.cloudPayloadTagging?.response) {
setAndTrack(this, 'cloudPayloadTagging.rules', appendRules(
this.cloudPayloadTagging.request,
this.cloudPayloadTagging.response
))
}
if (this.DD_INJECTION_ENABLED) {
setAndTrack(this, 'instrumentationSource', 'ssi')
}
if (!trackedConfigOrigins.has('runtimeMetrics.enabled') && this.OTEL_METRICS_EXPORTER === 'none') {
setAndTrack(this, 'runtimeMetrics.enabled', false)
}
// Apply the OTel sampler when the user opted into OTel traces or explicitly set the sampler.
// OTEL_TRACES_SAMPLER has `default: parentbased_always_on` (per OTel spec), so opt-in users
// that don't set the sampler still get parent-based sampling.
if (!trackedConfigOrigins.has('sampleRate') &&
(trackedConfigOrigins.has('OTEL_TRACES_SAMPLER') || this.OTEL_TRACES_EXPORTER === 'otlp')) {
setAndTrack(this, 'sampleRate',
getFromOtelSamplerMap(this.OTEL_TRACES_SAMPLER, this.OTEL_TRACES_SAMPLER_ARG))
}
if (this.DD_SPAN_SAMPLING_RULES_FILE) {
try {
// TODO: Should we log a warning in case this is defined next to spanSamplingRules?
setAndTrack(this, 'spanSamplingRules', transformers.toCamelCase(JSON.parse(this.DD_SPAN_SAMPLING_RULES_FILE)))
} catch (error) {
log.warn('Error reading span sampling rules file %s; %o', this.DD_SPAN_SAMPLING_RULES_FILE, error)
}
}
// All sampler options are tracked as individual values. No need to track the sampler object as a whole.
this.sampler = {
rules: this.samplingRules,
rateLimit: this.rateLimit,
sampleRate: this.sampleRate,
spanSamplingRules: this.spanSamplingRules,
}
// For LLMObs, we want to auto enable it when other llmobs options are defined.
if (!this.llmobs.enabled &&
!trackedConfigOrigins.has('llmobs.enabled') &&
(trackedConfigOrigins.has('llmobs.agentlessEnabled') ||
trackedConfigOrigins.has('llmobs.mlApp'))) {
setAndTrack(this, 'llmobs.enabled', true)
}
if (this.OTEL_RESOURCE_ATTRIBUTES) {
for (const [key, value] of Object.entries(this.OTEL_RESOURCE_ATTRIBUTES)) {
// Not replacing existing tags keeps the order of the tags as before.
if (!this.tags[key]) {
this.tags[key] = value
}
}
}
if (this.DD_TRACE_TAGS) {
// TODO: This is a hack to keep the order of the tags as before.
// That hack is not sufficient, since it does not handle other cases where the tags are set by the user.
if (trackedConfigOrigins.get('tags') === 'code') {
for (const [key, value] of Object.entries(this.DD_TRACE_TAGS)) {
// Not replacing existing tags keeps the order of the tags as before.
if (!this.tags[key]) {
this.tags[key] = value
}
}
} else {
Object.assign(this.tags, this.DD_TRACE_TAGS)
}
}
if (!this.#parsedDdTags) {
this.#parsedDdTags = rfdc(this.tags)
}
if (!this.env && this.tags.env !== undefined) {
setAndTrack(this, 'env', this.tags.env)
}
if (!this.version) {
setAndTrack(this, 'version', this.tags.version || pkg.version)
this.tags.version ??= pkg.version
}
let isServiceNameInferred = false
if (!trackedConfigOrigins.has('service')) {
if (this.tags.service) {
setAndTrack(this, 'service', this.tags.service)
} else {
const NX_TASK_TARGET_PROJECT = getEnvironmentVariable('NX_TASK_TARGET_PROJECT')
if (NX_TASK_TARGET_PROJECT) {
if (DD_MAJOR >= 6 || this.DD_ENABLE_NX_SERVICE_NAME) {
setAndTrack(this, 'service', normalizeService(NX_TASK_TARGET_PROJECT) || 'node')
isServiceNameInferred = true
} else if (DD_MAJOR < 6) {
log.warn(
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
'NX_TASK_TARGET_PROJECT is set but no service name was configured. In v6, NX_TASK_TARGET_PROJECT will ' +
'be used as the default service name. Set DD_ENABLE_NX_SERVICE_NAME=true to opt-in to this behavior ' +
'now, or set a service name explicitly.'
)
}
}
}
if (!this.service) {
const serverlessName = IS_SERVERLESS
? (
getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') ||
getEnvironmentVariable('FUNCTION_NAME') || // Google Cloud Function Name set by deprecated runtimes
getEnvironmentVariable('K_SERVICE') || // Google Cloud Function Name set by newer runtimes
getEnvironmentVariable('WEBSITE_SITE_NAME') // set by Azure Functions
)
: undefined
setAndTrack(this, 'service', normalizeService(serverlessName) || normalizeService(pkg.name) || 'node')
this.tags.service ??= /** @type {string} */ (this.service)
isServiceNameInferred = true
}
}
// This should not be tracked.
// TODO: Consider moving this outside of the config.
set(this, 'isServiceNameInferred', isServiceNameInferred)
// Add missing tags, in case they are defined otherwise.
if (this.service) {
this.tags.service = this.service
}
if (this.env) {
this.tags.env = this.env
}
if (this.version) {
this.tags.version = this.version
}
this.tags['runtime-id'] = RUNTIME_ID
if (IS_SERVERLESS) {
setAndTrack(this, 'telemetry.enabled', false)
setAndTrack(this, 'DD_CRASHTRACKING_ENABLED', false)
setAndTrack(this, 'remoteConfig.enabled', false)
}
// TODO: Should this unconditionally be disabled?
if (getEnvironmentVariable('JEST_WORKER_ID') && !trackedConfigOrigins.has('telemetry.enabled')) {
setAndTrack(this, 'telemetry.enabled', false)
}
// Experimental agentless APM span intake
// When enabled, sends spans directly to Datadog intake without an agent
// TODO: Replace this with a proper configuration
const agentlessEnabled = isTrue(getEnvironmentVariable('_DD_APM_TRACING_AGENTLESS_ENABLED'))
if (agentlessEnabled) {
setAndTrack(this, 'experimental.exporter', 'agentless')
// Disable client-side stats computation
setAndTrack(this, 'stats.enabled', false)
// Enable hostname reporting
setAndTrack(this, 'reportHostname', true)
// Disable rate limiting - server-side sampling will be used
setAndTrack(this, 'sampler.rateLimit', -1)
// Clear sampling rules - server-side sampling handles this
setAndTrack(this, 'sampler.rules', [])
// Agentless intake only accepts 64-bit trace IDs; disable 128-bit generation
if (!trackedConfigOrigins.has('traceId128BitGenerationEnabled')) {
setAndTrack(this, 'traceId128BitGenerationEnabled', false)
}
}
// Apply all fallbacks to the calculated config.
for (const [configName, alias] of fallbackConfigurations) {
if (!trackedConfigOrigins.has(configName) && trackedConfigOrigins.has(alias)) {
setAndTrack(this, configName, this[alias])
}
}
// TODO: This could likely be moved to the base class and allow easier GRPC handling
// Default OTLP endpoints follow the configured agent host so users who point DD at a custom
// agent (DD_AGENT_HOST / DD_TRACE_AGENT_URL) also reach OTLP on that host.
const defaultOtlpBase = this.OTEL_EXPORTER_OTLP_ENDPOINT?.replace(/\/$/, '') ?? `http://${agentHostname}:4318`
if (!this.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
setAndTrack(this, 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT', `${defaultOtlpBase}/v1/logs`)
}
if (!this.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) {
setAndTrack(this, 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', `${defaultOtlpBase}/v1/metrics`)
}
if (!this.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) {
setAndTrack(this, 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', `${defaultOtlpBase}/v1/traces`)
}
if (process.platform === 'win32') {
// OOM monitoring does not work properly on Windows, so it will be disabled.
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED')
// Profiler sampling contexts are not available on Windows, so features
// depending on those (code hotspots and endpoint collection) need to be disabled on Windows.
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_CODEHOTSPOTS_ENABLED')
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED')
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_CPU_ENABLED')
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_TIMELINE_ENABLED')
deactivateIfEnabledAndWarnOnWindows(this, 'DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED')
}
// Single tags update is tracked as a calculated value.
setAndTrack(this, 'tags', this.tags)
telemetry.updateConfig([...configWithOrigin.values()], this)
}
}
/**
* @param {Config} config
* @param {ConfigKey} envVar
*/
function deactivateIfEnabledAndWarnOnWindows (config, envVar) {
if (config[envVar]) {
const source = trackedConfigOrigins.get(envVar)
setAndTrack(config, envVar, false)
// TODO: Should we log even for default values?
if (source) {
log.warn('%s is not supported on Windows. Deactivating. (source: %s)', envVar, source)
}
}
}
function increaseCounter (event, ddVar, otelVar) {
const tags = []
if (ddVar) {
tags.push(`config_datadog:${ddVar.toLowerCase()}`)
}
tags.push(`config_opentelemetry:${otelVar.toLowerCase()}`)
tracerMetrics.count(event, tags).inc()
}
function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
const NON_PARENTBASED_TO_PARENTBASED = {
always_on: 'parentbased_always_on',
always_off: 'parentbased_always_off',
traceidratio: 'parentbased_traceidratio',
}
const OTEL_TRACES_SAMPLER_MAPPING = {
parentbased_always_on: 1,
parentbased_always_off: 0,
}
const parentBasedEquivalent = NON_PARENTBASED_TO_PARENTBASED[otelTracesSampler]
if (parentBasedEquivalent) {
log.info(
'OTEL_TRACES_SAMPLER=%s does not respect upstream sampling decisions; using parent-based equivalent %s instead',
otelTracesSampler, parentBasedEquivalent
)
otelTracesSampler = parentBasedEquivalent
}
const result = OTEL_TRACES_SAMPLER_MAPPING[otelTracesSampler] ?? otelTracesSamplerArg
if (result === undefined) {
increaseCounter('otel.env.invalid', 'DD_TRACE_SAMPLE_RATE', 'OTEL_TRACES_SAMPLER')
}
return result
}
function warnWrongOtelSettings () {
// This mostly works for non-aliased environment variables only.
// TODO: Adjust this to work across all sources.
for (const [otelEnvVar, ddEnvVar, key] of [
// eslint-disable-next-line eslint-rules/eslint-env-aliases
['OTEL_LOG_LEVEL', 'DD_TRACE_LOG_LEVEL', 'logLevel'],
// eslint-disable-next-line eslint-rules/eslint-env-aliases
['OTEL_PROPAGATORS', 'DD_TRACE_PROPAGATION_STYLE', 'DD_TRACE_PROPAGATION_STYLE'],
// eslint-disable-next-line eslint-rules/eslint-env-aliases
['OTEL_SERVICE_NAME', 'DD_SERVICE', 'service'],
['OTEL_TRACES_SAMPLER', 'DD_TRACE_SAMPLE_RATE'],
['OTEL_TRACES_SAMPLER_ARG', 'DD_TRACE_SAMPLE_RATE'],
['OTEL_TRACES_EXPORTER', 'DD_TRACE_ENABLED'],
['OTEL_METRICS_EXPORTER', 'DD_RUNTIME_METRICS_ENABLED'],
['OTEL_RESOURCE_ATTRIBUTES', 'DD_TAGS'],
['OTEL_SDK_DISABLED', 'DD_TRACE_OTEL_ENABLED'],
['OTEL_LOGS_EXPORTER'],
]) {
// eslint-disable-next-line eslint-rules/eslint-process-env
const envs = process.env
const otelSource = trackedConfigOrigins.get(/** @type {ConfigPath} */ (key ?? otelEnvVar))
const otelEnvValue = envs[otelEnvVar]
if (otelEnvValue) {
if (envs[ddEnvVar]) {
log.warn('Conflicting %s and %s environment variables are set for %s', ddEnvVar, otelEnvVar, otelSource)
increaseCounter('otel.env.hiding', ddEnvVar, otelEnvVar)
}
const invalidOtelValue = !otelSource
if (invalidOtelValue) {
increaseCounter('otel.env.invalid', ddEnvVar, otelEnvVar)
}
}
}
}
/**
* @param {TracerOptions} [options]
*/
function getConfig (options) {
if (!configInstance) {
configInstance = new Config(options)
}
return configInstance
}