Node.js
| Traces | Metrics | App Logs | Custom Logs | Profiling |
|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | ✅ |
This guide provides instructions to set up Application Performance Monitoring (APM) in a Node.js application. You can also find these instructions on the installation page in your Middleware account. For example code, view here.
Prerequisites#
- Node.js 18.17.1+: Verify with
node --version - Python Version 3.8+ Verify with
python3 --version
Installation#
1 Install Node.js APM Package#
Run the following command in your terminal:
1npm install @middleware.io/node-apm --save2 Setup Options#
Host based APM requires the Middleware Agent which can be installed using the relevant OS based instructions. here.
Method 1: Direct Code Initialization#
Add the following lines to the beginning of your application code base. The access token is your account key, which can be found on the Installation page.
index.js
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5});
6// must come before importing any other module.
7// At the very top of the entrypoint
8const express = require('express'); // other dependenciesIf you want to track APM data across different deployments of your application. You can pass along the app.version,
For Example, If your deployment version is 1.2.0 the config should look something like this.
index.js
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5 customResourceAttributes: {
6 "app.version": "1.2.0",
7 }
8});Method 2: Add the tracker with command line arguments#
Create a file named instrument.js or instrument.ts in your project root:
instrument.js
1// instrument.js
2const tracker = require('@middleware.io/node-apm');
3tracker.track({
4 serviceName: "your-service-name",
5 accessToken: "<MW_API_KEY>",
6 customResourceAttributes: {
7 "app.version": "1.2.0",
8 }
9});To ensure the tracker is initialized before any other code runs, use the --require flag when starting your Node.js application:
Shell
1node --require ./instrument.js your-main-app.jsMethod 1: Direct Code Initialization#
Add the following lines to the beginning of your application code base. The access token is your account key, which can be found on the Installation page.
index.js
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5 target: "https://<MW_UID>.middleware.io:443",
6});
7// must come before importing any other module.
8// At the very top of the entrypoint
9const express = require('express'); // other dependenciesIf you want to track APM data across different deployments of your application. You can pass along the app.version,
For Example, If your deployment version is 1.2.0 the config should look something like this.
index.js
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5 target: "https://<MW_UID>.middleware.io:443",
6 customResourceAttributes: {
7 "app.version": "1.2.0",
8 }
9});Method 2: Add the tracker with command line arguments#
Create a file named instrument.js or instrument.ts in your project root:
instrument.js
1// instrument.js
2const tracker = require('@middleware.io/node-apm');
3tracker.track({
4 serviceName: "your-service-name",
5 accessToken: "<MW_API_KEY>",
6 target: "https://<MW_UID>.middleware.io:443",
7 customResourceAttributes: {
8 "app.version": "1.2.0",
9 }
10});To ensure the tracker is initialized before any other code runs, use the --require flag when starting your Node.js application:
Shell
1node --require ./instrument.js your-main-app.js3 Container Variables#
No Container Variables Required in case of serverless mode
Docker#
Applications running in a container require an additional environment variable. If your application is not running in a container, move to Step 4.
For Docker containers, add the following environment variable to your application.
Bash
1MW_AGENT_SERVICE=<DOCKER_BRIDGE_GATEWAY_ADDRESS>The DOCKER_BRIDGE_GATEWAY_ADDRESS is the IP address of the gateway between the Docker host and bridge network. This is 172.17.0.1 by default. Learn more about Docker bridge networking here
Kubernetes#
Bash
1kubectl get service --all-namespaces | grep mw-serviceThen add the following environment variable to your application deployment YAML file.
Bash
1MW_AGENT_SERVICE=mw-service.mw-agent-ns.svc.cluster.local4 Capture Application Data#
Traces#
Distributed tracing is automatically enabled upon completion of Step 2.
Span Attributes#
For any automatically instrumented endpoints, add the following code snippet:
1router.post("/email/send", async (req, res) => {
2 const email = req.body.email;
3 tracker.setAttribute("email", email);
4 res.json({ status: "email sent" });
5});Custom Logs#
To ingest custom logs into Middleware, utilize the following functions inside your logging method based on desired log severity levels.
index.js
1tracker.info('Info sample');
2tracker.warn('Warning sample');
3tracker.debug('Debugging Sample');
4tracker.error('Error Sample');To add stack traces along with the error log, use the following error tracking function.
index.js
1tracker.error(new Error('Error sample with stack trace'));Custom Metrics and Spans#
Create custom instruments and spans by exposing the meter and tracer with the following pattern:
index.js
1const tracker = require("@middleware.io/node-apm");
2// Initialize the tracker
3tracker.track({
4 // Service name differentiates your applications in the APM section of the Middleware platform.
5 serviceName: "<your_service_name_here>",
6 // You can find your access token in the Middleware platform by going to the installation page.
7 accessToken: "<MW_API_KEY>",
8 // The target is your Middleware platform URL.
9 // When you login to the Middleware platform, you can find your target URL in the URL bar.
10 target: "<your_mw_url_here>",
11});
12const express = require("express");
13const app = express();
14
15// Create a tracer, used to create spans.
16const tracer = tracker.getTracer();
17
18// Create a meter, used to create custom metrics.
19const meter = tracker.getMeter();
20
21// Create a counter metric.
22// For a list of other metric types, see the opentelemetry metrics API
23// documentation. (https://opentelemetry.io/docs/specs/otel/metrics/api/)
24let counter = meter.createCounter("health-check-counter");
25
26/**
27 * Generate a random integer between 0 and the range provided.
28 * @param {Number} range
29 * @returns {Number}
30 */
31function randInt(range) {
32 // Create a span. A span must be closed.
33 return tracer.startActiveSpan("randInt", (span) => {
34 const randomInt = Math.floor(Math.random() * range);
35 // Be sure to end the span
36 span.end();
37 return randomInt;
38 });
39}
40
41app.get("/randInt", (req, res) => {
42 // Calling the randInt function will generate a span.
43 const randomInt = randInt(100);
44 res.json({ number: randomInt });
45});
46
47app.get("/health", (req, res) => {
48 // Increment the health-check-counter by 1.
49 counter.add(1);
50 res.json({ status: "ok" });
51});
52
53app.listen(5051, () => {
54 console.log("Server is running on port 5051");
55});Profiling#
Application Profiling is auto-configured upon completing Step 2.
Stack Traces#
Use the errorRecord method to record a stack trace when an error occurs. See an example of this method below.
index.js
1app.get('/error', function (req, res) {
2 try{
3 throw new Error('oh error!');
4 }catch (e) {
5 track.errorRecord(e)
6 }
7 res.status(500).send("wrong");
8});Continuous Profiling#
Continuous profiling captures real-time performance insights. Access this feature under the APM > Continuous Profiling section in the Middleware platform.
Continuous profiling adds CPU/wall-clock sampling overhead to your application. We recommend enabling it in non-production environments (dev, staging) first to evaluate the performance impact before turning it on in production.
When using the @middleware.io/node-apm package, continuous profiling is enabled by default. If you've explicitly disabled it, just set enableProfiling: true in your tracker.track() call to turn it back on.
index.js
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5 enableProfiling: true,
6});Enabling Continuous Profiling on an Existing Node.js Deployment (Kubernetes)#
If your Node.js app is already running in Kubernetes and you want to enable Pyroscope-based continuous profiling without rebuilding or redeploying the app image, you can patch the existing Deployment to add an init container that installs the Pyroscope Node.js SDK into a shared volume, then preload it into the app container via NODE_OPTIONS.
How it works#
- An init container (
pyroscope-injector) installs@pyroscope/nodejsinto anemptyDirvolume shared with the app container, and writes a small loader script that callsPyroscope.init()/Pyroscope.start(). - The app container mounts that same volume and gets
NODE_OPTIONS=--require=/pyroscope/pyroscope-loader.js, which preloads the profiler before the app's own entrypoint runs. - All of this is added via a
kubectl patchusing strategic merge, so it only adds to the existingvolumes,initContainers,env, andvolumeMountslists — it does not remove or replace anything else already configured on the deployment.
Prerequisites#
kubectlpointed at the target cluster/context.- The name of the
DeploymentandNamespaceyou want to profile. - The name of the container inside that pod spec that runs the Node.js process.
- Your Middleware project UID (used as both the profiling server subdomain and the tenant ID) — auto-filled below when you're logged in and select a project.
One-shot patch command#
Fill in the deployment-specific placeholders (<DEPLOYMENT_NAME>, <NAMESPACE>, <CONTAINER_NAME>, <APP_NAME>) and run. <MW_UID> is auto-populated with your Middleware project UID once you select a project below (while logged in) — otherwise replace it manually.
Bash
1kubectl patch deployment <DEPLOYMENT_NAME> \
2 -n <NAMESPACE> \
3 --type strategic \
4 --patch "$(cat <<'PATCH_EOF'
5spec:
6 template:
7 spec:
8 volumes:
9 - name: pyroscope-inject
10 emptyDir: {}
11 initContainers:
12 - name: pyroscope-injector
13 image: node:20-bookworm-slim
14 command: ["/bin/sh", "-c"]
15 args:
16 - |
17 set -ux
18 apt-get update || true
19 apt-get install -y --no-install-recommends python3 make g++ || true
20 cd /pyroscope
21 npm init -y || true
22 npm install @pyroscope/nodejs@0.2.6 || true
23 cat > /pyroscope/pyroscope-loader.js <<'JS_EOF'
24 try {
25 const Pyroscope = require('/pyroscope/node_modules/@pyroscope/nodejs');
26 Pyroscope.init({
27 serverAddress: process.env.PYROSCOPE_SERVER_ADDRESS,
28 appName: process.env.PYROSCOPE_APPLICATION_NAME,
29 tenantID: process.env.PYROSCOPE_TENANT_ID,
30 wall: { collectCpuTime: process.env.PYROSCOPE_WALL_COLLECT_CPU_TIME === 'true' }
31 });
32 Pyroscope.start();
33 } catch (err) {
34 console.error('[pyroscope] profiler failed to start, continuing without profiling:', err.message);
35 }
36 JS_EOF
37 exit 0
38 volumeMounts:
39 - name: pyroscope-inject
40 mountPath: /pyroscope
41 containers:
42 - name: <CONTAINER_NAME>
43 env:
44 - name: NODE_OPTIONS
45 value: "--require=/pyroscope/pyroscope-loader.js"
46 - name: PYROSCOPE_APPLICATION_NAME
47 value: "<APP_NAME>"
48 - name: PYROSCOPE_SERVER_ADDRESS
49 value: "https://<MW_UID>.middleware.io/profiling"
50 - name: PYROSCOPE_TENANT_ID
51 value: "<MW_UID>"
52 - name: PYROSCOPE_WALL_COLLECT_CPU_TIME
53 value: "true"
54 volumeMounts:
55 - name: pyroscope-inject
56 mountPath: /pyroscope
57PATCH_EOF
58)"Placeholder reference
| Placeholder | Description |
|---|---|
<DEPLOYMENT_NAME> | Name of the target Deployment |
<NAMESPACE> | Namespace the deployment lives in |
<CONTAINER_NAME> | The container in the pod spec that runs node — must match exactly, since the merge keys off container name |
<APP_NAME> | Service name to show up in profiling data |
<MW_UID> | Your Middleware project UID; used for both PYROSCOPE_SERVER_ADDRESS and PYROSCOPE_TENANT_ID — auto-filled if you're logged in and select a project above |
Why this is safe to apply directly#
volumes, initContainers, containers[].volumeMounts, and containers[].env are all strategic-merge lists keyed by name (or mountPath for volume mounts). That means this patch only adds entries — it won't remove or clobber the target container's other env vars, mounts, or init containers.
Patching spec.template changes the pod template hash, so Kubernetes automatically rolls the deployment. No separate kubectl rollout restart is required.
If the target container already sets NODE_OPTIONS, this patch will overwrite that entry rather than append to it (merge-by-name replaces the whole value). If that's the case, edit the patch so --require=/pyroscope/pyroscope-loader.js is appended to the existing NODE_OPTIONS value instead of replacing it.
Init container failures never block your app#
By default, Kubernetes treats init containers as blocking — if pyroscope-injector exited with a non-zero status (for example, if npm install failed because of no network access or a node-gyp build issue), the pod would get stuck in Init:CrashLoopBackOff and the main app container would never start. The script in this patch is written to avoid that:
- Every install step (
apt-get,npm init,npm install) is suffixed with|| true, and the script doesn't useset -e, so a failed install doesn't abort the script. pyroscope-loader.jsis always written, and the script always ends withexit 0— so the init container always succeeds and the pod always proceeds to start the app container, whether or not the profiler was installed.- The loader script itself wraps
Pyroscope.init()/Pyroscope.start()in atry/catch. If the module failed to install, or profiler startup throws for any other reason, it logs[pyroscope] profiler failed to start, continuing without profilingand lets--requirereturn normally instead of crashing the app process.
Worst case if profiling can't be enabled: you lose profiling data and see that log line — the app itself starts and runs exactly as it would without this patch.
Verifying the rollout#
Bash
1kubectl rollout status deployment/<DEPLOYMENT_NAME> -n <NAMESPACE>
2kubectl logs deployment/<DEPLOYMENT_NAME> -n <NAMESPACE> -c pyroscope-injector
3kubectl logs deployment/<DEPLOYMENT_NAME> -n <NAMESPACE> -c <CONTAINER_NAME> | grep pyroscopeYou should see [pyroscope] profiler started in the app container logs, along with the configured application name and endpoint. If profiling failed to install or start, you'll instead see [pyroscope] profiler failed to start, continuing without profiling: <reason> — the app container will still be Running as usual; only profiling data will be missing.
Environment Variables#
The following environment variables can be used to configure the Middleware APM tracker:
| Variable | Description | Possible Values |
|---|---|---|
| MW_TARGET | Sets the target URL | Your Middleware URL |
| MW_API_KEY | Sets the access token | Your Middleware API key |
| MW_PROJECT_NAME | Sets the project name | Any string |
| MW_SERVICE_NAME | Sets the service name | Any string |
| MW_APM_TRACES_ENABLED | Enables/disables trace collection | "true" or "false" |
| MW_APM_METRICS_ENABLED | Enables/disables metric collection | "true" or "false" |
| MW_CONSOLE_EXPORTER | Enables/disables console exporter | "true" or "false" |
| MW_AGENT_SERVICE | Sets the host for the APM service when using MW Agent | Valid hostname or IP address |
| OTEL_NODE_RESOURCE_DETECTORS | Specifies which resource detectors to use | Comma-separated list (see below) |
| MW_NODE_DISABLED_INSTRUMENTATIONS | Specifies which instrumentations to disable | Comma-separated list (see below) |
Customizing Resource Detection with OTEL_NODE_RESOURCE_DETECTORS#
Specifies which resource detectors to use. Each detector provides specific information about the application's environment.
Default: env, process, host, os, container, serviceinstance
Possible values: env, process, serviceinstance, os, host, container, aws, azure, gcp, none, all
Example: OTEL_NODE_RESOURCE_DETECTORS=env,process,host,aws
Disabling Specific Instrumentations with MW_NODE_DISABLED_INSTRUMENTATIONS#
Disables specific instrumentations to exclude certain types of data collection.
Note: File System (fs) instrumentation is permanently disabled due to performance issues.
Possible values: dns, net
Example: MW_NODE_DISABLED_INSTRUMENTATIONS=dns,net
Available instrumentations may vary depending on your Middleware APM version and configuration.
Configuration Options#
In addition to using environment variables, you can configure the Middleware APM tracker directly through the tracker.track() call. Here are the configuration options you can use: (env variables have higher priority)
| Option | Type | Default | Description |
|---|---|---|---|
| target | string | N/A | Sets the target URL for the APM service |
| accessToken | string | N/A | Sets the access token for authentication |
| projectName | string | N/A | Sets the project name |
| serviceName | string | N/A | Sets the service name |
| pauseTraces | boolean | false | Disables trace collection |
| pauseMetrics | boolean | false | Disables metric collection |
| consoleExporter | boolean | false | Enables console exporter |
| customResourceAttributes | object | N/A | Sets custom resource attributes |
| disabledInstrumentations | string | N/A | Specifies which instrumentations to disable (comma-separated) |
| consoleLog | boolean | false | Enables consoleLog logs collection |
| consoleError | boolean | false | Enables consoleError logs collection |
| enableSelfInstrumentation | boolean | false | Enables self-instrumentation for the profiling traces |
| enableProfiling | boolean | true | Enables profiling functionality for performance analysis |
| excludeHttpTraces | object | N/A | Exclude specific HTTP requests from tracing (see below) |
Usage Example#
index.js
1const tracker = require('@middleware.io/node-apm');
2
3tracker.track({
4 serviceName: "my-service",
5 accessToken: "your-access-token",
6 projectName: "my-project",
7 pauseTraces: false,
8 pauseMetrics: false,
9 consoleExporter: true,
10 customResourceAttributes: {
11 "app.version": "1.2.0",
12 "environment": "production"
13 },
14 disabledInstrumentations: "dns,net"
15});Excluding HTTP Requests from Tracing#
You can exclude specific HTTP requests from tracing using the excludeHttpTraces configuration. This allows you to control which incoming and outgoing HTTP requests are traced.
Programmatic Configuration#
1const tracker = require('@middleware.io/node-apm');
2tracker.track({
3 // ...other config...
4 excludeHttpTraces: {
5 incoming: {
6 urls: ['/health', '/metrics', '/status'],
7 methods: ['OPTIONS', 'HEAD']
8 },
9 outgoing: {
10 urls: ['/internal-api', '/telemetry', '/profiling'],
11 methods: ['TRACE']
12 }
13 }
14});Environment Variables#
Exclude incoming requests:
MW_EXCLUDE_INCOMING_HTTP_METHODS="OPTIONS,HEAD,TRACE"MW_EXCLUDE_INCOMING_HTTP_URLS="/health,/metrics,/status"
Exclude outgoing requests:
MW_EXCLUDE_OUTGOING_HTTP_METHODS="TRACE"MW_EXCLUDE_OUTGOING_HTTP_URLS="/internal-api,/telemetry,/profiling"
Use Cases#
- Exclude health check endpoints from tracing:
excludeHttpTraces: { incoming: { urls: ['/health', '/ready', '/live'] } }- Or:
MW_EXCLUDE_INCOMING_HTTP_URLS="/health,/ready,/live"
Graceful Shutdown (Optional)#
The Middleware APM tracker provides a sdkShutdown function for graceful shutdown. This ensures all pending telemetry data is sent before your application exits. Call tracker.sdkShutdown() when your application is about to exit to ensure all data is properly flushed.
Usage#
1const tracker = require('@middleware.io/node-apm');
2
3// Initialize the tracker
4tracker.track({
5 // Your configuration options
6});
7
8// Handle application shutdown
9process.on('SIGINT', async () => {
10 console.log('SIGINT signal received. Shutting down gracefully');
11 await tracker.sdkShutdown();
12 // Perform other cleanup tasks
13 process.exit(0);
14});Legacy Node.js Support (Node.js 10+)#
For applications running on legacy Node.js versions (10.x and above), Middleware provides a separate package with limited feature support and uses older OpenTelemetry libraries.
| Feature | Status | Notes |
|---|---|---|
| Traces | ✅ | Auto-instrumentation supported |
| Metrics | ✅ | Manual instrumentation only |
| Profiling | ❌ | Not supported |
| Logs | ✅ | Basic support |
Installation#
Install the legacy package using npm:
1npm install @middleware.io/node-apm-legacyBasic Setup#
Add the following code at the entry point of your application:
1const tracker = require('@middleware.io/node-apm-legacy');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5 target: "https://<MW_UID>.middleware.io:443",
6});
7// must come before importing any other module.
8// At the very top of the entrypoint
9const express = require('express'); // other dependencies1const tracker = require('@middleware.io/node-apm-legacy');
2tracker.track({
3 serviceName: "your-service-name",
4 accessToken: "<MW_API_KEY>",
5});
6// must come before importing any other module.
7// At the very top of the entrypoint
8const express = require('express'); // other dependenciesBasic Troubleshooting Guidelines#
If you're experiencing issues with the Middleware APM tracker or not seeing the expected data, try the following steps:
Enable Debug Logging: Set the DEBUG flag to true in your configuration or use the environment variable to get more detailed logs:
1tracker.track({ 2 // other options... 3 DEBUG: true 4});Check Configuration: Ensure all required fields (like
serviceName,accessToken,targetandaccessToken) are correctly set.Initialize Tracking Function: Make sure your tracker.track() is initialized at the very top of the main server file.
Instrumentation Issues: If specific instrumentations aren't working, check if they're accidentally disabled in your configuration.
Resource Detection: If you're not seeing expected resource attributes, verify your
OTEL_NODE_RESOURCE_DETECTORSsetting.Restart Your Application: Sometimes, a simple restart after configuration changes can resolve issues.
Agent Health Check: If you're using the Middleware Agent, the SDK performs a health check at startup. If you see a warning message about the health check failing, it could be due to:
- Incorrect value of
MW_AGENT_SERVICE
If the problem persists, verify your agent configuration and ensure it's running correctly.
- Incorrect value of
OpenTelemetry Dependency Conflicts: If you're using OpenTelemetry in your application code alongside the Middleware Node SDK, ensure that the versions are compatible. Conflicting versions can lead to unexpected behavior. Check the OpenTelemetry version used by the Middleware SDK (you can find this in the SDK's
package.json) and make sure your application's OpenTelemetry dependencies match or are compatible with this version.
If problems persist after trying these steps, please contact Middleware support with your debug logs and configuration details (with sensitive information redacted).
Frequently Asked Questions (FAQ)#
node-gyp Failing or Missing Dependencies
If node-gyp fails or dependencies are missing, run the following commands:
1sudo apt-get build-dep build-essential
2sudo apt-get install gcc g++ makeRunning Apps on Docker
If you're unable to run your Node.js app on Docker, ensure port 9319 is open to allow proper firewall configurations.
Installation successful but no data appears / Existing OpenTelemetry dependency older version issue
If you're seeing the following error, especially when DEBUG: true:
Error: @opentelemetry/api: Registration of version vx.x.x for trace does not match previously registered API vy.y.y
This error occurs due to a version conflict in OpenTelemetry dependencies:
- Your project (or one of its dependencies) is using OpenTelemetry API older version
- Our package requires OpenTelemetry API vx.x.x
This version mismatch prevents proper registration of the OpenTelemetry API, which blocks data collection.
Override the older OpenTelemetry API version by explicitly installing vx.x.x:
1npm i @opentelemetry/api@x.x.x --saveDocker Image Failing
If your Docker image build is failing, it might be due to missing build dependencies. Add the following lines to your Dockerfile:
1RUN apk --no-cache add build-base
2RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/pythonThese commands install necessary build tools and Python, which are often required for compiling native addons.
ESM Modules Auto-instrumentation not working
If your JavaScript or TypeScript code ( compiled to ES6 Modules (ESM)) and auto-instrumentation isn't working for few libraries.
- Install the required package
1npm i @opentelemetry/instrumentation@0.54.1 // or any other new version- Use this startup command:
1node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import instrument.js app.jsPyroscope profiling is not available
This warning appears because Pyroscope is an optional dependency that requires system-level build tools. It may fail if:
- Required system dependencies are missing
node-gypbuild fails- Running in Docker without build essentials
- Not supported for few machines
Solution For local development, install build dependencies:
1sudo apt-get build-dep build-essential python3
2sudo apt-get install gcc g++ makeFor Docker, add build tools in your Dockerfile:
1RUN apk --no-cache add build-base
2RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/pythonMemory Spike & Duplicate Instrumentation with --require / --import Flag
You see instrumentation loading multiple times in logs and high memory usage (DEBUG should be true) :
1Loading instrumentation for @opentelemetry/instrumentation-express
2...
3[INFO] API server running
4Loading instrumentation for @opentelemetry/instrumentation-express #Loading again!
5...This happens when using node --require or node --import flags with node services that create worker threads or child processes:
pinowith transports (pino-pretty)- Cluster mode
- Bull/BullMQ queues
- Any library using
worker_threads
Root Cause: Node.js applies --require/--import to every process, including workers, causing multiple instrumentation initializations.
Impact: 2-3x memory usage (~150-300MB instead of ~75-100MB) and random issues
Avoid:
1{
2 "scripts": {
3 "start": "node --require ./instrumentation.js ./index.js"
4 }
5}Use instead:
1// index.ts - Import instrumentation FIRST
2import './instrumentation';
3
4import express from 'express';
5// ... rest of importsOr CommonJS:
1// index.js
2require('./instrumentation');
3
4const express = require('express');
5// ... rest of codeAfter fix, instrumentation should load only once in debug logs and issue should be fixed.
Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.