---
title: "Node.js"
description: "Learn how to configure Middleware APM for Node.js apps to monitor performance, detect issues, and improve application stability."
section: "APM"
breadcrumb: "APM > Node.JS"
canonical: "https://docs.middleware.io/get-started/apm/nodejs"
---
# 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](https://github.com/middleware-labs/demo-apm-node).

## Prerequisites

1. **Node.js 18.17.1+**: Verify with `node --version`
2. **Python Version 3.8+** Verify with `python3 --version`

## Installation

### 1 Install `Node.js` APM Package

Run the following command in your terminal:

```bash
npm install @middleware.io/node-apm --save
```

### 2 Setup Options

#### With Host

> **Note:** Host based APM requires the Middleware Agent which can be installed using the relevant OS based instructions. [here](https://docs.middleware.io/get-started/agent).

#### 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.

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
});
// must come before importing any other module.
// At the very top of the entrypoint
const express = require('express'); // other dependencies
```

```javascript
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
});
// must come before importing any other module.
// At the very top of the entrypoint
```

If 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.

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

```javascript
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

#### Method 2: Add the tracker with command line arguments

Create a file named `instrument.js or instrument.ts`  in your project root:

```javascript
// instrument.js
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

```javascript
// instrument.ts
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

To ensure the tracker is initialized before any other code runs, use the `--require` flag when starting your Node.js application:

```bash
node --require ./instrument.js your-main-app.js
```

```bash
npx ts-node --require ./instrumentation.ts your-main-app.ts
```

#### Serverless

#### 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.

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
});
// must come before importing any other module.
// At the very top of the entrypoint
const express = require('express'); // other dependencies
```

```javascript
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
});
// must come before importing any other module.
// At the very top of the entrypoint
```

If 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.

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

```javascript
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

#### Method 2: Add the tracker with command line arguments

Create a file named `instrument.js or instrument.ts`  in your project root:

```javascript
// instrument.js
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

```javascript
// instrument.ts
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
    customResourceAttributes: {
      "app.version": "1.2.0",
    }
});
```

To ensure the tracker is initialized before any other code runs, use the `--require` flag when starting your Node.js application:

```bash
node --require ./instrument.js your-main-app.js
```

```bash
npx ts-node --require ./instrumentation.ts your-main-app.ts
```

### 3 Container Variables

> **Note:** 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
MW_AGENT_SERVICE=<DOCKER_BRIDGE_GATEWAY_ADDRESS>
```

