dd-trace
Version:
Datadog APM tracing client for JavaScript
192 lines (154 loc) • 5.39 kB
JavaScript
'use strict'
const { channel } = require('dc-polyfill')
const { isFalse, isTrue, normalizePluginEnvName } = require('./util')
const plugins = require('./plugins')
const log = require('./log')
const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
const loadChannel = channel('dd-trace:instrumentation:load')
// instrument everything that needs Plugin System V2 instrumentation
require('../../datadog-instrumentations')
if (getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined) {
// instrument lambda environment
require('./lambda')
}
const DD_TRACE_DISABLED_PLUGINS = getEnvironmentVariable('DD_TRACE_DISABLED_PLUGINS')
const disabledPlugins = new Set(
DD_TRACE_DISABLED_PLUGINS && DD_TRACE_DISABLED_PLUGINS.split(',').map(plugin => plugin.trim())
)
// TODO actually ... should we be looking at environment variables this deep down in the code?
const pluginClasses = {}
loadChannel.subscribe(({ name }) => {
maybeEnable(plugins[name])
})
function maybeEnable (Plugin) {
if (!Plugin || typeof Plugin !== 'function') return
if (!pluginClasses[Plugin.id]) {
const enabled = getEnvEnabled(Plugin)
// TODO: remove the need to load the plugin class in order to disable the plugin
if (isFalse(enabled) || disabledPlugins.has(Plugin.id)) {
log.debug('Plugin "%s" was disabled via configuration option.', Plugin.id)
pluginClasses[Plugin.id] = null
} else {
pluginClasses[Plugin.id] = Plugin
}
}
}
function getEnvEnabled (Plugin) {
const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED`
return getEnvironmentVariable(normalizePluginEnvName(envName))
}
// TODO this must always be a singleton.
module.exports = class PluginManager {
constructor (tracer) {
this._tracer = tracer
this._tracerConfig = null
this._pluginsByName = {}
this._configsByName = {}
this._loadedSubscriber = ({ name }) => {
const Plugin = plugins[name]
if (!Plugin || typeof Plugin !== 'function') return
this.loadPlugin(Plugin.id)
}
loadChannel.subscribe(this._loadedSubscriber)
}
loadPlugin (name) {
const Plugin = pluginClasses[name]
if (!Plugin) return
if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
if (!this._pluginsByName[name]) {
this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
}
const pluginConfig = this._configsByName[name] || {
enabled: this._tracerConfig.plugins !== false && (!Plugin.experimental || isTrue(getEnvEnabled(Plugin)))
}
// extracts predetermined configuration from tracer and combines it with plugin-specific config
this._pluginsByName[name].configure({
...this._getSharedConfig(name),
...pluginConfig
})
}
// TODO: merge config instead of replacing
configurePlugin (name, pluginConfig) {
const enabled = this._isEnabled(pluginConfig)
this._configsByName[name] = {
...pluginConfig,
enabled
}
this.loadPlugin(name)
}
// like instrumenter.enable()
configure (config = {}) {
this._tracerConfig = config
this._tracer._nomenclature.configure(config)
for (const name in pluginClasses) {
this.loadPlugin(name)
}
}
// This is basically just for testing. like intrumenter.disable()
destroy () {
for (const name in this._pluginsByName) {
this._pluginsByName[name].configure({ enabled: false })
}
loadChannel.unsubscribe(this._loadedSubscriber)
}
_isEnabled (pluginConfig) {
if (typeof pluginConfig === 'boolean') return pluginConfig
if (!pluginConfig) return true
return pluginConfig.enabled !== false
}
// TODO: figure out a better way to handle this
_getSharedConfig (name) {
const {
logInjection,
serviceMapping,
queryStringObfuscation,
site,
url,
headerTags,
codeOriginForSpans,
dbmPropagationMode,
dsmEnabled,
clientIpEnabled,
clientIpHeader,
memcachedCommandEnabled,
ciVisibilityTestSessionName,
ciVisAgentlessLogSubmissionEnabled,
isTestDynamicInstrumentationEnabled,
isServiceUserProvided,
middlewareTracingEnabled
} = this._tracerConfig
const sharedConfig = {
codeOriginForSpans,
dbmPropagationMode,
dsmEnabled,
memcachedCommandEnabled,
site,
url,
headers: headerTags || [],
clientIpHeader,
ciVisibilityTestSessionName,
ciVisAgentlessLogSubmissionEnabled,
isTestDynamicInstrumentationEnabled,
isServiceUserProvided
}
if (logInjection !== undefined) {
sharedConfig.logInjection = logInjection
}
if (queryStringObfuscation !== undefined) {
sharedConfig.queryStringObfuscation = queryStringObfuscation
}
if (serviceMapping && serviceMapping[name]) {
sharedConfig.service = serviceMapping[name]
}
if (clientIpEnabled !== undefined) {
sharedConfig.clientIpEnabled = clientIpEnabled
}
// For the global setting, we use the name `middlewareTracingEnabled`, but
// for the plugin-specific setting, we use `middleware`. They mean the same
// to an individual plugin, so we normalize them here.
if (middlewareTracingEnabled !== undefined) {
sharedConfig.middleware = middlewareTracingEnabled
}
return sharedConfig
}
}