Skip to content
Enables custom tracing of Node.js applications in Dynatrace
Branch: master
Clone or download

README.md

Disclaimer: This SDK is currently in beta and still work in progress.

Dynatrace OneAgent SDK for Node.js

This SDK allows Dynatrace customers to instrument Node.js applications. This is useful to add service level insight for modules currently not directly supported by Dynatrace OneAgent out-of-the-box.

This is the official Node.js implementation of the Dynatrace OneAgent SDK.

Table of Contents

Package contents

  • samples: sample applications, which demonstrates the usage of the SDK. See readme inside the samples directory for more details
  • src: source code of the SDK
  • test: unit tests
  • LICENSE: license under which the whole SDK and sample applications are published

Requirements

  • When loading the OneAgent via OneAgent NPM module or a similar tool, make sure to require the SDK after the OneAgent
  • Dynatrace OneAgent (required versions see below)
OneAgent SDK for Node.js Required OneAgent version
1.3.x >=1.165
1.2.x >=1.145
1.1.x >=1.143
1.0.x >=1.137

Integration

Using this module should not cause any errors if no OneAgent is present (e.g. in testing).

Make sure that this module is required after Dynatrace OneAgent.

Installation

npm install --save @dynatrace/oneagent-sdk

Troubleshooting

If the SDK can't connect to the OneAgent (Current SDK state is not ACTIVE) verify that a matching version of OneAgent is used and required before the SDK module.

Verify that OneAgent is working as intended (see Dynatrace OneAgent).

You should ensure that you have set LoggingCallbacks.

OneAgent transparently wrap supported libraries to add context information. For every yet unsupported module Pass Context can be used to provide transactional context to callbacks.

API Concepts

Common concepts of the Dynatrace OneAgent SDK are explained the Dynatrace OneAgent SDK repository.

OneAgentSDK object

The first step is to acquire an OneAgent SDK API object by calling createInstance(). The resulting object holds methods to create tracers, administrative methods (e.g. check SDK state, install logging callbacks) and passContext(). Every call to createInstance() will return a new API object allowing the user to install separate logging callbacks for separate use cases.

const Sdk = require('@dynatrace/oneagent-sdk');
const Api = Sdk.createInstance();

Tracers

The life-cycle of a tracer is as follows:

  1. Create a trace using the traceXXX method matching to your use case. For incoming taggable traces pass the received Dynatrace tag (if present) to the traceXXX method.
  2. Start the trace which in turn invokes and times the given handler function.
  3. For outgoing taggable traces fetch a Dynatrace tag and include it to the message sent out.
  4. Optional mark the traced operation as failed via a call to error()
  5. End the trace once the operation is done. For outgoing traces you may pass a callback to be included in this trace.

Each tracer offers following methods:

  • start(handler, ...args) Start a trace and calls the given handler function with the provides arguments. Returns the return value of handler.
  • startWithContext(handler, thisObj, ...args) Like start() but allows to specify the this context for the call to handler.
  • error(err) Mark trace as failed and attach an error object. Shall be call at max once per trace. Does not end the trace! Returns the tracer itself.
  • end() End the trace.

Tracers for outgoing requests additionally offer enhanced methods to end a trace which allow to include the follow-up callback to the PurePath of this trace.

  • end(callback, ...args) End the trace like end() but additionally calls the passed callback with given arguments. The return value from callback is forwarded to caller of end.
  • endWithContext(callback, thisObj, ...args) like end() above but with the possibility to specify the this context for the callback.

Tracers for outgoing taggable requests additionally offer following methods to get a dynatrace tag to be sent to remote service after the trace was started:

  • getDynatraceStringTag() returns a Dynatrace tag encoded as string
  • getDynatraceByteTag() returns a Dynatrace tag binary encoded as Buffer