> **Note:** 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](https://docs.docker.com/engine/network)

#### Kubernetes

```bash
kubectl get service --all-namespaces | grep mw-service
```

Then add the following environment variable to your application deployment YAML file.

```javascript
MW_AGENT_SERVICE=mw-service.mw-agent-ns.svc.cluster.local
```

#### 4 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:

```javascript
router.post("/email/send", async (req, res) => {
  const email  = req.body.email;
  tracker.setAttribute("email", email);
  res.json({ status: "email sent" });
});
```

#### Custom Logs

To ingest custom logs into Middleware, utilize the following functions inside your logging method based on desired log severity levels.

```javascript
tracker.info('Info sample');
tracker.warn('Warning sample');
tracker.debug('Debugging Sample');
tracker.error('Error Sample');
```

```javascript
tracker.info('Info sample');
tracker.warn('Warning sample');
tracker.debug('Debugging Sample');
tracker.error('Error Sample');
```

To add stack traces along with the error log, use the following error tracking function.

```javascript
tracker.error(new Error('Error sample with stack trace'));
```

```javascript
tracker.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:

```javascript
const tracker = require("@middleware.io/node-apm");
// Initialize the tracker
tracker.track({
  // Service name differentiates your applications in the APM section of the Middleware platform.
  serviceName: "<your_service_name_here>",
  // You can find your access token in the Middleware platform by going to the installation page.
  accessToken: "your-initial-token",
  // The target is your Middleware platform URL.
  // When you login to the Middleware platform, you can find your target URL in the URL bar.
  target: "<your_mw_url_here>",
});
const express = require("express");
const app = express();

// Create a tracer, used to create spans.
const tracer = tracker.getTracer();

// Create a meter, used to create custom metrics.
const meter = tracker.getMeter();

// Create a counter metric.
// For a list of other metric types, see the opentelemetry metrics API
// documentation. (https://opentelemetry.io/docs/specs/otel/metrics/api/)
let counter = meter.createCounter("health-check-counter");

/**
 * Generate a random integer between 0 and the range provided.
 * @param {Number} range
 * @returns {Number}
 */
function randInt(range) {
  // Create a span. A span must be closed.
  return tracer.startActiveSpan("randInt", (span) => {
    const randomInt = Math.floor(Math.random() * range);
    // Be sure to end the span
    span.end();
    return randomInt;
  });
}

app.get("/randInt", (req, res) => {
  // Calling the randInt function will generate a span.
  const randomInt = randInt(100);
  res.json({ number: randomInt });
});

app.get("/health", (req, res) => {
  // Increment the health-check-counter by 1.
  counter.add(1);
  res.json({ status: "ok" });
});

app.listen(5051, () => {
  console.log("Server is running on port 5051");
});
```

```javascript
import * as tracker from "@middleware.io/node-apm";
// Initialize the tracker
tracker.track({
  // Service name differentiates your applications in the APM section of the Middleware platform.
  serviceName: "<your_service_name_here>",
  // You can find your access token in the Middleware platform by going to the installation page.
  accessToken: "wkwpxrgcnaiebzhfivqesuotvmpigvmrderg",
  // The target is your Middleware platform URL.
  // When you login to the Middleware platform, you can find your target URL in the URL bar.
  target: "<your_mw_url_here>",
});
import express from "express";
const app = express();
// Create a tracer, used to create spans.
const tracer = tracker.getTracer("test-tracer");

// Create a meter, used to create custom metrics.
const meter = tracker.getMeter("test-meter");

// Create a counter metric.
// For a list of other metric types, see the opentelemetry metrics API
// documentation. (https://opentelemetry.io/docs/specs/otel/metrics/api/)
let counter = meter.createCounter("health-check-counter");

/**
 * Generate a random integer between 0 and the range provided.
 * @param {Number} range
 * @returns {Number}
 */
function randInt(range: number) {
  // Create a span. A span must be closed.
  return tracer.startActiveSpan("randInt", (span) => {
    const randomInt = Math.floor(Math.random() * range);
    // Be sure to end the span
    span.end();
    return randomInt;
  });
}

app.get("/randInt", (req, res) => {
  // Calling the randInt function will generate a span.
  const randomInt = randInt(100);
  res.json({ number: randomInt });
});

app.get("/health", (req, res) => {
  // Increment the health-check-counter by 1.
  counter.add(1);
  res.json({ status: "ok" });
});

app.listen(5051, () => {
  console.log("Server is running on port 5051");
});
```

#### 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.

```javascript
app.get('/error', function (req, res) {
    try{
        throw new Error('oh error!');
    }catch (e) {
    track.errorRecord(e)
    }
    res.status(500).send("wrong");
});
```

```javascript
app.get('/error', function (req, res) {
    try{
        throw new Error('oh error!');
    }catch (e) {
    track.errorRecord(e as Error)
    }
    res.status(500).send("wrong");
});
```

## Continuous Profiling

[Continuous profiling](/features/apm/profiling) captures real-time performance insights. Access this feature under the [**APM → Continuous Profiling**](https://app.middleware.io/apm/profiling) section in the Middleware platform.

> **Note:** 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.

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    enableProfiling: true,
});
```

```javascript
import * as tracker from '@middleware.io/node-apm';
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    enableProfiling: true,
});
```

### 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/nodejs` into an `emptyDir` volume shared with the app container, and writes a small loader script that calls `Pyroscope.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 patch` using strategic merge, so it only **adds** to the existing `volumes`, `initContainers`, `env`, and `volumeMounts` lists — it does not remove or replace anything else already configured on the deployment.

#### Prerequisites

- `kubectl` pointed at the target cluster/context.
- The name of the `Deployment` and `Namespace` you 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
kubectl patch deployment <DEPLOYMENT_NAME> \
  -n <NAMESPACE> \
  --type strategic \
  --patch "$(cat <<'PATCH_EOF'
spec:
  template:
    spec:
      volumes:
        - name: pyroscope-inject
          emptyDir: {}
      initContainers:
        - name: pyroscope-injector
          image: node:20-bookworm-slim
          command: ["/bin/sh", "-c"]
          args:
            - |
              set -ux
              apt-get update || true
              apt-get install -y --no-install-recommends python3 make g++ || true
              cd /pyroscope
              npm init -y || true
              npm install @pyroscope/nodejs@0.2.6 || true
              cat > /pyroscope/pyroscope-loader.js <<'JS_EOF'
              try {
                const Pyroscope = require('/pyroscope/node_modules/@pyroscope/nodejs');
                Pyroscope.init({
                  serverAddress: process.env.PYROSCOPE_SERVER_ADDRESS,
                  appName: process.env.PYROSCOPE_APPLICATION_NAME,
                  tenantID: process.env.PYROSCOPE_TENANT_ID,
                  wall: { collectCpuTime: process.env.PYROSCOPE_WALL_COLLECT_CPU_TIME === 'true' }
                });
                Pyroscope.start();
              } catch (err) {
                console.error('[pyroscope] profiler failed to start, continuing without profiling:', err.message);
              }
              JS_EOF
              exit 0
          volumeMounts:
            - name: pyroscope-inject
              mountPath: /pyroscope
      containers:
        - name: <CONTAINER_NAME>
          env:
            - name: NODE_OPTIONS
              value: "--require=/pyroscope/pyroscope-loader.js"
            - name: PYROSCOPE_APPLICATION_NAME
              value: "<APP_NAME>"
            - name: PYROSCOPE_SERVER_ADDRESS
              value: "https://<MW_UID>.middleware.io/profiling"
            - name: PYROSCOPE_TENANT_ID
              value: "<MW_UID>"
            - name: PYROSCOPE_WALL_COLLECT_CPU_TIME
              value: "true"
          volumeMounts:
            - name: pyroscope-inject
              mountPath: /pyroscope
PATCH_EOF
)"
```

##### 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.

> **Warning:** 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 use `set -e`, so a failed install doesn't abort the script.
- `pyroscope-loader.js` is always written, and the script always ends with `exit 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 a `try`/`catch`. If the module failed to install, or profiler startup throws for any other reason, it logs `[pyroscope] profiler failed to start, continuing without profiling` and lets `--require` return 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
kubectl rollout status deployment/<DEPLOYMENT_NAME> -n <NAMESPACE>
kubectl logs deployment/<DEPLOYMENT_NAME> -n <NAMESPACE> -c pyroscope-injector
kubectl logs deployment/<DEPLOYMENT_NAME> -n <NAMESPACE> -c <CONTAINER_NAME> | grep pyroscope
```

You 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`

