Docs » µAPM Instrumentation Guide » Instrumenting with Node.js

Instrumenting with Node.js

Important

Before you start instrumenting your applications, review the information in Instrumentation Overview.

Using the OpenTracing API

Adding tracing functionality to your Node.js project, like in some other platforms, generally consists of incorporating the initialization of a global tracer and using its interfaces for the creation and modification of spans to denote the runtime state of your application. The OpenTracing project has an API for JavaScript, and our suggested implementation is the Jaeger Tracer for Node.js. Using Jaeger’s initTracer(), a tracer can be easily configured to report spans to SignalFx:

const { initTracer } = require('jaeger-client');
const opentracing = require('opentracing');

const config = {
  serviceName: 'YourApplication',
  sampler: { type: 'const', param: 1 },
  reporter: {
    collectorEndpoint: 'https://ingest.signalfx.com/v1/trace',
    username: 'auth',
    password: '<MyAccessToken>',
    logSpans: true
  }
};
const options = { logger: console };

const tracer = initTracer(config, options);
opentracing.initGlobalTracer(tracer);

With this tracer instance, it’s possible to instrument your application code for automatic reporting to SignalFx.

function myApplicationFunction(key, value) {
  const span = opentracing.globalTracer().startSpan('myApplicationFunction');
  span.setTag('key', key);
  span.setTag('value', value);
  try {
    const computed = myHelperFunction(key, value);
    span.setTag('computed', JSON.stringify(computed));
  }
  catch (ex) {
    span.setTag(opentracing.Tags.ERROR, true);
    span.log({ event: 'error', message: ex.message });
    throw ex;
  }
  finally {
    span.finish();
  }
}

One helpful feature of OpenTracing’s global tracer and its interfaces is that noops are performed if a tracer instance is never registered. This way instrumented code is still functional outside of an active tracer’s context.

Distributed Traces in Node.js

To establish distributed traces over the remote request-response points of your application, we suggest using the B3 span context propagation codec. The following example demonstrates using both span context injection as a client and extraction as a server:

const http = require('http');
const { ZipkinB3TextMapCodec } = require('jaeger-client');

const codec = new ZipkinB3TextMapCodec({ urlEncoding: true });
tracer.registerInjector(opentracing.FORMAT_HTTP_HEADERS, codec);
tracer.registerExtractor(opentracing.FORMAT_HTTP_HEADERS, codec);

function myClientFunction() {
  const tracer = opentracing.globalTracer();
  const span = tracer.startSpan('POST to trusted service');

  // inject the current span context into the request's http headers
  // so that it will be a child of the root span in the trace
  const headers = {}
  tracer.inject(span, opentracing.FORMAT_HTTP_HEADERS, headers)
  const options = { method: 'POST', hostname: 'MyTrustedService', port: 8080, headers: headers }

  const req = http.request(options, (res) => {
    let body = ''
    res.on('data', (chunk) => {
      body += chunk;
    });
    res.on('end', () => {
      span.setTag('computed', body);
    });
  });

  req.on('error', (ex) => {
    span.setTag(opentracing.Tags.ERROR, true);
    span.log({ event: 'error', message: ex.message });
  });

  req.on('close', () => {
    span.finish();
  }),

  req.end();
}

function myRequestHandler(request, response) {
  const tracer = opentracing.globalTracer();
  // extract the client's span context from the request's http headers
  // to continue the trace
  const context = tracer.extract(opentracing.FORMAT_HTTP_HEADERS, request.headers);
  const span = tracer.startSpan('Compute from request', {childOf: context});
  response.end('Computed Value!')
  span.finish()
}

const server = http.createServer(myRequestHandler);
server.listen(8080, () => {
  console.log('Server is listening.')
});

For a working example of using the Jaeger tracer for reporting your traces to SignalFx, see our Node.js tracing example.