This Dynatrace tag needs to be embedded into the message sent to remote service. Depending on the concrete protocol used the string or binary representation may fit better and it's up to the user to decide which variant to use. On incoming service this tag needs to be extracted by the user and passed to the corresponding traceXXX method using the dynatraceTag property of the arguments to allow linking of outgoing and the corresponding incoming trace.

The tracer objects returned by above methods are always valid even if there is no OneAgent present or no trace is created for whatever reason. In this case the methods are still present to avoid the need of extra checking in client code.

Please note that OneAgent uses the name of the given functions in start() and end() to name PurePath nodes. Therefore we recommend to choose named functions over anonymous functions to give you higher quality traces.

Features

The feature sets differ slightly with each language implementation. More functionality will be added over time, see Planned features for OneAgent SDK for details on upcoming features.

A more detailed specification of the features can be found in Dynatrace OneAgent SDK.

Feature Required OneAgent SDK for Node.js version
Trace incoming and outgoing remote calls >=1.0.1
Trace SQL database requests >=1.0.1
Set result data on SQL database requests >=1.1.0
Set custom request attributes >=1.2.0
Trace Messaging >=1.3.0

Trace incoming and outgoing remote calls

Trace outgoing remote calls

An outgoing remote call is traced by calling traceOutgoingRemoteCall() passing an object with following properties:

  • serviceMethod Mandatory - a string holding the name of the called remote method
  • serviceName Mandatory - a string holding the name of the remote service
  • serviceEndpoint Mandatory - a string describing the logical endpoint of the remote service. In case of a clustered/load balanced service, the serviceEndpoint represents the common logical endpoint (e.g. registry://staging-environment/myservices/serviceA) whereas the ConnectionInfo describes the actual communication endpoint. As such a single serviceEndpoint can have many connections.
  • protocolName Optional - a string describing the protocol used (e.g. Protobuf, GIOP,...), only for display purposes

Additionally it holds following properties describing the connection to the remote service. Depending on the connection type the corresponding property/properties shall be set. If the specific information like host/socketPath/... is not available, the property channelType shall be set.

  • host A string specifying the hostname/IP of the server side in case of a TCP/IP connection is used (note that OneAgent may try to resolve the hostname)
  • port The TCP/IP port (optional)
  • socketPath A string specifying the UNIX domain socket file
  • pipeName A string specifying the name of the Pipe
  • channelType Specifies the protocol used as communication channel (e.g. TCP/IP, IN_PROCESS,... ). Valid values are available via ChannelType)

The result of traceOutgoingRemoteCall() is a tracer object to be used for further operations related to this trace (see Tracers for details). As an outgoing remote call is taggable a Dynatrace tag shall be created from tracer after it has been started and embedded to the remote call message content.

Example (see OutgoingRemoteCallSample.js for more details):

// Issue a traced outgoing remote call
async function tracedOutgoingRemoteCall(method, data) {
  const tracer = Api.traceOutgoingRemoteCall({
    serviceEndpoint: "ChildProcess",
    serviceMethod: method,  // the name of the remote method called
    serviceName: "StringManipulator",
    channelType: Sdk.ChannelType.NAMED_PIPE
  });

  try {
    // start tracer, get dynatrace tag and trigger sending via doOutgoingRemoteCall()
    return await tracer.start(function triggerTaggedRemoteCall() {
      // getting a tag from tracer needs to be done after start()
      const dtTag = tracer.getDynatraceStringTag();
      // now trigger the actual remote call
      return doOutgoingRemoteCall(method, data, dtTag);
    });
  } catch (e) {
    tracer.error(e);
    throw e;
  } finally {
    tracer.end();
  }
}

Trace incoming remote calls

An incoming remote call is traced by calling traceIncomingRemoteCall() passing an object with following properties:

  • serviceMethod Mandatory - a string holding the name of the called remote method
  • serviceName Mandatory - a string holding the name of the remote service
  • serviceEndpoint Mandatory - a string describing the logical deployment endpoint of the remote service on server side
  • protocolName Optional - a string describing the protocol used (e.g. Protobuf, GIOP,...), only for display purposes
  • dynatraceTag - a string or Buffer holding the received Dynatrace tag received

