NodeJS Opentelemetry Initialization Issue

TODO: collapasable, copyable, download, etc

I'm trying to hook up nodejs opentelemetry to my NextJs app, what is going wrong?

2024-05-15 14:17:40     at PeriodicExportingMetricReader.setMetricProducer (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricReader.js:50:19)
2024-05-15 14:17:40     at MeterProvider.addMetricReader (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:72:22)
2024-05-15 14:17:40     at new MeterProvider (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:39:22)
2024-05-15 14:17:40     at NodeSDK.start (/app-ui/node_modules/@opentelemetry/sdk-node/build/src/sdk.js:165:35)
2024-05-15 14:17:40     at 60666 (/app-ui/.next/server/chunks/666.js:1:540)
2024-05-15 14:17:40     at Function.t (/app-ui/.next/server/webpack-runtime.js:1:128)
2024-05-15 14:17:40     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2024-05-15 14:17:40     at async Module.o (/app-ui/.next/server/instrumentation.js:1:96)
2024-05-15 14:17:40     at async NextNodeServer.prepareImpl (/app-ui/node_modules/next/dist/server/next-server.js:435:17)
2024-05-15 14:17:40 Error: An error occurred while loading instrumentation hook: MetricReader can not be bound to a MeterProvider again.
2024-05-15 14:17:40     at PeriodicExportingMetricReader.setMetricProducer (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricReader.js:50:19)
2024-05-15 14:17:40     at MeterProvider.addMetricReader (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:72:22)
2024-05-15 14:17:40     at new MeterProvider (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:39:22)
2024-05-15 14:17:40     at NodeSDK.start (/app-ui/node_modules/@opentelemetry/sdk-node/build/src/sdk.js:165:35)
2024-05-15 14:17:40     at 60666 (/app-ui/.next/server/chunks/666.js:1:540)
2024-05-15 14:17:40     at Function.t (/app-ui/.next/server/webpack-runtime.js:1:128)
2024-05-15 14:17:40     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2024-05-15 14:17:40     at async Module.o (/app-ui/.next/server/instrumentation.js:1:96)
2024-05-15 14:17:40     at async NextNodeServer.prepareImpl (/app-ui/node_modules/next/dist/server/next-server.js:435:17)
2024-05-15 14:17:40 Error: An error occurred while loading instrumentation hook: MetricReader can not be bound to a MeterProvider again.
2024-05-15 14:17:40     at PeriodicExportingMetricReader.setMetricProducer (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricReader.js:50:19)
2024-05-15 14:17:40     at MeterProvider.addMetricReader (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:72:22)
2024-05-15 14:17:40     at new MeterProvider (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:39:22)
2024-05-15 14:17:40     at NodeSDK.start (/app-ui/node_modules/@opentelemetry/sdk-node/build/src/sdk.js:165:35)
2024-05-15 14:17:40     at 60666 (/app-ui/.next/server/chunks/666.js:1:540)
2024-05-15 14:17:40     at Function.t (/app-ui/.next/server/webpack-runtime.js:1:128)
2024-05-15 14:17:40     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2024-05-15 14:17:40     at async Module.o (/app-ui/.next/server/instrumentation.js:1:96)
2024-05-15 14:17:40     at async NextNodeServer.prepareImpl (/app-ui/node_modules/next/dist/server/next-server.js:435:17)
2024-05-15 14:17:40 Error: An error occurred while loading instrumentation hook: MetricReader can not be bound to a MeterProvider again.
2024-05-15 14:17:40     at PeriodicExportingMetricReader.setMetricProducer (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricReader.js:50:19)
2024-05-15 14:17:40     at MeterProvider.addMetricReader (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:72:22)
2024-05-15 14:17:40     at new MeterProvider (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:39:22)
2024-05-15 14:17:40     at NodeSDK.start (/app-ui/node_modules/@opentelemetry/sdk-node/build/src/sdk.js:165:35)
2024-05-15 14:17:40     at 60666 (/app-ui/.next/server/chunks/666.js:1:540)
2024-05-15 14:17:40     at Function.t (/app-ui/.next/server/webpack-runtime.js:1:128)
2024-05-15 14:17:40     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2024-05-15 14:17:40     at async Module.o (/app-ui/.next/server/instrumentation.js:1:96)
2024-05-15 14:17:40     at async NextNodeServer.prepareImpl (/app-ui/node_modules/next/dist/server/next-server.js:435:17)
2024-05-15 14:17:40 Error: An error occurred while loading instrumentation hook: MetricReader can not be bound to a MeterProvider again.
2024-05-15 14:17:40     at PeriodicExportingMetricReader.setMetricProducer (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricReader.js:50:19)
2024-05-15 14:17:40     at MeterProvider.addMetricReader (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:72:22)
2024-05-15 14:17:40     at new MeterProvider (/app-ui/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.js:39:22)
2024-05-15 14:17:40     at NodeSDK.start (/app-ui/node_modules/@opentelemetry/sdk-node/build/src/sdk.js:165:35)
2024-05-15 14:17:40     at 60666 (/app-ui/.next/server/chunks/666.js:1:540)
2024-05-15 14:17:40     at Function.t (/app-ui/.next/server/webpack-runtime.js:1:128)
2024-05-15 14:17:40     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2024-05-15 14:17:40     at async Module.o (/app-ui/.next/server/instrumentation.js:1:96)
2024-05-15 14:17:40     at async NextNodeServer.prepareImpl (/app-ui/node_modules/next/dist/server/next-server.js:435:17)

