Skip to content

Latest commit

 

History

History
456 lines (352 loc) · 16.2 KB

API.md

File metadata and controls

456 lines (352 loc) · 16.2 KB

Datadog JavaScript Tracer API

This is the API documentation for the Datadog JavaScript Tracer. If you are just looking to get started, check out the tracing setup documentation.

Overview

The module exported by this library is an instance of the Tracer class.

Automatic Instrumentation

APM provides out-of-the-box instrumentation for many popular frameworks and libraries by using a plugin system. By default, all built-in plugins are enabled. Disabling plugins can cause unexpected side effects, so it is highly recommended to leave them enabled.

Built-in plugins can be configured individually:

const tracer = require('dd-trace').init()

// enable and configure postgresql integration
tracer.use('pg', {
  service: 'pg-cluster'
})

Available Plugins

Manual Instrumentation

If you aren’t using supported library instrumentation (see Compatibility), you may want to manually instrument your code.

This can be done using the tracer.trace() and the tracer.wrap() methods which handle the span lifecycle and scope management automatically. In some rare cases the scope needs to be handled manually as well in which case the tracer.scope() method is provided.

The different ways to use the above methods are described below.

tracer.trace(name[, options], callback)

This method allows you to trace a specific operation at the moment it is executed. It supports synchronous and asynchronous operations depending on how it's called.

Synchronous

To trace synchronously, simply call tracer.trace() without passing a function to the callback.

function handle (err) {
  tracer.trace('web.request', span => {
    // some code
  })
}

If an error is thrown in the callback, it will be automatically added to the span.

Callback

The most basic approach to trace asynchronous operations is to pass a function to the callback provided to the method.

function handle (err) {
  tracer.trace('web.request', (span, cb) => {
    // some code
    cb(err)
  })
}

Errors passed to the callback will automatically be added to the span.

Promise

For promises, the span will be finished after the promise has been either resolved or rejected.

function handle () {
  return tracer.trace('web.request', () => {
    return new Promise((resolve, reject) => {
      // some code
    })
  })
}

Any error from rejected promises will automatically be added to the span.

Async/await

For promises, the span lifecycle will be according to the returned promise.

async function handle () {
  return await tracer.trace('web.request', async () => {
    // some code
  })
}

Any error from the awaited handler will automatically be added to the span.

tracer.wrap(name[, options], fn)

This method works very similarly to tracer.trace() except it wraps a function so that tracer.trace() is called automatically every time the function is called. This makes it easier to patch entire functions that have already been defined, or that are returned from code that cannot be edited easily.

function handle () {
  // some code
}

const handleWithTrace = tracer.wrap('web.request', handle)

Similar to tracer.trace(), it handles synchronous calls, callbacks, promises and async/await. The only difference being that if the last argument of the wrapped function is a callback, the span will only be finished when that callback is called.

For example:

function handle (a, b, c, callback) {
  // some code
  callback()
}

const handleWithTrace = tracer.wrap('web.request', handle)

tracer.scope()

In order to provide context propagation, this library includes a scope manager available with tracer.scope(). A scope is basically a wrapper around a span that can cross both synchronous and asynchronous contexts.

In most cases, it's not necessary to interact with the scope manager since tracer.trace() activates the span on its scope, and uses the active span on the current scope if available as its parent. This should only be used directly for edge cases, like an internal queue of functions that are executed on a timer for example in which case the scope is lost.

The scope manager contains 3 APIs available on tracer.scope():

scope.active()

This method returns the active span from the current scope.

scope.activate(span, fn)

This method activates the provided span in a new scope available in the provided function. Any asynchronous context created from within that function will also have the same scope.

const tracer = require('dd-trace').init()
const scope = tracer.scope()
const log = console.log

const requestSpan = tracer.startSpan('web.request')
const promise = Promise.resolve()

scope.activate(requestSpan, () => {
  log(scope.active()) // requestSpan because in new scope

  someFunction() // requestSpan because called in scope

  setTimeout(() => {
    log(scope.active()) // requestSpan because setTimeout called in scope
  })

  promise.then(() => {
    log(scope.active()) // requestSpan because then() called in scope
  })
})

function someFunction () {
  log(scope.active())
}

log(scope.active()) // null

someFunction() // null because called outside the scope

scope.bind(target, [span])

This method binds a function to the specified span, or to the active span if unspecified.

When a span is provided, the function is always bound to that span. Explicitly passing null as the span will actually bind to null or no span. When a span is not provided, the function is bound to the span that is active when scope.bind(fn) is called.

Example
const tracer = require('dd-trace').init()
const scope = tracer.scope()
const log = console.log

const outerSpan = tracer.startSpan('web.request')

scope.activate(outerSpan, () => {
  const innerSpan = tracer.startSpan('web.middleware')

  const boundToInner = scope.bind(() => {
    log(scope.active())
  }, innerSpan)

  const boundToOuter = scope.bind(() => {
    log(scope.active())
  })

  boundToInner() // innerSpan because explicitly bound
  boundToOuter() // outerSpan because implicitly bound
})

See the API documentation for more details.

OpenTracing Compatibility

This library is OpenTracing compliant. Use the OpenTracing API and the Datadog Tracer (dd-trace) library to measure execution times for specific pieces of code. In the following example, a Datadog Tracer is initialized and used as a global tracer:

const tracer = require('dd-trace').init()
const opentracing = require('opentracing')

opentracing.initGlobalTracer(tracer)

The following tags are available to override Datadog-specific options:

  • service.name: The service name to be used for this span. The service name from the tracer will be used if this is not provided.
  • resource.name: The resource name to be used for this span. The operation name will be used if this is not provided.
  • span.type: The span type to be used for this span. Will fallback to custom if not provided.

OpenTelemetry Compatibility

This library is OpenTelemetry compliant. Use the OpenTelemetry API and the Datadog Tracer (dd-trace) library to measure execution times for specific pieces of code. In the following example, a Datadog TracerProvider is registered with @opentelemetry/api:

const tracer = require('dd-trace').init()

const tracerProvider = new tracer.TracerProvider()
tracerProvider.register()

The following attributes are available to override Datadog-specific options:

  • service.name: The service name to be used for this span. The service name from the tracer will be used if this is not provided.
  • resource.name: The resource name to be used for this span. The operation name will be used if this is not provided.
  • span.type: The span type to be used for this span. Will fallback to custom if not provided.

Advanced Configuration

Tracer settings

Options can be configured as a parameter to the init() method or as environment variables. These are documented over on Configuring the NodeJS Tracing Library.

Custom Logging

By default, logging from this library is disabled. In order to get debugging information and errors sent to logs, the DD_TRACE_DEBUG env var should be set to true.

The tracer will then log debug information to console.log() and errors to console.error(). This behavior can be changed by passing a custom logger to the tracer. The logger should contain a debug() and error() methods that can handle messages and errors, respectively.

For example:

const bunyan = require('bunyan')
const logger = bunyan.createLogger({
  name: 'dd-trace',
  level: 'trace'
})

process.env.DD_TRACE_DEBUG = 'true'

const tracer = require('dd-trace').init({
  logger: {
    error: err => logger.error(err),
    warn: message => logger.warn(message),
    info: message => logger.info(message),
    debug: message => logger.trace(message),
  }
})

Span Hooks

In some cases, it's necessary to update the metadata of a span created by one of the built-in integrations. This is possible using span hooks registered by integration. Each hook provides the span as the first argument and other contextual objects as additional arguments.

For example:

const tracer = require('dd-trace').init()

tracer.use('express', {
  hooks: {
    request: (span, req, res) => {
      span.setTag('customer.id', req.query.id)
    }
  }
})

Right now this functionality is limited to Web frameworks.

More information on which hooks are supported for each integration can be found in each individual plugins.

User Identification

The tracer provides a convenience function to link an actor to a trace. For example to correlate users to web requests. You have to pass an object with at least an id property.

For example:

const tracer = require('dd-trace').init()

function handle () {
  tracer.setUser({
    id: '123456789', // *REQUIRED* Unique identifier of the user.

    // All other fields are optional.
    email: 'jane.doe@example.com', // Email of the user.
    name: 'Jane Doe', // User-friendly name of the user.
    session_id: '987654321', // Session ID of the user.
    role: 'admin', // Role the user is making the request under.
    scope: 'read:message, write:files', // Scopes or granted authorizations the user currently possesses.

    // Arbitrary fields are also accepted to attach custom data to the user (RBAC, Oauth, etc…)
    custom_tag: 'custom data'
  })
}