The result of this call is a tracer object to be used for further operations related to this trace (see Tracers).

Example (see IncomingRemoteCallSample.js for more details):

// trace and handle incoming messages
async function tracedMessageHandler(message) {
  const tracer = Api.traceIncomingRemoteCall({
    serviceEndpoint: "ChildProcess",
    serviceMethod: message.method,
    serviceName: "StringManipulator",
    dynatraceTag: message.traceTag,   // extract and set the dynatrace tag
    protocolName: "Json"              // optional
  });

  try {
    // start tracer and trigger actual message processing via processMessage(message)
    const result = await tracer.start(processMessage, message);

    // send result calculated by processMessage() back to caller
    process.send({ result: result, id: message.id });

    // end tracer
    tracer.end();
  } catch (e) {
    // send error back
    process.send({ error: e.message, id: message.id });

    // set error and end tracer
    tracer.error(e).end();
  }
}

Trace Messaging

Trace outgoing messages

An outgoing message is traced by calling traceOutgoingMessage() passing an object with following properties:

  • vendorName Mandatory - a string holding the messaging system vendor name (e.g. RabbitMq, Apache Kafka, ...), can be a user defined name. If possible use a constant defined in MessageSystemVendor.
  • destinationName Mandatory - a string holding the destination name (e.g. queue name, topic name).
  • destinationType Mandatory - specifies the type of the destination. Valid values are available via MessageDestinationType

Additionally it holds following properties describing the connection to the messaging service. Depending on the connection type the corresponding property/properties shall be set. If the specific information like host/socketPath/... is not available, the property channelType shall be set.

  • host A string specifying the hostname/IP of the server side in case of a TCP/IP connection is used (note that OneAgent may try to resolve the hostname)
  • port The TCP/IP port (optional)
  • socketPath A string specifying the UNIX domain socket file
  • pipeName A string specifying the name of the Pipe
  • channelType Specifies the protocol used as communication channel (e.g. TCP/IP, IN_PROCESS,... ). Valid values are available via ChannelType)

The result of traceOutgoingMessage() is a tracer object to be used for further operations related to this trace (see Tracers for details). As an outgoing message is taggable a Dynatrace tag shall be created from tracer after it has been started and embedded to the message content.

Besides the common APIs for outgoing tracers this tracer offers the additional methods setVendorMessageId() and setCorrelationId() which may be used to set more details about the message sent. Both APIs receive a string as parameter to pass the correlationId or vendorMessageId provided by messaging system.

Example (see MessagingSample.js for more details):

function traceOutgoingMessage(name, data, corrId) {
  // create a tracer instance and start the trace
  const tracer = Api.traceOutgoingMessage(systemInfo);
  tracer.start(function sendTaggedMessage() {
    // getting a tag from tracer needs to be done after start()
    const dtTag = tracer.getDynatraceStringTag();
    try {
      // now trigger the actual sending of the message
      const messageId = sendMessage(name, data, corrId, dtTag);

      // optional: set correlationId/vendorMessageId if present/relevant
      tracer.setCorrelationId(corrId).setVendorMessageId(`${messageId}`);
    } catch (e) {
      tracer.error(e);
      throw e;
    } finally {
      tracer.end();
    }
  });
}

Trace incoming messages

An incoming message is traced by calling traceIncomingMessage() passing an object with following properties:

  • vendorName Mandatory - a string holding the messaging system vendor name (e.g. RabbitMq, Apache Kafka, ...), can be a user defined name. If possible use a constant defined in MessageSystemVendor.
  • destinationName Mandatory - a string holding the destination name (e.g. queue name, topic name).
  • destinationType Mandatory - specifies the type of the destination. Valid values are available via MessageDestinationType
  • dynatraceTag - a string or Buffer holding the received Dynatrace tag received