My code

 * Copyright (c) 2023, Deductive AI, Inc. All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * Deductive AI, Inc. You shall not disclose such confidential
 * information and shall use it only in accordance with the terms of
 * the license agreement you entered into with Deductive AI, Inc.
 */

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import opentelemetry from '@opentelemetry/api';

const metricReader = new PeriodicExportingMetricReader({
  exporter:  new ConsoleMetricExporter(), // new OTLPTraceExporter(),
  exportIntervalMillis: 10000, // 60000
});


const resource = new Resource({
  [SEMRESATTRS_SERVICE_NAME]: "app-ui",
});

const sdk = new NodeSDK({
  metricReader: metricReader,
  resource: resource,
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),    // new OTLPTraceExporter()
  
});

const appUiMeterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader]
}); 

opentelemetry.metrics.setGlobalMeterProvider(appUiMeterProvider);

sdk.start();
console.log('instrumentation started');```The user is working on integrating OpenTelemetry with a Next.js application using Node.js.Model set context updated.The error you're encountering suggests that the `MetricReader` is being bound to the `MeterProvider` more than once. This can happen if the same instance of `MetricReader` is being reused or if the `MeterProvider` is being re-initialized.

Here are a few steps to help you resolve the issue:

1. **Ensure Single Initialization:** Make sure that your `MeterProvider` and `MetricReader` are initialized only once. You should avoid creating multiple instances or re-binding them.

2. **Check for Duplicate Code:** Verify that there are no duplicate initializations in your application. For example, if you have the initialization code in multiple places or in a loop, it can cause this issue.

3. **Avoid Re-binding:** Ensure that you are not re-binding the `MetricReader` to the `MeterProvider`. This can happen if you are calling the initialization code multiple times.

Here is an updated version of your code to ensure proper initialization:

```javascript
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import opentelemetry from '@opentelemetry/api';

// Create MetricReader instance
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

// Define resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "app-ui",
});

// Initialize NodeSDK with metricReader and resource
const sdk = new NodeSDK({
  resource: resource,
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
  metricReader: metricReader,
});

// Create and set global MeterProvider
const appUiMeterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader],
});
opentelemetry.metrics.setGlobalMeterProvider(appUiMeterProvider);

// Start the SDK
sdk.start().then(() => {
  console.log('Instrumentation started');
}).catch((error) => {
  console.error('Error starting instrumentation:', error);
});

In this code:

  • Ensure that MetricReader is created and assigned only once.
  • Avoid re-binding the MetricReader to the MeterProvider.
  • Use then and catch with sdk.start() to handle asynchronous initialization properly.

By ensuring single initialization and avoiding re-binding, this should help resolve the issue you're encountering.Your code seems identical to mine except for the then catch partThe key issue is ensuring that MetricReader is not bound to the MeterProvider multiple times. The duplication can occur if you are trying to set the metricReader both in the NodeSDK configuration and separately for the MeterProvider.

