nextjs - 💡(How to fix) Fix Telemetry broken when serverExternalPackages used for external telemetry package after upgrade from next15 next v16.0.2 [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86206Fetched 2026-04-08 02:12:01
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Author
Timeline (top)
labeled ×2closed ×1commented ×1issue_type_added ×1

Error Message

} catch (error) { console.error("Failed to start OpenTelemetry SDK:", error); throw error; } catch (error) { console.error("Failed to shut down OpenTelemetry SDK:", error); throw error;

Code Example

import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { HostMetrics } from "@opentelemetry/host-metrics";
import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { BaseInstrumentation } from "./base";
import { InstrumentationConfig } from "./types";
import { DeploymentMetrics } from "./metrics/deployment-metrics";
import { HandlesAndRequestsMetrics } from "./metrics/handles-and-requests-metrics";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { ExpressLayerType } from "@opentelemetry/instrumentation-express";
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { BullMQInstrumentation } from "@appsignal/opentelemetry-instrumentation-bullmq";

interface Startable {
  start(): void;
}

export class NodeInstrumentation extends BaseInstrumentation {
  private sdk: NodeSDK;
  private components: Startable[] = [];

  constructor(config: InstrumentationConfig) {
    super(config);
    this.sdk = this.initializeNodeSDK(config);
    this.components.push(this.sdk);
  }

  private initializeNodeSDK(config: InstrumentationConfig): NodeSDK {
    return new NodeSDK({
      sampler: config.tracerProvider?.sampler,
      spanProcessors: [
        ...(config.tracerProvider?.spanProcessors ?? []),
        this.defaultSpanProcessor(),
      ],
      contextManager: config.contextManager,
      textMapPropagator: config.textMapPropagator,
      metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter(this.exporterConfigs.metrics),
      }),
      logRecordProcessors: [
        new BatchLogRecordProcessor(
          new OTLPLogExporter(this.exporterConfigs.logs)
        ),
      ],
      instrumentations: [
        getNodeAutoInstrumentations({
          "@opentelemetry/instrumentation-http": {
            ignoreIncomingRequestHook: (request) => {
              return (
                this.config.ignoreUrls?.some((pattern) =>
                  request.url?.match(pattern)
                ) ?? false
              );
            },
            ...this.config.httpInstrumentation,
          },
          "@opentelemetry/instrumentation-express": {
            ignoreLayers: this.config.ignoreUrls,
            requestHook: (span, info) => {
              if (info.layerType === ExpressLayerType.REQUEST_HANDLER) {
                const route = info.route || info.request.route?.path;

                span.setAttribute("http.route", route);
                span.setAttribute("http.target", route);
              }
            },
          },
        }),
        ...(this.config.instrumentations ?? []),
      ],
      resource: this.resource,
    });
  }

  withPinoInstrumentation(): this {
    registerInstrumentations({
      instrumentations: [new PinoInstrumentation()],
    });
    return this;
  }

  withBullMQInstrumentation(options?: Record<string, unknown>): this {
    registerInstrumentations({
      instrumentations: [new BullMQInstrumentation(options)],
    });
    return this;
  }

  withPrismaInstrumentation(options?: Record<string, unknown>): this {
    registerInstrumentations({
      instrumentations: [new PrismaInstrumentation(options)],
    });
    return this;
  }

  withHostMetrics(): this {
    const hostMetrics = new HostMetrics({
      meterProvider: this.getMeterProvider(),
    });

    console.log("hostMetrics", hostMetrics);

    this.components.push(hostMetrics);

    return this;
  }

  withDeploymentMetrics(): this {
    const deploymentMetrics = new DeploymentMetrics({
      meterProvider: this.getMeterProvider(),
    });

    this.components.push(deploymentMetrics);

    return this;
  }

  withHandlesAndRequestsMetrics(): this {
    const handlesAndRequestsMetrics = new HandlesAndRequestsMetrics({
      meterProvider: this.getMeterProvider(),
    });

    this.components.push(handlesAndRequestsMetrics);

    return this;
  }

  start(): void {
    if (this.config.debug) {
      console.log("Starting OpenTelemetry Node Instrumentation");
    }

    try {
      this.components.forEach((component) => component.start());

      if (this.config.debug) {
        console.log("OpenTelemetry Node SDK started successfully");
      }
    } catch (error) {
      console.error("Failed to start OpenTelemetry SDK:", error);
      throw error;
    }
  }

  async shutdown(): Promise<void> {
    if (this.config.debug) {
      console.log("Shutting down OpenTelemetry Node Instrumentation");
    }

    try {
      await this.sdk.shutdown();

      if (this.config.debug) {
        console.log("OpenTelemetry Node SDK shut down successfully");
      }
    } catch (error) {
      console.error("Failed to shut down OpenTelemetry SDK:", error);
      throw error;
    }
  }
}