> **Warning:** 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

```javascript
const tracker = require('@middleware.io/node-apm');

tracker.track({
  serviceName: "my-service",
  accessToken: "your-access-token",
  projectName: "my-project",
  pauseTraces: false,
  pauseMetrics: false,
  consoleExporter: true,
  customResourceAttributes: {
    "app.version": "1.2.0",
    "environment": "production"
  },
  disabledInstrumentations: "dns,net"
});
```

```javascript
import * as  tracker from '@middleware.io/node-apm';

tracker.track({
  serviceName: "my-service",
  accessToken: "your-access-token",
  projectName: "my-project",
  pauseTraces: false,
  pauseMetrics: false,
  consoleExporter: true,
  customResourceAttributes: {
    "app.version": "1.2.0",
    "environment": "production"
  },
  disabledInstrumentations: "dns,net"
});
```

### 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

```javascript
const tracker = require('@middleware.io/node-apm');
tracker.track({
  // ...other config...
  excludeHttpTraces: {
    incoming: {
      urls: ['/health', '/metrics', '/status'],
      methods: ['OPTIONS', 'HEAD']
    },
    outgoing: {
      urls: ['/internal-api', '/telemetry', '/profiling'],
      methods: ['TRACE']
    }
  }
});
```

#### 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

```javascript
const tracker = require('@middleware.io/node-apm');

// Initialize the tracker
tracker.track({
  // Your configuration options
});

// Handle application shutdown
process.on('SIGINT', async () => {
  console.log('SIGINT signal received. Shutting down gracefully');
  await tracker.sdkShutdown();
  // Perform other cleanup tasks
  process.exit(0);
});
```