To resolve this, ensure you initialize the MetricReader and MeterProvider correctly and avoid redundancy.

Here is an updated approach to avoid the potential duplication:

  1. Remove Redundant MetricReader Assignment: Only bind the metricReader once either in the NodeSDK configuration or directly in the MeterProvider but not both.

  2. Ensure Initialization is not duplicated: Make sure the initialization block does not get executed multiple times.

Here's the modified code:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, PeriodicExportingMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics";
import opentelemetry from '@opentelemetry/api';

// Define the resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "app-ui",
});

// Initialize MeterProvider and MetricReader
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

const appUiMeterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader],
});

opentelemetry.metrics.setGlobalMeterProvider(appUiMeterProvider);

// Initialize and start the NodeSDK
const sdk = new NodeSDK({
  resource: resource,
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
  metricReader: metricReader, // Only bind it once here
});

sdk.start().then(() => {
  console.log('Instrumentation started');
}).catch((error) => {
  console.error('Error starting instrumentation:', error);
});

In this approach:

  • MetricReader is only assigned within the NodeSDK configuration.
  • MeterProvider is configured separately and set globally to avoid redundancy.
  • Ensure the initialization does not run multiple times by wrapping the setup code within a guard if needed (not shown here but useful for larger apps).

If you still encounter issues, consider adding checks to see if sdk.start() is called multiple times or wrapping the initialization code in a module or a singleton pattern to prevent duplicate invocations.Your sample code has const appUiMeterProvider = new MeterProvider({ resource: resource, readers: [metricReader], }); and const sdk = new NodeSDK({ resource: resource, spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()), metricReader: metricReader, // Only bind it once here });

Isn't that binding twice? Isn't that what I had originally?You're right, I did not resolve the duplication properly. To fix this, you should avoid binding the metricReader in both MeterProvider and NodeSDK. Here’s the corrected approach to ensure the metricReader is bound only once.

Corrected Code:

  1. Bind metricReader only once in NodeSDK.
  2. Remove the binding in the MeterProvider.

Here is the revised code:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, PeriodicExportingMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics";
import opentelemetry from '@opentelemetry/api';

// Define the resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "app-ui",
});

// Initialize MetricReader
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

// Initialize and start the NodeSDK with MetricReader
const sdk = new NodeSDK({
  resource: resource,
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
  metricReader: metricReader, // Bind it here
});

// Remove the separate MeterProvider setup
// Ensure the global MeterProvider uses the NodeSDK configuration
opentelemetry.metrics.setGlobalMeterProvider(sdk.getMeterProvider());

sdk.start().then(() => {
  console.log('Instrumentation started');
}).catch((error) => {
  console.error('Error starting instrumentation:', error);
});

In this corrected version:

  • The MetricReader is bound only within the NodeSDK configuration.
  • The global MeterProvider is set using sdk.getMeterProvider(), avoiding the need to rebind the MetricReader.

This approach ensures that the MetricReader is not bound multiple times, resolving the error you encountered.sdk.getMeterProvider() does not exist as an APIApologies for the confusion. Let's resolve this by properly managing the MetricReader and ensuring no duplication occurs. We'll bind the MetricReader within the MeterProvider and set it globally without rebinding it in the NodeSDK.

Here's the updated approach:

  1. Bind the MetricReader within the MeterProvider and set it globally.
  2. Do not bind the MetricReader in the NodeSDK configuration.

Corrected Code:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, PeriodicExportingMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics";
import opentelemetry from '@opentelemetry/api';

// Define the resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "app-ui",
});

// Initialize MetricReader
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

// Initialize MeterProvider with MetricReader
const appUiMeterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader],
});

// Set the global MeterProvider
opentelemetry.metrics.setGlobalMeterProvider(appUiMeterProvider);

// Initialize and start the NodeSDK without MetricReader
const sdk = new NodeSDK({
  resource: resource,
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
  // Do not bind metricReader here to avoid duplication
});

sdk.start().then(() => {
  console.log('Instrumentation started');
}).catch((error) => {
  console.error('Error starting instrumentation:', error);
});

In this version:

  • The MetricReader is only bound within the MeterProvider.
  • The NodeSDK does not include the metricReader to avoid the binding duplication.
  • The global MeterProvider is set using opentelemetry.metrics.setGlobalMeterProvider(appUiMeterProvider).