---

Next.js Version: 16.0.3 (upgraded from 15.5.6)
Node.js Version: 24.11.0
OpenTelemetry API: 1.9.0
Package Manager: pnpm 10.20.0
Build Tool: Turbopack (default in Next.js 16)
Custom Instrumentation: @customrepo/instrumentation (local package)
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/RockFi/rockfi-instrumentation

To Reproduce

After upgrading from Next.js 15.5.6 to 16.0.3, OpenTelemetry metrics are no longer being exported to our Grafana backend, while traces continue to work correctly. The application uses a custom instrumentation package (@customrepo/instrumentation) that implements OpenTelemetry metrics collection including host metrics, deployment metrics, and request metrics.

Here is how we implement the instrumentation in our library

import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { HostMetrics } from "@opentelemetry/host-metrics";
import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { BaseInstrumentation } from "./base";
import { InstrumentationConfig } from "./types";
import { DeploymentMetrics } from "./metrics/deployment-metrics";
import { HandlesAndRequestsMetrics } from "./metrics/handles-and-requests-metrics";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { ExpressLayerType } from "@opentelemetry/instrumentation-express";
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { BullMQInstrumentation } from "@appsignal/opentelemetry-instrumentation-bullmq";

interface Startable {
  start(): void;
}

export class NodeInstrumentation extends BaseInstrumentation {
  private sdk: NodeSDK;
  private components: Startable[] = [];

  constructor(config: InstrumentationConfig) {
    super(config);
    this.sdk = this.initializeNodeSDK(config);
    this.components.push(this.sdk);
  }

  private initializeNodeSDK(config: InstrumentationConfig): NodeSDK {
    return new NodeSDK({
      sampler: config.tracerProvider?.sampler,
      spanProcessors: [
        ...(config.tracerProvider?.spanProcessors ?? []),
        this.defaultSpanProcessor(),
      ],
      contextManager: config.contextManager,
      textMapPropagator: config.textMapPropagator,
      metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter(this.exporterConfigs.metrics),
      }),
      logRecordProcessors: [
        new BatchLogRecordProcessor(
          new OTLPLogExporter(this.exporterConfigs.logs)
        ),
      ],
      instrumentations: [
        getNodeAutoInstrumentations({
          "@opentelemetry/instrumentation-http": {
            ignoreIncomingRequestHook: (request) => {
              return (
                this.config.ignoreUrls?.some((pattern) =>
                  request.url?.match(pattern)
                ) ?? false
              );
            },
            ...this.config.httpInstrumentation,
          },
          "@opentelemetry/instrumentation-express": {
            ignoreLayers: this.config.ignoreUrls,
            requestHook: (span, info) => {
              if (info.layerType === ExpressLayerType.REQUEST_HANDLER) {
                const route = info.route || info.request.route?.path;

                span.setAttribute("http.route", route);
                span.setAttribute("http.target", route);
              }
            },
          },
        }),
        ...(this.config.instrumentations ?? []),
      ],
      resource: this.resource,
    });
  }

  withPinoInstrumentation(): this {
    registerInstrumentations({
      instrumentations: [new PinoInstrumentation()],
    });
    return this;
  }

  withBullMQInstrumentation(options?: Record<string, unknown>): this {
    registerInstrumentations({
      instrumentations: [new BullMQInstrumentation(options)],
    });
    return this;
  }

  withPrismaInstrumentation(options?: Record<string, unknown>): this {
    registerInstrumentations({
      instrumentations: [new PrismaInstrumentation(options)],
    });
    return this;
  }

  withHostMetrics(): this {
    const hostMetrics = new HostMetrics({
      meterProvider: this.getMeterProvider(),
    });

    console.log("hostMetrics", hostMetrics);

    this.components.push(hostMetrics);

    return this;
  }

  withDeploymentMetrics(): this {
    const deploymentMetrics = new DeploymentMetrics({
      meterProvider: this.getMeterProvider(),
    });

    this.components.push(deploymentMetrics);

    return this;
  }

  withHandlesAndRequestsMetrics(): this {
    const handlesAndRequestsMetrics = new HandlesAndRequestsMetrics({
      meterProvider: this.getMeterProvider(),
    });

    this.components.push(handlesAndRequestsMetrics);

    return this;
  }

  start(): void {
    if (this.config.debug) {
      console.log("Starting OpenTelemetry Node Instrumentation");
    }

    try {
      this.components.forEach((component) => component.start());

      if (this.config.debug) {
        console.log("OpenTelemetry Node SDK started successfully");
      }
    } catch (error) {
      console.error("Failed to start OpenTelemetry SDK:", error);
      throw error;
    }
  }

  async shutdown(): Promise<void> {
    if (this.config.debug) {
      console.log("Shutting down OpenTelemetry Node Instrumentation");
    }

    try {
      await this.sdk.shutdown();

      if (this.config.debug) {
        console.log("OpenTelemetry Node SDK shut down successfully");
      }
    } catch (error) {
      console.error("Failed to shut down OpenTelemetry SDK:", error);
      throw error;
    }
  }
}