Additionally it holds following properties describing the connection to the messaging service. Depending on the connection type the corresponding property/properties shall be set. If the specific information like host/socketPath/... is not available, the property channelType shall be set.

  • host A string specifying the hostname/IP of the server side in case of a TCP/IP connection is used (note that OneAgent may try to resolve the hostname)
  • port The TCP/IP port (optional)
  • socketPath A string specifying the UNIX domain socket file
  • pipeName A string specifying the name of the Pipe
  • channelType Specifies the protocol used as communication channel (e.g. TCP/IP, IN_PROCESS,... ). Valid values are available via ChannelType)

The result of traceIncomingMessage() is a tracer object to be used for further operations related to this trace (see Tracers for details).

Besides the common APIs for outgoing tracers this tracer offers the additional methods setVendorMessageId() and setCorrelationId() which may be used to set more details about the message sent. Both APIs receive a string as parameter to pass the correlationId or vendorMessageId provided by messaging system.

Example (see MessagingSample.js for more details):

function traceIncomingMessage(msg) {
  // create a tracer instance and start the trace, ensure dynatraceTag is set
  const startData = Object.assign({ dynatraceTag: msg.meta.dtTag }, systemInfo);
  const tracer = Api.traceIncomingMessage(startData);
  tracer.start(function processMessage(done) {
    // optional: set correlationId/vendorMessageId if present/relevant
    tracer.setCorrelationId(msg.meta.corrId).setVendorMessageId(`${msg.meta.msgId}`);

    // do whatever needed with the message, simulated via nextTick() here
    process.nextTick(done);
  }, function onDone(err) {
    if (err) {
      tracer.error();
    }
    tracer.end();
  });
}

Trace SQL database requests

A SQL database request is traced by calling traceSQLDatabaseRequest() which requires a database info object as first argument and an object with request specific data as second parameter.

The database info is an object which usually doesn't change during runtime. It holds following properties:

  • name Mandatory - a string defining the name of the database
  • vendor Mandatory - a string holding the database vendor name (e.g. Oracle, MySQL, ...), can be a user defined name. If possible use a constant defined in DatabaseVendor

Additionally, it holds following properties describing the connection to the database. Depending on the actual connection used the corresponding property/properties shall be set. If the specific information like host/socketPath/... is not available, the property channelType shall be set.

  • host A string specifying the hostname/IP of the server side in case of a TCP/IP connection is used (note that OneAgent may try to resolve the hostname)
  • port The TCP/IP port (optional)
  • socketPath A string specifying the UNIX domain socket file
  • pipeName A string specifying the name of the pipe
  • channelType Specifies the protocol used as communication channel (e.g. TCP/IP, IN_PROCESS,... ). Valid values are available via ChannelType.

The second argument holds data describing the concrete operation and holds following properties:

  • statement Mandatory - a string holding the SQL statement to be sent to database.

The result of traceSQLDatabaseRequest() is a tracer object to be used for further operations related to this trace (see Tracers for details).

Besides the common APIs for outgoing tracers this tracer offers the additional method setResultData() which may be used to set details about the result of the database request. It receives an object with following properties:

  • rowsReturned Optional - Number of rows returned by this traced database request. Only positive values are allowed
  • roundTripCount Optional - Count of round-trips that took place. Only positive values are allowed

Please note that SQL database traces are only created if they occur within some other SDK trace (e.g. incoming remote call) or an OneAgent built-in trace (e.g. incoming web request).

Example (see DatabaseRequestSample.js for more details):

// Static info describing the database
const dbInfo = {
  name: dbConfig.database,
  vendor: Sdk.DatabaseVendor.MARIADB,
  host: dbConfig.host,
  port: dbConfig.port
};