This setup should prevent the MetricReader can not be bound to a MeterProvider again error.I've got metrics working. How would you write this instrumentation?

for each route

latency

error count

total, bucketed by http status code

success countTo instrument metrics for each route, including latency, error count, total requests bucketed by HTTP status code, and success count, you can use OpenTelemetry's API for metrics. Here’s an example of how you might do this in a Next.js application using Node.js and OpenTelemetry:

Step-by-Step Implementation

  1. Setup Metrics in OpenTelemetry:

    • Define meters and instruments for tracking latency, error count, request count (bucketed by HTTP status code), and success count.
  2. Middleware for Metrics Collection:

    • Create middleware to capture metrics for each request.
  3. Recording Metrics:

    • Use the middleware to record the necessary metrics for each route.

Example Code

1. Setup Metrics in OpenTelemetry

import { MeterProvider, PeriodicExportingMetricReader, ConsoleMetricExporter } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';

// Enable diagnostic logging (optional)
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// Define the resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'app-ui',
});

// Initialize MetricReader
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

// Initialize MeterProvider with MetricReader
const meterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader],
});

// Set the global MeterProvider
opentelemetry.metrics.setGlobalMeterProvider(meterProvider);

// Get a meter
const meter = meterProvider.getMeter('app-ui');

// Define instruments
const requestLatency = meter.createHistogram('http_request_latency', {
  description: 'Duration of HTTP requests in milliseconds',
});

const requestCount = meter.createCounter('http_request_count', {
  description: 'Count of HTTP requests',
});

const errorCount = meter.createCounter('http_request_errors', {
  description: 'Count of HTTP request errors',
});

const successCount = meter.createCounter('http_request_successes', {
  description: 'Count of successful HTTP requests',
});

2. Middleware for Metrics Collection

import { context, trace } from '@opentelemetry/api';

// Middleware to record metrics
const metricsMiddleware = async (req, res, next) => {
  const start = process.hrtime();

  res.on('finish', () => {
    const duration = process.hrtime(start);
    const durationInMillis = duration[0] * 1000 + duration[1] / 1e6;

    const route = req.route ? req.route.path : 'unknown_route';
    const method = req.method;
    const statusCode = res.statusCode;

    // Record latency
    requestLatency.record(durationInMillis, {
      route,
      method,
      status_code: statusCode,
    });

    // Increment request count
    requestCount.add(1, {
      route,
      method,
      status_code: statusCode,
    });

    // Increment error or success count
    if (statusCode >= 400) {
      errorCount.add(1, {
        route,
        method,
        status_code: statusCode,
      });
    } else {
      successCount.add(1, {
        route,
        method,
        status_code: statusCode,
      });
    }
  });

  next();
};

// Use the middleware in your Next.js app
export default function handler(req, res) {
  metricsMiddleware(req, res, () => {
    // Your route handler logic here
    res.status(200).json({ message: 'Hello, World!' });
  });
}

3. Apply Middleware in Next.js

To apply the middleware in a Next.js application, you need to add it to your custom server if you're using one. Here's how you can integrate it into a custom Next.js server using Express.js:

const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  // Use metrics middleware
  server.use(metricsMiddleware);

  // Custom route handler
  server.get('/api/hello', (req, res) => {
    res.status(200).json({ message: 'Hello, World!' });
  });

  // Default request handler
  server.all('*', (req, res) => {
    return handle(req, res);
  });

  server.listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

Explanation:

  • MeterProvider and Instruments: We initialize the MeterProvider, define the necessary instruments (counters and histograms), and set the global meter provider.
  • Metrics Middleware: Middleware captures the start time of each request, calculates the duration when the response finishes, and records metrics accordingly.
  • Integration with Next.js: Metrics middleware is applied to the server, and custom route handlers are set up.

This setup will enable you to capture latency, error count, request count by HTTP status code, and success count for each route in your Next.js application using OpenTelemetry.const successCount = meter.createCounter... should this only run once? Like should I put that in a TypeScript module and export it? Then import it from the middleware?Yes, to ensure that the metrics instruments (Counter, Histogram, etc.) are only created once and can be reused throughout your application, it's a good practice to initialize them in a separate module and then import them wherever needed.

