Instrument your JavaScript application on Node.js with OpenTelemetry
This walkthrough shows how to add observability to your JavaScript application using the OpenTelemetry JavaScript libraries and tools.
Feature | Supported |
---|---|
Automatic Instrumentation | Yes |
Automatic OneAgent Ingestion | Yes |
Prerequisites
- Dynatrace version 1.222+
- For tracing, W3C Trace Context is enabled
- From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
- Turn on Send W3C Trace Context HTTP headers.
Choose how to ingest data into Dynatrace
OneAgent currently only ingests traces automatically. If you are recording metrics or logs, choose the OTLP export route.
Initialize OpenTelemetry
-
Run the following
npm
command to install the necessary libraries and dependencies.1npm install \2 @opentelemetry/api \3 @opentelemetry/exporter-metrics-otlp-proto \4 @opentelemetry/exporter-trace-otlp-proto \5 @opentelemetry/instrumentation \6 @opentelemetry/resources \7 @opentelemetry/sdk-metrics \8 @opentelemetry/sdk-trace-node \9 @opentelemetry/semantic-conventionsDepending on the libraries your application is using, there may be also additional instrumentation support libraries that you want to add to the dependencies. A list of support libraries can be found here. Common examples would be @opentelemetry/instrumentation-http and @opentelemetry/instrumentation-net for the HTTP and network libraries.
-
Create a file named
otel.js
in your application directory and save the following content.1const opentelemetry = require("@opentelemetry/api");2const { Resource } = require("@opentelemetry/resources");3const { SemanticResourceAttributes } = require("@opentelemetry/semantic-conventions");4const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");5const { registerInstrumentations } = require("@opentelemetry/instrumentation");6const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");7const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-proto");8const { OTLPMetricExporter } = require("@opentelemetry/exporter-metrics-otlp-proto");9const { MeterProvider, PeriodicExportingMetricReader, AggregationTemporality } = require('@opentelemetry/sdk-metrics');1011const DT_API_URL = ''; // TODO: Provide your SaaS/Managed URL here12const DT_API_TOKEN = ''; // TODO: Provide the OpenTelemetry-scoped access token here131415// ===== GENERAL SETUP =====1617registerInstrumentations({18 instrumentations: [ /* TODO Register your auto-instrumentation libraries here */ ],19});2021const fs = require("fs");22let dtmetadata = new Resource({})23for (let name of ['dt_metadata_e617c525669e072eebe3d0f08212e8f2.json', '/var/lib/dynatrace/enrichment/dt_metadata.json', '/var/lib/dynatrace/enrichment/dt_host_metadata.json']) {24 try {25 dtmetadata = dtmetadata.merge(26 new Resource(JSON.parse(fs.readFileSync(name.startsWith("/var") ?27 name : fs.readFileSync(name).toString('utf-8').trim()).toString('utf-8'))));28 break29 } catch { }30}3132const resource =33 Resource.default().merge(34 new Resource({35 [SemanticResourceAttributes.SERVICE_NAME]: "js-agent",36 [SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0",37 })38 ).merge(dtmetadata);394041// ===== TRACING SETUP =====4243const provider = new NodeTracerProvider({44 resource: resource,45});4647const exporter = new OTLPTraceExporter({48 url: DT_API_URL + '/v1/traces',49 headers: { Authorization: 'Api-Token ' + DT_API_TOKEN }50});5152const processor = new BatchSpanProcessor(exporter);53provider.addSpanProcessor(processor);5455provider.register();565758// ===== METRIC SETUP =====5960const metricExporter = new OTLPMetricExporter({61 url: DT_API_URL + '/v1/metrics',62 headers: { Authorization: 'Api-Token ' + DT_API_TOKEN },63 temporalityPreference: AggregationTemporality.DELTA64});6566const metricReader = new PeriodicExportingMetricReader({67 exporter: metricExporter,68 exportIntervalMillis: 300069});70const meterProvider = new MeterProvider({ resource: resource });71meterProvider.addMetricReader(metricReader);7273// Set this MeterProvider to be global to the app being instrumented.74opentelemetry.metrics.setGlobalMeterProvider(meterProvider); -
Configure
DT_API_URL
andDT_API_TOKEN
for the Dynatrace URL and access token inotel.js
.Value injectionInstead of hardcoding the URL and token, you might also consider reading them from storage specific to your application framework (for example, environment variables or framework secrets).
-
Adjust the Node.js call for your application to include the –require command line parameter and point it towards
otel.js
.1node --require ./otel.js ./myapplication.js
Manually instrument your application
Add tracing
-
Obtain a reference to the OpenTelemetry API.
1const opentelemetry = require("@opentelemetry/api"); -
Now we can get a tracer object.
1const tracer = opentelemetry.trace.getTracer('my-tracer'); -
With
tracer
, we can use a span builder to create and start new spans.1const span = tracer.startSpan('Call to /myendpoint');23span.setAttribute('http.method', 'GET');4span.setAttribute('net.protocol.version','1.1');56// TODO your code goes here78span.end();In the above code, we:
Create a new span and name it "Call to /myendpoint"
- Add two attributes, following the semantic naming convention, specific to the action of this span: information on the HTTP method and version
- Add a
TODO
in place of the eventual business logic - Call the span's
end()
method to complete the span
Collect metrics
-
As with tracing, we first need to get a reference to the OpenTelemetry API.
1const opentelemetry = require("@opentelemetry/api"); -
Next, we need to obtain a meter object.
1const meter = opentelemetry.metrics.getMeter('my-meter'); -
With
meter
, we can now create individual instruments, such as a counter.1const requestCounter = meter.createCounter('request_counter', {2 description: 'The number of requests we received'3}); -
We can now invoke the
add()
method ofrequestCounter
to record new values with the counter and save additional attributes (for example,action.type
).1requestCounter.add(1, { 'action.type': 'create' });
You can also create an asynchronous gauge, which requires a callback function that will be invoked by OpenTelemetry upon data collection.
The following example records on each invocation the available memory:
1const gauge = meter.createObservableGauge('free_memory');2gauge.addCallback(r => {3 r.observe(require('os').freemem());4});
Connect logs
OpenTelemetry logging is currently not yet available for JavaScript and is still under development.
Ensure context propagation optional
Context propagation is particularly important when network calls (for example, REST) are involved.
If you are using automatic instrumentation and your networking libraries are covered by automatic instrumentation, this will be automatically taken care of by the instrumentation libraries. Otherwise, your code needs to take this into account.
Extracting the context when receiving a request
The following examples assume that we received a network call via ClientRequest
and uses extract()
to create the context object remoteCtx
, based on the context information received from the HTTP headers. This allows us to continue the previous trace with our spans.
1//Extract context from incoming headers2const { SpanKind, ROOT_CONTEXT} = require("@opentelemetry/api");34const remoteCtx = opentelemetry.propagation.extract(ROOT_CONTEXT, req.headers);56const serverSpan = opentelemetry.trace.getTracer('my-server-tracer')7 .startSpan('my-server-span', {8 kind: SpanKind.SERVER,9 attributes: {10 'my-server-key-1': 'my-server-value-1'11 },12 }, remoteCtx);
Injecting the context when sending requests
In the following example, we use the axios HTTP client to send a REST request to another service and provide our existing context as part of the HTTP headers of our request.
To do so, we create a ctx
object, pass it the current span, and mark it as active. Then we pass that context object and an empty my_headers
object to inject()
and, once the call is returned, we have the appropriate headers in my_headers
, which we can eventually pass to our HTTP call.
1const ctx = opentelemetry.trace.setSpan(2 opentelemetry.context.active(),3 serverSpan4);56const my_headers = {};7opentelemetry.propagation.inject(ctx, my_headers);89await axios.get(URL, {headers: my_headers});1011serverSpan.end();
Configure data capture to meet privacy requirements optional
While Dynatrace automatically captures all OpenTelemetry attributes, only attribute values specified in the allowlist are stored and displayed in the Dynatrace web UI. This prevents accidental storage of personal data, so you can meet your privacy requirements and control the amount of monitoring data stored.
To view your custom attributes, you need to allow them in the Dynatrace web UI first. To learn how to configure attribute storage and masking, see Attribute redaction.
Verify data ingestion into Dynatrace
Once you have finished the instrumentation of your application, perform a couple of test actions to create and send demo traces, metrics, and logs and verify that they were correctly ingested into Dynatrace.
To do that for traces, in the Dynatrace menu, go to Distributed traces and select the Ingested traces tab. If you use OneAgent, select PurePaths instead.
Metrics and logs can be found under their respective entries at Observe and explore.