// Issue a traced SQL database request
function tracedSqlDatabaseRequest(sql, clientCb) {
  // create a SQL database tracer
  const tracer = Api.traceSQLDatabaseRequest(dbInfo, {
    statement: sql
  });

  // start tracer, calls connection.query(sql, cb) with connection set as this in query()
  tracer.startWithContext(connection.query, connection, sql, (err, results, fields) => {
    if (err) {
      // set the error on the tracer
      tracer.error(err);
    } else {
      // optionally set result data
      tracer.setResultData({
        rowsReturned: 15,
        roundTripCount: 32
      });
    }
    // end the tracer and call client callback forwarding results
    tracer.end(clientCb, err, results, fields);
  });
}

Set custom request attributes

The API addCustomRequestAttribute() adds a custom request attribute to the currently traced service call. There is no reference to a tracer needed as OneAgent SDK will select the current open trace. This may be a trace created by SDK or a trace created by built in sensors of OneAgent. The API may be called several times to add more attributes. If the same attribute key is set several times all values will be recorded.

addCustomRequestAttribute() takes two arguments:

  • key Mandatory - a string specifying the name of the attribute
  • value Mandatory - a string or number specifying the attribute value

Example (see CustomRequestAttributesSample.js for more details):

Api.addCustomRequestAttribute("fooAttribute", "bar");
Api.addCustomRequestAttribute("barAttribute", 15.34);

Administrative Apis

Current SDK state

The method getCurrentState() returns the current status of SDK. Valid values are available via constant object SDK.SDKState:

  • ACTIVE SDK is connected to OneAgent and capturing data.
  • TEMPORARILY_INACTIVE SDK is connected to OneAgent, but capturing is disabled. It is good practice to skip creating SDK transactions to save resources. The SDK state should be checked regularly as it may change at every point in time.
  • PERMANENTLY_INACTIVE SDK isn't connected to OneAgent, so it will never capture data. This SDK state will never change during the lifetime of the process. It is good practice to never call any SDK API to save resources.

Set callbacks for logging

The method setLoggingCallback(callbacks) can be used to set callbacks for warning and error logs. A logging callback receives a string as argument. As parameter pass an object with following properties:

  • warning Optional - a function accepting a string as parameter called to pass warning messages
  • error Optional - a function accepting a string as parameter called to pass error messages

Omitting a callback property will uninstall a previous installed callback.

Api.setLoggingCallbacks({
  warning: (msg) => console.error("DatabaseRequestSample SDK warning: " + msg),
  error: (msg) => console.error("DatabaseRequestSample SDK error: " + msg),
});

Constants

Channel type

The values of constant ChannelType specify the type of the transport channel used. Following values are provided:

  • TCP_IP Communication via TCP/IP
  • UNIX_DOMAIN_SOCKET Communication via UNIX domain socket
  • NAMED_PIPE Communication via named pipe
  • IN_PROCESS Communication is some mechanism within current process (e.g. via files,...)
  • OTHER To be used for any other channel type

Database vendors

The values of constant DatabaseVendor may be used as input for traceSQLDatabaseRequest(). Following values are provided:

  • APACHE_HIVE
  • CLOUDSCAPE
  • HSQLDB
  • PROGRESS
  • MAXDB
  • HANADB
  • INGRES
  • FIRST_SQL
  • ENTERPRISE_DB
  • CACHE
  • ADABAS
  • FIREBIRD
  • DB2
  • DERBY_CLIENT
  • DERBY_EMBEDDED
  • FILEMAKER
  • INFORMIX
  • INSTANT_DB
  • INTERBASE
  • MYSQL
  • MARIADB
  • NETEZZA
  • ORACLE
  • PERVASIVE
  • POINTBASE
  • POSTGRESQL
  • SQLSERVER
  • SQLITE
  • SYBASE
  • TERADATA
  • VERTICA
  • CASSANDRA
  • H2
  • COLDFUSION_IMQ
  • REDSHIFT
  • COUCHBASE

Message destination type

The values of constant MessageDestinationType specify the type of the message destination used. Following values are provided:

  • QUEUE
  • TOPIC

Message system vendors

