Skip to main content

Send Node.js runtime metrics

This guide walks through the steps to make a Node.js service emit runtime metrics in the OpenTelemetry format so that they appear in the Runtime tab for the service in the APM entity drilldown.

Use this guide if your service is Node.js-based but the Runtime tab shows a zero state, or if you are setting up a new Node.js service and want to ensure the tab is populated from day one.

OpenTelemetry semantic conventions only

The Runtime tab reads only the runtime metrics emitted by @opentelemetry/instrumentation-runtime-node - the nodejs.* (event loop) and v8js.* (V8 heap and garbage collection) namespaces defined by the OpenTelemetry semantic conventions. Metrics that follow other naming conventions, such as the defaults exposed by prom-client, are not recognized.

What you need​

  • A Node.js service running Node.js 22.15.0 or later (required by @opentelemetry/instrumentation-runtime-node).
  • The OpenTelemetry Node.js runtime instrumentation and an OTLP metrics exporter (installed in Step 1).
  • A Coralogix Send-Your-Data API key.
  • The OpenTelemetry endpoint for your Coralogix domain.
  • Permission to set environment variables or modify the service start command.

Step 1: Install the runtime instrumentation​

Add the runtime instrumentation, the Node SDK, and an OTLP metrics exporter to your project.

npm install --save @opentelemetry/sdk-node
npm install --save @opentelemetry/sdk-metrics
npm install --save @opentelemetry/exporter-metrics-otlp-grpc
npm install --save @opentelemetry/instrumentation-runtime-node

This example uses the gRPC metrics exporter. To export over HTTP instead, install @opentelemetry/exporter-metrics-otlp-http and use it in place of the gRPC package in the steps below.

Step 2: Configure the OTLP metrics exporter​

Create an instrumentation file that registers RuntimeNodeInstrumentation with the Node SDK and a periodic metric reader that exports over OTLP to Coralogix. Load this file before your application code.

// instrumentation.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');
const {
RuntimeNodeInstrumentation,
} = require('@opentelemetry/instrumentation-runtime-node');

const sdk = new NodeSDK({
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
}),
instrumentations: [new RuntimeNodeInstrumentation()],
});

sdk.start();

RuntimeNodeInstrumentation also accepts a monitoringPrecision option (milliseconds, default 10) that controls how often the event-loop delay metrics are sampled.

With no arguments, OTLPMetricExporter reads its endpoint and headers from environment variables. Set them before the process starts:

export OTEL_EXPORTER_OTLP_ENDPOINT="<coralogix-otel-endpoint>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <CXPrivateKey>"

Replace <CXPrivateKey> with your Send-Your-Data API key and <coralogix-otel-endpoint> with the OpenTelemetry endpoint for your selected Coralogix domain.

To configure the exporter in code instead, pass url and headers to OTLPMetricExporter({ ... }).

Step 3: Set the resource attributes​

The Runtime tab correlates metrics to a service through the service.name resource attribute, and groups Node.js instances in the selector and heatmap by k8s.pod.name. When k8s.pod.name is absent, the backend falls back to other resource attributes - such as container.name, host.name, and host.id - to tell instances apart, but for pod-level granularity, set k8s.pod.name explicitly.

Set the service name and the other attributes through environment variables that the SDK reads at startup:

export OTEL_SERVICE_NAME="<ServiceName>"
export OTEL_RESOURCE_ATTRIBUTES="cx.application.name=<CXApplicationName>,cx.subsystem.name=<CXSubsystemName>"

In Kubernetes, k8s.pod.name and other pod-level attributes are not auto-detected by the Node SDK - its default resource detectors fill in host.name, host.id, and process attributes only. To populate k8s.pod.name and k8s.namespace.name, choose one of:

  • Downward API environment variables: pass pod metadata into the container as env vars and append them to OTEL_RESOURCE_ATTRIBUTES. Example: OTEL_RESOURCE_ATTRIBUTES=...,k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(POD_NAMESPACE).
  • OpenTelemetry Operator auto-instrumentation: install the OpenTelemetry Operator and create an Instrumentation custom resource. The Operator injects the Node.js instrumentation and the right resource attributes automatically.
  • Collector-side enrichment: run the k8sattributes processor in your OpenTelemetry Collector. The processor adds Kubernetes attributes to telemetry as it passes through, so the application itself does not need to know about Kubernetes.