## 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:

```bash
npm install @middleware.io/node-apm-legacy
```

#### Basic Setup

Add the following code at the entry point of your application:

#### Serverless

```javascript
const tracker = require('@middleware.io/node-apm-legacy');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
    target: "your-initial-uid",
});
// must come before importing any other module.
// At the very top of the entrypoint
const express = require('express'); // other dependencies
```

#### With Host

```javascript
const tracker = require('@middleware.io/node-apm-legacy');
tracker.track({
    serviceName: "your-service-name",
    accessToken: "your-initial-token",
});
// must come before importing any other module.
// At the very top of the entrypoint
const express = require('express'); // other dependencies
```

## Basic Troubleshooting Guidelines

If you're experiencing issues with the Middleware APM tracker or not seeing the expected data, try the following steps:

1. **Enable Debug Logging**: Set the DEBUG flag to true in your configuration or use the environment variable to get more detailed logs:

   ```javascript
   tracker.track({
     // other options...
     DEBUG: true
   });
   ```
2. **Check Configuration**: Ensure all required fields (like `serviceName` , `accessToken` , `target` and `accessToken`) are correctly set.
3. **Initialize Tracking Function**: Make sure your tracker.track() is initialized at the very top of the main server file.
4. **Instrumentation Issues**: If specific instrumentations aren't working, check if they're accidentally disabled in your configuration.
5. **Resource Detection**: If you're not seeing expected resource attributes, verify your `OTEL_NODE_RESOURCE_DETECTORS` setting.
6. **Restart Your Application**: Sometimes, a simple restart after configuration changes can resolve issues.
7. **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.
8. **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:

```bash
sudo apt-get build-dep build-essential
sudo apt-get install gcc g++ make
```

#### Running 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:

```bash
npm i @opentelemetry/api@x.x.x --save
```

#### Docker Image Failing

If your Docker image build is failing, it might be due to missing build dependencies. Add the following lines to your Dockerfile:

```bash
RUN apk --no-cache add build-base
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
```

These 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.

1. Install the required  package

```bash
npm i @opentelemetry/instrumentation@0.54.1 // or any other new version
```

2. Use this startup command:

```bash
node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import instrument.js app.js
```

#### Pyroscope 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-gyp` build fails
- Running in Docker without build essentials
- Not supported for few machines

Solution
For local development, install build dependencies:

```bash
sudo apt-get build-dep build-essential python3
sudo apt-get install gcc g++ make
```

For Docker, add build tools in your Dockerfile:

```bash
RUN apk --no-cache add build-base
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
```

#### Memory Spike & Duplicate Instrumentation with --require / --import Flag

You see instrumentation loading multiple times in logs and high memory usage (DEBUG should be true) :

```bash
Loading instrumentation for @opentelemetry/instrumentation-express
...
[INFO] API server running
Loading instrumentation for @opentelemetry/instrumentation-express  #Loading again!
...
```

This happens when using `node --require` or `node --import` flags with node services that create worker threads or child processes:

- `pino` with 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:**

```json
{
  "scripts": {
    "start": "node --require ./instrumentation.js ./index.js"
  }
}
```

**Use instead:**

```typescript
// index.ts - Import instrumentation FIRST
import './instrumentation';

import express from 'express';
// ... rest of imports
```

Or CommonJS:

```javascript
// index.js
require('./instrumentation');

const express = require('express');
// ... rest of code
```

After 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 page or join our Slack channel.