The values of constant MessageSystemVendor may be used as input for traceIncomingMessage() and traceOutgoingMessage(). Using these constants ensures that services captured by OneAgentSDK are handled the same way as traced via built-in sensors. Following values are provided:

  • HORNETQ
  • ACTIVE_MQ
  • RABBIT_MQ
  • ARTEMIS
  • WEBSPHERE
  • MQSERIES_JMS
  • MQSERIES
  • TIBCO
  • KAFKA

Pass Context

Dynatrace transparently wraps supported libraries to add context information. For every yet unsupported module passContext() can be used to provide transactional context to callbacks.

What is transactional context?

Dynatrace patented PurePath Technology® captures timing and code level context for all transactions, end-to-end, from user click, across all tiers, to the database of record and back. Technically this means that Dynatrace adds transactional context to any inbound and outbound function calls of an application.

What does this mean for Node.js applications?

Node.js is single threaded - its control flow is based on events and asynchronous callbacks.

Let's look at an example for finding a document with mongoDB:

function callback(err, document) {
  console.log(document.name);

  http.get('https://some-api.xyz/service', (res) => {});
    // ^^^                                 °°°°°°°°°°°
    // Asynchronous call                   Asynchronous callback
}

collection.findOne({_id: doc_id}, callback);
        // ^^^^^^^                °°°°°°°°
        // Asynchronous call      Asynchronous callback

After collection.findOne() is executed asynchronously callback() will be called. callback() again contains an asynchronous call http.get() which performs an outbound HTTP request. If there is a current transactional context with an ongoing trace, Dynatrace OneAgent will transparently add a HTTP header containing a Dynatrace tag to this outbound request. The next tier - if instrumented with OneAgent - will continue this trace then. Without further intervention any transactional context would get lost between asynchronous invocation and a callback. Currently the only reliable way to pass over context information to a callback is called 'wrapping'.

Example: Regular callbacks Assume some.asyncFunction() in below sample causes loss of transactional context in OneAgent. To ensure that OneAgent correctly shows activities triggered inside the callback of this function passContext() can be used to create a closure preserving the transactional context active at call time of some.asyncFunction().

const Api = require('@dynatrace/oneagent-sdk').createInstance();

some.asyncFunction(someParam, Api.passContext(function(err, result) {
  // Context is preserved
  http.get('https://some-api.xyz/service', Api.passContext((res) => {
    // other activity, e.g. outgoing web requests,...
  }));
}));

Important: The wrapping via passContext() needs to happen call time of the corresponding sync call

// This will *NOT* work as transactional context at call time of some.asyncFunction() is not preserved
// instead the transactional context at definition of doSomething() is preserved which is not
// related to the relevant transaction triggered by calling some.asyncFunction().
const wrappedFunction = dta.passContext(someFunction);
function doSomething() {
  some.asyncFunction('someParam', wrappedFunction);
}

// This works, passContext() is called at invocation time of some.asyncFunction()
function doSomething() {
  some.asyncFunction('someParam', dta.passContext(someFunction));
}

Further readings

Help & Support

The Dynatrace OneAgent SDK for Node.js is an open source project, currently in beta status. The features are fully supported by Dynatrace.

Get Help

Open a GitHub issue to

  • Report minor defects, minor items or typos
  • Ask for improvements or changes in the SDK API
  • Ask any questions related to the community effort

SLAs don't apply for GitHub tickets

Customers can open a ticket on the Dynatrace support portal to

  • Get support from the Dynatrace technical support engineering team
  • Manage and resolve product related technical issues

SLAs apply according to the customer's support level.

Release Notes

see also Releases

Version Description
1.3.0 add support to trace messaging
1.2.2 don't swallow exceptions
1.2.1 improve type definitions
1.2.0 add support for custom request attributes
1.1.0 add setResultData() for SQL Database tracer
1.0.3 early access to beta
1.0.1 Initial release
You can’t perform that action at this time.