Without one of these, k8s.pod.name is missing and the backend distinguishes instances only by the coarser attributes it can detect (such as host.name, the Kubernetes node), which groups multiple pods on the same node together - too coarse for per-pod debugging.

AttributeRequiredPurpose
service.nameYesIdentifies the service in the catalog. Without it, metrics arrive but cannot be correlated to a service row. Set it via OTEL_SERVICE_NAME.
service.namespaceRecommendedDisambiguates services with the same name in different teams or environments.
k8s.pod.nameYes for per-pod filteringThe attribute the instances selector and heatmap group by. Set it explicitly via the Downward API, OTel Operator, or Collector enrichment for pod-level granularity.
host.nameAuto-detectedBackend fallback for telling instances apart when k8s.pod.name is not present (host-level, not pod-level).
deployment.environment.nameRecommendedRequired for the environment filter.

Step 4: Restart the service​

Restart the service so it loads the instrumentation file before your application code. Preload it with the --require flag:

node --require ./instrumentation.js app.js

Or through NODE_OPTIONS:

export NODE_OPTIONS="--require ./instrumentation.js"
node app.js

The instrumentation file must be loaded before the rest of the application so that the SDK starts first.

Step 5: Verify metrics are flowing​

  1. In the Coralogix toolbar, go to Explore, then Metrics.
  2. Search for a Node.js runtime metric - for example v8js.memory.heap.used or nodejs.eventloop.utilization - filtered by your service name.
  3. Confirm at least one data point has arrived in the last few minutes.
Querying by service name in Explore

The OpenTelemetry resource attribute service.name (with a dot) is transformed during ingestion to the indexed metric label serviceName (camelCase). Use serviceName when filtering metrics in Explore or PromQL queries. The Runtime tab handles this mapping automatically - you only need to know the label name when you query the metrics directly.

If no nodejs.* or v8js.* metrics arrive at all:

  • Confirm the instrumentation file is loaded before application code (the --require flag or NODE_OPTIONS).
  • Confirm the OTEL_EXPORTER_OTLP_HEADERS value contains a valid Send-Your-Data API key.
  • Confirm the service has network access to the OpenTelemetry endpoint for your domain.
  • Enable SDK diagnostic logging to surface OTLP export errors.

Step 6: Open the Runtime tab​

  1. In the Coralogix toolbar, select APM.
  2. Select your service.
  3. Select the Runtime tab.

The widgets populate as soon as nodejs.* and v8js.* metrics for the service are present in the selected time window. If you still see the zero state, return to Step 5 and confirm the metrics are reaching Coralogix.

Troubleshooting​

SymptomLikely causeFix
Runtime tab is hiddenThe service's detected language is not Node.js, so the tab is not shown for itConfirm the instrumentation is running and service.name matches the service in the catalog
Tab shows the zero stateNo nodejs.* or v8js.* metrics arriving for the serviceRun through Step 5 - most often the instrumentation file is not preloaded before application code
Prometheus-style runtime metrics appear in Explore but the tab stays emptyMetrics use prom-client naming, not the OpenTelemetry conventionsSwitch to @opentelemetry/instrumentation-runtime-node, which emits the nodejs.* and v8js.* namespaces
Widgets render but the per-instance filter and heatmap show only one instanceResource detection has not picked up k8s.pod.name or host.nameSet k8s.pod.name via the Downward API, OTel Operator, or Collector enrichment
Metrics arrive in Explore but the catalog does not show the serviceservice.name is missing or differs from the spans' service nameSet service.name consistently for traces and metrics

Next steps​

Read and interpret the metrics in the Node.js runtime metrics tab.

Last updated on