dd-trace
Version:
Datadog APM tracing client for JavaScript
242 lines (209 loc) • 6.91 kB
JavaScript
'use strict'
const { storage } = require('../../../datadog-core')
const analyticsSampler = require('../analytics_sampler')
const { COMPONENT, SVC_SRC_KEY } = require('../constants')
const Plugin = require('./plugin')
const legacyStorage = storage('legacy')
class TracingPlugin extends Plugin {
constructor (...args) {
super(...args)
this.component = this.constructor.component || this.constructor.id
this.operation = this.constructor.operation
this.addTraceSubs()
}
get activeSpan () {
const store = /** @type {{ span?: import('../../../..').Span }} */ (legacyStorage.getStore())
return store?.span
}
/**
* @param {object} opts
* @param {string} [opts.type]
* @param {string} [opts.id]
* @param {string} [opts.kind]
* @returns {{ name: string, source: string | undefined }}
*/
serviceName (opts = {}) {
const {
type = this.constructor.type,
id = this.constructor.id,
kind = this.constructor.kind,
} = opts
return this._tracer._nomenclature.serviceName(type, kind, id, opts)
}
/**
* @param {object} opts
* @param {string} [opts.type]
* @param {string} [opts.id]
* @param {string} [opts.kind]
* @returns {string}
*/
operationName (opts = {}) {
const {
type = this.constructor.type,
id = this.constructor.id,
kind = this.constructor.kind,
} = opts
return this._tracer._nomenclature.opName(type, kind, id, opts)
}
/**
* @param {object} config
* @returns {object}
*/
configure (config) {
return super.configure({
...config,
hooks: {
[this.operation]: () => {},
...config.hooks,
},
})
}
start () {} // implemented by individual plugins
/**
* @param {{ currentStore?: { span: import('../../../..').Span } }} ctx
*/
finish (ctx) {
const span = ctx?.currentStore?.span || this.activeSpan
span?.finish()
}
/**
* @param {{ currentStore?: { span: import('../../../..').Span }, error?: unknown }} ctxOrError
*/
error (ctxOrError) {
if (ctxOrError?.currentStore) {
ctxOrError.currentStore?.span.setTag('error', ctxOrError?.error)
return
}
this.addError(ctxOrError)
}
addTraceSubs () {
const events = ['start', 'end', 'asyncStart', 'asyncEnd', 'error', 'finish']
for (const event of events) {
const bindName = `bind${event.charAt(0).toUpperCase()}${event.slice(1)}`
if (this[event]) {
this.addTraceSub(event, message => {
this[event](message)
})
}
if (this[bindName]) {
this.addTraceBind(event, message => this[bindName](message))
}
}
}
/**
* @param {string} eventName
* @param {Function} handler
*/
addTraceSub (eventName, handler) {
const prefix = this.constructor.prefix || `apm:${this.component}:${this.operation}`
this.addSub(`${prefix}:${eventName}`, handler)
}
/**
* @param {string} eventName
* @param {Function} transform
*/
addTraceBind (eventName, transform) {
const prefix = this.constructor.prefix || `apm:${this.component}:${this.operation}`
this.addBind(`${prefix}:${eventName}`, transform)
}
/**
* @param {unknown} error
* @param {import('../../../..').Span} [span]
*/
addError (error, span = this.activeSpan) {
if (span && !span._spanContext._tags.error) {
// Errors may be wrapped in a context.
span.setTag('error', error?.error || error || 1)
}
}
/**
* Start a new span.
*
* Important: `childOf` can be `null` to indicate that the span is a root span.
* This is useful for plugins that need to start a span without a parent, such
* as the root span of a serverless function.
*
* @example
* const span = this.startSpan('my.span', {
* childOf: null,
* })
*
* @param {string} name - The name of the span.
* @param {object} [options] - The options for the span.
* @param {string} [options.component] - The component of the span.
* @param {import('../opentracing/span') | null} [options.childOf] - The parent span.
* @param {string} [options.integrationName] - The integration name.
* @param {string} [options.kind] - The kind of the span.
* @param {object} [options.meta] - The meta data for the span.
* @param {object} [options.metrics] - The metrics for the span.
* @param {string | { name: string, source?: string }} [options.service] - The service name, or an object with
* name and source.
* @param {number} [options.startTime] - The start time of the span.
* @param {string} [options.resource] - The resource name.
* @param {string} [options.type] - The type of the span.
* @param {import('../tracer')} [options.tracer] - The tracer.
* @param {object} [options.config] - The config for the span.
*
* @param {boolean} enterOrCtx - Whether to enter the span context into the storage.
*/
startSpan (name, options = {}, enterOrCtx = true) {
// TODO: modularize this code to a helper function
let {
component = this.component,
childOf,
integrationName,
kind,
meta,
metrics,
service,
startTime,
resource,
type,
} = options
let serviceSource
const tracer = options.tracer || this.tracer
const config = options.config || this.config
if (service && typeof service === 'object') {
serviceSource = service.source
service = service.name
} else if (service !== undefined) {
// service is a plain value returned by service naming/config logic
serviceSource = service ? 'opt.plugin' : undefined
}
const store = legacyStorage.getStore()
if (store && childOf === undefined) {
childOf = /** @type {import('../opentracing/span') | undefined} */ (store.span)
}
// clear service source if service is the same as tracer._service
const serviceName = service || meta?.service
if (!serviceName || serviceName === tracer._service) {
serviceSource = undefined
}
const span = tracer.startSpan(name, {
startTime,
childOf,
tags: {
[COMPONENT]: component,
'service.name': serviceName || tracer._service,
'resource.name': resource,
'span.kind': kind,
'span.type': type,
...(serviceSource === undefined ? undefined : { [SVC_SRC_KEY]: serviceSource }),
...meta,
...metrics,
},
integrationName: integrationName || component,
links: childOf?._links,
})
analyticsSampler.sample(span, config.measured)
// TODO: Remove this after migration to TracingChannel is done.
if (enterOrCtx === true) {
legacyStorage.enterWith({ ...store, span })
} else if (enterOrCtx) {
enterOrCtx.parentStore = store
enterOrCtx.currentStore = { ...store, span }
}
return span
}
}
module.exports = TracingPlugin