Here's how you can structure your code to ensure that the instruments are only created once and are reused across your application:

Step-by-Step Implementation

  1. Create a Metrics Module:

    • Define and export the instruments in a separate module.
  2. Import and Use Instruments in Middleware:

    • Import the instruments in your middleware and use them for recording metrics.

Example Code

1. Create a Metrics Module

Create a file metrics.ts (or metrics.js if not using TypeScript) to initialize and export the metrics instruments.

// metrics.ts

import { MeterProvider, PeriodicExportingMetricReader, ConsoleMetricExporter } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import opentelemetry from '@opentelemetry/api';

// Define the resource with service name
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'app-ui',
});

// Initialize MetricReader
const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 10000,
});

// Initialize MeterProvider with MetricReader
const meterProvider = new MeterProvider({
  resource: resource,
  readers: [metricReader],
});

// Set the global MeterProvider
opentelemetry.metrics.setGlobalMeterProvider(meterProvider);

// Get a meter
const meter = meterProvider.getMeter('app-ui');

// Define and export instruments
export const requestLatency = meter.createHistogram('http_request_latency', {
  description: 'Duration of HTTP requests in milliseconds',
});

export const requestCount = meter.createCounter('http_request_count', {
  description: 'Count of HTTP requests',
});

export const errorCount = meter.createCounter('http_request_errors', {
  description: 'Count of HTTP request errors',
});

export const successCount = meter.createCounter('http_request_successes', {
  description: 'Count of successful HTTP requests',
});

2. Middleware for Metrics Collection

Import the instruments in your middleware and use them for recording metrics.

// metricsMiddleware.ts

import { requestLatency, requestCount, errorCount, successCount } from './metrics';

const metricsMiddleware = async (req, res, next) => {
  const start = process.hrtime();

  res.on('finish', () => {
    const duration = process.hrtime(start);
    const durationInMillis = duration[0] * 1000 + duration[1] / 1e6;

    const route = req.route ? req.route.path : 'unknown_route';
    const method = req.method;
    const statusCode = res.statusCode;

    // Record latency
    requestLatency.record(durationInMillis, {
      route,
      method,
      status_code: statusCode,
    });

    // Increment request count
    requestCount.add(1, {
      route,
      method,
      status_code: statusCode,
    });

    // Increment error or success count
    if (statusCode >= 400) {
      errorCount.add(1, {
        route,
        method,
        status_code: statusCode,
      });
    } else {
      successCount.add(1, {
        route,
        method,
        status_code: statusCode,
      });
    }
  });

  next();
};

export default metricsMiddleware;

3. Apply Middleware in Next.js

Integrate the middleware into your Next.js custom server or API routes.

// server.js

const express = require('express');
const next = require('next');
const metricsMiddleware = require('./metricsMiddleware'); // Adjust the path as needed

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  // Use metrics middleware
  server.use(metricsMiddleware);

  // Custom route handler
  server.get('/api/hello', (req, res) => {
    res.status(200).json({ message: 'Hello, World!' });
  });

  // Default request handler
  server.all('*', (req, res) => {
    return handle(req, res);
  });

  server.listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

Explanation:

  • Metrics Module (metrics.ts): This module initializes the metrics instruments and exports them for reuse. This ensures that the instruments are only created once.
  • Middleware (metricsMiddleware.ts): The middleware imports the instruments from the metrics module and uses them to record metrics for each request.
  • Custom Server (server.js): The custom server integrates the middleware to collect metrics for each incoming request.

By organizing your code in this way, you ensure that the metrics instruments are only created once and can be easily reused across your application, promoting better modularity and maintainability.

NodeJS Opentelemetry Initialization Issue created at Tue Jan 20 1970 20:36:47 GMT+0000 (Coordinated Universal Time)

Related Chats

NodeJS Opentelemetry Initialization Issue 1.000

Configure OpenTelemetry SDK. 0.540

Metrics Middleware for Next.js 0.478

Next.js Middleware & HOC 0.450

Check Closed Controller State 0.380

Teste de Unidade Vite 0.365

Async Handler Type Declaration 0.351

Docker ENV Variables Not Set 0.340

SwApp para Compartir Datos 0.321

Prisma HMR Disconnect Solution 0.305