Current vs. Expected behavior

Current Behavior ✅ Traces are successfully collected and exported to the OTLP endpoint ❌ Metrics (host metrics, deployment metrics, request metrics) are not being exported ✅ OpenTelemetry SDK initializes successfully ✅ Telemetry endpoint is reachable (traces prove this) ❌ No metric export attempts appear in logs (unlike traces which show OTLPExportDelegate items to be sent) Expected Behavior Metrics should be collected and exported periodically to the OTLP endpoint similar to how they worked in Next.js 15.5.6.

Provide environment information

Next.js Version: 16.0.3 (upgraded from 15.5.6)
Node.js Version: 24.11.0
OpenTelemetry API: 1.9.0
Package Manager: pnpm 10.20.0
Build Tool: Turbopack (default in Next.js 16)
Custom Instrumentation: @customrepo/instrumentation (local package)

Which area(s) are affected? (Select all that apply)

Instrumentation

Which stage(s) are affected? (Select all that apply)

next build (local), next dev (local)

Additional context

Attempted Solutions ✅ Verified endpoint connectivity (traces work) ✅ Confirmed instrumentation is loading ✅ Checked that metrics API is registered (@opentelemetry/api: Registered a global for metrics v1.9.0) ❌ Metrics still not being exported

extent analysis

TL;DR

The issue is likely due to a compatibility problem between the custom instrumentation package and the upgraded Next.js version, causing metrics to not be exported to the OTLP endpoint.

Guidance

  1. Verify the compatibility of the custom instrumentation package @customrepo/instrumentation with Next.js 16.0.3 and OpenTelemetry API 1.9.0.
  2. Check the metric reader configuration in the initializeNodeSDK method to ensure it is correctly set up to export metrics to the OTLP endpoint.
  3. Enable debug logging in the NodeInstrumentation class to see if there are any errors or warnings related to metric export.
  4. Compare the differences between the Next.js 15.5.6 and 16.0.3 versions to identify any breaking changes that might affect the custom instrumentation package.

Example

No code snippet is provided as the issue is likely related to the compatibility of the custom instrumentation package with the upgraded Next.js version.

Notes

The issue might be specific to the custom instrumentation package, and more information about the package's implementation and configuration might be needed to provide a more accurate solution.

Recommendation

Apply a workaround by downgrading Next.js to a version compatible with the custom instrumentation package, or upgrade the custom instrumentation package to be compatible with Next.js 16.0.3. The reason is that the custom instrumentation package is not compatible with the upgraded Next.js version, causing metrics to not be exported.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING