OpenTelemetry interoperability in Node.js
OneAgent version 1.229+
OpenTelemetry interoperability connects the Dynatrace AWS Lambda extension to the OpenTelemetry Node.js instrumentation to use the instrumentation packages and extensions. You can then monitor technologies like databases or messaging frameworks that aren't supported by Dynatrace AWS Lambda extension out of the box.
Before you start
-
Ensure that OpenTelemetry interoperability is enabled.
-
Verify that the installed JavaScript OpenTelemetry API is compatible with the Dynatrace AWS Lambda extension. The following table lists the compatible versions:
OneAgent version Maximum OpenTelemetry API version 1.229+ 1.0.x 1.241+ 1.1.x 1.257+ 1.2.x 1.259+ 1.3.x 1.261+ 1.4.x
Use OpenTelemetry Node.js instrumentation
When using an OpenTelemetry Node.js instrumentation, the configuration of all necessary OpenTelemetry SDK components and the registration of a TracerProvider are automatically handled by the Dynatrace AWS Lambda extension, so you don't need to register another TracerProvider.
Instrumentation packages for JavaScript can be found in the OpenTelemetry JavaScript contributions repository. Note that
- Some instrumentations might interfere with the Dynatrace HTTP and Lambda instrumentations and are automatically suppressed. These include @opentelemetry/instrumentation-http and @opentelemetry/instrumentation-aws-lambda.
- @opentelemetry/auto-instrumentations-node use is discouraged, as it includes many different instrumentations.
The following code example shows how to instrument PostgreSQL calls in your Node.js Lambda function by using the opentelemetry-instrumentation-pg instrumentation package.
1const { registerInstrumentations } = require('@opentelemetry/instrumentation');2const { PgInstrumentation } = require('@opentelemetry/instrumentation-pg');34// You must create the PgInstrumentation (and other instrumentations)5// before loading any corresponding modules, such as `require('pg')`.6registerInstrumentations({7 instrumentations: [8 new PgInstrumentation(),9 ],10});1112const { Client } = require('pg');1314exports.handler = async function myHandler(event, context) {15 let client;16 try {17 client = new Client(/* DB connection information */);18 await client.connect();19 const result = await client.query('SELECT * FROM users;');20 return result.rows;21 } finally {22 client?.end();23 }24}
To instrument the AWS SDK for JavaScript, OpenTelemetry provides the opentelemetry/instrumentation-aws-sdk
instrumentation package.
The following code example shows how the opentelemetry/instrumentation-aws-sdk
instrumentation package can be used to add observability for calls to a DynamoDB database (Dynatrace version 1.244+).
1const AWS = require('aws-sdk');2const { registerInstrumentations } = require('@opentelemetry/instrumentation');3const { AwsInstrumentation } = require('@opentelemetry/instrumentation-aws-sdk');45registerInstrumentations({6 instrumentations: [7 new AwsInstrumentation()8 ]9});1011exports.handler = function(event, context) {12 const ddb = new AWS.DynamoDB();1314 const dbParamsGetDelete = {15 TableName: 'E2E_test_table',16 Key: {17 'svnr': { N: '1234'}18 }19 };20 ddb.getItem(dbParamsGetDelete, function(err, data) {21 if (err) {22 console.error('Error', err);23 } else {24 console.log('Success', data.Item);25 }26 });27};
After running the above code snippet, the DynamoDB service page looks as follows.
Use OpenTelemetry Node.js API
OpenTelemetry JavaScript can be used in an SDK-like approach to trace additional operations that aren't covered by an instrumentation package.
1const opentelemetry = require('@opentelemetry/api');23const tracer = opentelemetry.trace.getTracer('my-package-name');45exports.handler = function(event, context) {6 // create a span using the OTel API7 const span = tracer.startSpan('do some work');8 span.setAttribute('foo', 'bar');9 span.end();1011 // ...1213 const response = {14 statusCode: 200,15 body: JSON.stringify('Hello from Node.js'),16 };17 return response;18};
Trace AWS SQS and SNS messages with Node.js
OneAgent version 1.253+ for SQS OneAgent version 1.257+ for SNS
You can use @opentelemetry/instrumentation-aws-sdk package to trace AWS SQS and SNS messages and collect the traces via Dynatrace AWS Lambda extension.
Install the required dependencies
1npm install @opentelemetry/api @opentelemetry/instrumentation-aws-sdk @opentelemetry/instrumentation aws-sdk
Set up tracing
Use the following code to set up tracing for sending SQS messages to an SQS queue from a Dynatrace-monitored Node.js application:
1const { AwsInstrumentation } = require('@opentelemetry/instrumentation-aws-sdk');2const { registerInstrumentations } = require('@opentelemetry/instrumentation');34// The instrumentation must be registered before importing the aws-sdk module!5registerInstrumentations({6 instrumentations: [7 new AwsInstrumentation()8 ]9});1011// You can now import the aws-sdk module if needed:12const AWS = require('aws-sdk');
Send an SQS/SNS message
-
Via Node.js HTTP server:
When you make a request to the HTTP server, a message is sent to an SQS queue or SNS topic. If you send a message before the root span exists, make sure to create the root span manually. For details on the span manual creation with OpenTelemetry, see OpenTelemetry traces with OneAgent.
In this code example, a root span for the incoming HTTP request is created by the tracer.
1const http = require("http");2const AWS = require('aws-sdk');3const sqs = new AWS.SQS();4const sns = new AWS.SNS();56const server = http.createServer((req, res) => {7 const messageSendCallback = function (err, message) {8 if (err) {9 console.log("failed to send a message: " + err);10 res.writeHead(500);11 res.end("failure");12 } else {13 console.log("Success", message.MessageId);14 res.writeHead(200);15 res.end("success");16 }17 }1819 if (req.url === "/send-sqs-message") {20 const params = {21 DelaySeconds: 10,22 MessageBody: "[your payload]",23 QueueUrl: "[your SQS-queue URL]"24 };25 sqs.sendMessage(params, messageSendCallback);26 } else if (req.url === "/send-sns-message") {27 const params = {28 Message: "[your payload]",29 TopicArn: "[your SNS-topic ARN]"30 };31 sns.publish(params, messageSendCallback);32 } else {33 res.writeHead(404);34 res.end("not found");35 }36});3738server.on("close", () => { console.log("Closing server") });39server.listen(8004, () => {40 console.log("server started!");41});A request to the
/send-sqs-message
path should produce traces as shown below.The second node in the distributed trace named
sqs-minimal-sample-nodejs-receiver-trigger send
represents the sent SQS message and is generated by the aws-sdk instrumentation.Because
aws-sdk
package uses HTTP requests to send SQS messages, there is a call toRequests to public networks
, which are captured by the OneAgent HTTP instrumentation. The callinvoke
comes from the AWS Lambda function subscribed to the SQS queue, which is monitored by the Dynatrace AWS Lambda extension. -
Via AWS Lambda function
You can send an SQS or SNS message from an AWS Lambda function monitored by the Dynatrace AWS Lambda extension.
1const AWS = require('aws-sdk');23exports.handler = function (event, context, callback) {4 const sqs = new AWS.SQS();5 const params = {6 DelaySeconds: 10,7 MessageBody: "[your payload]",8 QueueUrl: "[your SQS-queue URL]"9 };1011 sqs.sendMessage(params, function (err, data) {12 if (err) {13 context.succeed({14 statusCode: 500,15 body: err,16 });17 } else {18 console.log("SQS-Success", data.MessageId);19 context.succeed({20 statusCode: 200,21 body: "SQS-Success",22 });23 }24 });25}The resulting distributed trace is similar to the Node.js application example:
Receive an SQS/SNS message
You can trace SQS messages forwarded from
-
An SQS topic
The Dynatrace AWS Lambda extension automatically extracts the parent and creates a Lambda span when an AWS Lambda function is triggered by AWS SQS. However, when a batch of multiple messages is received, only the last message is considered and used for parent propagation. To propagate parents from the batch of multiple incoming messages you can, for example, manually create spans with the parent from each message.
To configure the Dynatrace AWS Lambda extension to allow setting parent spans manually:
-
For the environment variables configuration method, set the
DT_OPEN_TELEMETRY_ALLOW_EXPLICIT_PARENT
environment variable totrue
:1DT_OPEN_TELEMETRY_ALLOW_EXPLICIT_PARENT=true -
For the JSON file configuration method, in
dtconfig.json
, set the following field totrue
:1{2 ...other configuration properties...3 "OpenTelemetry": {4 "AllowExplicitParent": "true"5 }6}
Then new spans can be created with the parent span extracted from each received SQS message.
1const { propagation, ROOT_CONTEXT, trace, SpanKind } = require("@opentelemetry/api");2const { MessagingOperationValues, SemanticAttributes } = require("@opentelemetry/semantic-conventions");34const AWS = require("aws-sdk");5const queueUrl = "[sqs queue url]";6const tracer = trace.getTracer("my-receiver");78exports.handler = async (event) => {9 const sqs = new AWS.SQS();1011 await new Promise((resolve, reject) => {12 const receiveParams = {13 MaxNumberOfMessages: 10,14 QueueUrl: queueUrl,15 // MessageAttributeNames not needed if @opentelemetry/instrumentation-aws-sdk is used16 MessageAttributeNames: propagation.fields()17 };18 sqs.receiveMessage(receiveParams, function (err, data) {19 if (err) {20 console.log("ERROR: ", err);21 reject(err);22 } else if (data.Messages?.length) {23 data.Messages.forEach((msg) => {24 console.log("message received:", msg.MessageId)2526 // manual span creation27 const ctx = extractParent(msg, /*fromSnsPayload=*/ false);28 const spanAttributes = {29 [SemanticAttributes.MESSAGING_MESSAGE_ID]: msg.MessageId,30 [SemanticAttributes.MESSAGING_URL]: queueUrl,31 [SemanticAttributes.MESSAGING_SYSTEM]: "aws.sqs",32 [SemanticAttributes.MESSAGING_OPERATION]: MessagingOperationValues.PROCESS,33 };34 const span = tracer.startSpan("received message", { kind: SpanKind.CONSUMER, attributes: spanAttributes }, ctx);35 // ... Here your actual processing would go...36 span.end();3738 const deleteParams = {39 QueueUrl: queueUrl,40 ReceiptHandle: msg.ReceiptHandle41 };42 sqs.deleteMessage(deleteParams, function (err, data) {43 if (err) {44 console.log("Delete Error", err);45 } else {46 console.log("Message Deleted", data);47 }48 });49 });50 }5152 resolve();53 });54 });55};5657function extractParent(msg, fromSnsPayload=false) {58 let valueKey = "StringValue"59 if (fromSnsPayload) {60 valueKey = "Value";61 try {62 msg = JSON.parse(msg.Body)63 } catch {64 msg = {}65 }66 }67 const carrier = {};68 Object.keys(msg.MessageAttributes || {}).forEach((attrKey) => {69 carrier[attrKey] = msg.MessageAttributes[attrKey]?.[valueKey];70 });71 return propagation.extract(ROOT_CONTEXT, carrier)72};In the example above,
aws-sdk
is used to subscribe and receive messages from an SQS queue. For each incoming message, the parent span is extracted from the message attributes, and a newreceived message
span with the extracted parent is created. If your SQS queue is subscribed to an SNS topic, the example above might need to be adapted. For details, see Tracing SQS messages forwarded from an SNS topic.The resulting distributed trace with two messages sent to a queue looks as below.
-
-
An SNS topic
For SNS messages that are forwarded to SQS, the message format depends on the raw message delivery configuration on the SNS subscription.
Raw message delivery Message format Example Enabled
The SNS message attributes are converted to SQS message attributes and the parent can be directly extracted from the
MessageAttributes
of the SQS message.Disabled
The SNS message and its
MessageAttributes
are delivered as a serialized JSON string in the body of the received SQS message. To correctly link the receive span, the parent needs to be extracted from theMessageAttributes
of the serialized SNS message.- Receive a batch of multiple messages
Additional configuration is required for this example. When calling the
extractParent
method, set the value of thefromSnsPayload
parameter totrue
.
- Receive a batch of multiple messages
Additional configuration is required for this example. When calling the