Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[node-sdk] Missing @opentelemetry/exporter-jaeger raises an error on Next.js app #4297

Open
jstlaurent opened this issue Nov 15, 2023 · 17 comments
Assignees
Labels
bug Something isn't working priority:p1 Bugs which cause problems in end-user applications such as crashes, data inconsistencies, etc

Comments

@jstlaurent
Copy link

jstlaurent commented Nov 15, 2023

What happened?

Steps to Reproduce

Create a basic Next.js app and follow the steps to manually add OpenTelemetry to the application.

Use the OpenTelemetry setup code below. Don't set any environment variables, so that a ConsoleSpanExporter gets configured.

Start the dev server (npm run dev) and launch the app.

Expected Result

I see the default OpenTelemetry spans from my request in the console.

Actual Result

I get an error message from a failed import: Can't resolve '@opentelemetry/exporter-jaeger'. See the full trace below.

Additional Details

It looks like node-sdk imports TracerProviderWithEnvExporters. As a side effect of which, a Map of registered providers is built that assumes Jaeger is available and throws an error if it isn't. Since the dependency was removed in v0.44, the error gets thrown.

Edit:
Note that I'm not using Jaeger. I'll add the dependency to solve the error, but I would rather not have to include it at all. I can't include @opentelemetry/exporter-jaeger in my project because #3759 happens.

OpenTelemetry Setup Code

//instrumentation.ts
import { settings } from '@/config'
import { Span, SpanOptions, SpanStatusCode, trace } from '@opentelemetry/api'

export const tracer = trace.getTracer(settings.SERVICE_NAME)

export async function register() {
  if (settings.DISABLE_OTEL) {
    console.info('OpenTelemetry is disabled')
    return
  }

  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}

//instrumentation.node.ts

import { settings } from '@/config'
import * as grpc from '@grpc/grpc-js'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
import { Resource } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import {
  BatchSpanProcessor,
  ConsoleSpanExporter,
  SimpleSpanProcessor
} from '@opentelemetry/sdk-trace-node'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'

function getProcessor() {
  if (settings.HONEYCOMB_API_KEY) {
    const metadata = new grpc.Metadata()
    metadata.set('x-honeycomb-team', settings.HONEYCOMB_API_KEY)

    const exporter = new OTLPTraceExporter({
      url: 'grpc://api.honeycomb.io:443/',
      credentials: grpc.credentials.createSsl(),
      metadata
    })

    // Values from https://github.com/honeycombio/intro-to-o11y-nodejs/blob/main/src/tracing.js
    return new BatchSpanProcessor(exporter, {
      maxQueueSize: 16000,
      maxExportBatchSize: 1000,
      scheduledDelayMillis: 500
    })
  }

  console.info('Using console exporter')
  return new SimpleSpanProcessor(new ConsoleSpanExporter())
}

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: settings.SERVICE_NAME,
    [SemanticResourceAttributes.SERVICE_NAMESPACE]: settings.PROJECT_NAME,
    [SemanticResourceAttributes.SERVICE_VERSION]:
      settings.VERCEL_GIT_COMMIT_SHA,
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: settings.VERCEL_ENV
  }),
  spanProcessor: getProcessor()
})

sdk.start()

package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "format": "prettier --write ."
  },
  "dependencies": {
    "next": "^14.0.2",
    "react": "^18.2.0",
    "@opentelemetry/api": "^1.7.0",
    "@opentelemetry/exporter-trace-otlp-grpc": "^0.45.1",
    "@opentelemetry/resources": "^1.18.1",
    "@opentelemetry/sdk-node": "^0.45.1",
    "@opentelemetry/sdk-trace-node": "^1.18.1",
    "@opentelemetry/semantic-conventions": "^1.18.1",
    "tailwind-merge": "^2.0.0",
    "tailwindcss": "^3.3.5",
    "tailwindcss-animate": "^1.0.7",
  },
  "devDependencies": {
    "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
    "@tailwindcss/typography": "^0.5.10",
    "@types/node": "^20.9.0",
    "@types/react": "^18.2.37",
    "@types/react-dom": "^18.2.15",
    "@types/ws": "^8.5.9",
    "autoprefixer": "^10.4.16",
    "dotenv": "^16.3.1",
    "dotenv-cli": "^7.3.0",
    "eslint": "^8.53.0",
    "eslint-config-next": "^14.0.2",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-tailwindcss": "^3.13.0",
    "postcss": "^8.4.30",
    "prettier": "^3.1.0",
    "prettier-plugin-tailwindcss": "^0.5.7",
    "ts-node": "^10.9.1",
    "typescript": "^5.2.2"
  },
  "postcss": {
    "plugins": {
      "tailwindcss": {},
      "autoprefixer": {}
    }
  }
}

Relevant log output

./node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
Module not found: Can't resolve '@opentelemetry/exporter-jaeger' in '/Users/julien/projects/my-app/node_modules/@opentelemetry/sdk-node/build/src'

Import trace for requested module:
./node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
./node_modules/@opentelemetry/sdk-node/build/src/sdk.js
./node_modules/@opentelemetry/sdk-node/build/src/index.js
./src/instrumentation.node.ts
./src/instrumentation.ts
./src/services/dataset.ts
./src/app/datasets/page.tsx
@jstlaurent jstlaurent added bug Something isn't working triage labels Nov 15, 2023
@dyladan dyladan added priority:p1 Bugs which cause problems in end-user applications such as crashes, data inconsistencies, etc and removed triage labels Nov 22, 2023
@dyladan dyladan self-assigned this Nov 22, 2023
@lewinskimaciej
Copy link

Encountering the same issue with next.js app.
As far as I understand #4214 was supposed to fix this, but we still have the issue.

 "@opentelemetry/api": "^1.7.0",
    "@opentelemetry/exporter-metrics-otlp-http": "^0.47.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.47.0",
    "@opentelemetry/instrumentation-http": "^0.47.0",
    "@opentelemetry/resources": "^1.20.0",
    "@opentelemetry/sdk-metrics": "^1.20.0",
    "@opentelemetry/sdk-node": "^0.47.0",
    "@opentelemetry/sdk-trace-node": "^1.20.0",
    "@opentelemetry/semantic-conventions": "^1.20.0",
Import trace for requested module:
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/sdk.js
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/index.js
 ⚠ ../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
Module not found: Can't resolve '@opentelemetry/exporter-jaeger' in 'C:\Users\macie\Documents\src\sphere\node_modules\.pnpm\@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0\node_modules\@opentelemetry\sdk-node\build\src'

@djboulia
Copy link

djboulia commented Feb 13, 2024

I am encountering this as well. I think the core issue is that the attempt to lazy load exporter-jaeger doesn't work because Nextjs is trying to bundle the library via webpack. So it searches out the dynamic require() calls and can't find the module. If I manually add exporter-jaeger, I get back to the original problem listed in #4214 . I also tried fixing ansi-color (which is the offending module in #4214) but that still yields warnings due to other dynamic imports. I think testing this library against webpack will be necessary in order for it to work properly with Nextjs.

@djboulia
Copy link

UPDATE: I was able to work around these issues for my server components in nextjs by adding this to my nextjs.config.js file:

experimental: {
serverComponentsExternalPackages: ['@acme/package'],
},

In my case, all of the OpenTelemetry stuff was already isolated in an NPM package, so I just added that to the stanza above. If you are directly importing the OTel libraries, you may need to specify multiple modules here. My understanding is that this directive tells Next not to bundle the given library, avoiding webpack and avoiding all of the errors from dynamic loading.

@lewinskimaciej
Copy link

Encountering the same issue with next.js app. As far as I understand #4214 was supposed to fix this, but we still have the issue.

 "@opentelemetry/api": "^1.7.0",
    "@opentelemetry/exporter-metrics-otlp-http": "^0.47.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.47.0",
    "@opentelemetry/instrumentation-http": "^0.47.0",
    "@opentelemetry/resources": "^1.20.0",
    "@opentelemetry/sdk-metrics": "^1.20.0",
    "@opentelemetry/sdk-node": "^0.47.0",
    "@opentelemetry/sdk-trace-node": "^1.20.0",
    "@opentelemetry/semantic-conventions": "^1.20.0",
Import trace for requested module:
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/sdk.js
../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/index.js
 ⚠ ../../node_modules/.pnpm/@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0/node_modules/@opentelemetry/sdk-node/build/src/TracerProviderWithEnvExporter.js
Module not found: Can't resolve '@opentelemetry/exporter-jaeger' in 'C:\Users\macie\Documents\src\sphere\node_modules\.pnpm\@opentelemetry+sdk-node@0.47.0_@opentelemetry+api@1.7.0\node_modules\@opentelemetry\sdk-node\build\src'

I forgot to update:
I managed to fix this issue by... actually following docs.
https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry
This part is really important:

Now you can initialize NodeSDK in your instrumentation.ts. Unlike @vercel/otel, NodeSDK is not compatible with edge runtime, so you need to make sure that you are importing them only when process.env.NEXT_RUNTIME === 'nodejs'.

What happened on my side is that I've put register function in a condition based on NEXT_RUNTIME and one other var we care about but... not the import. It's actually important to import conditionally as well, as some OTel stuff will initialize in the background the moment it's require'd, We've moved our OTel registration stuff to a separate file that we import conditionally based on runtime and it works fine now. Spans from next.js are really weird and I'm to this day not sure if I can get any useful info from them on my own but it does work.

@jstlaurent
Copy link
Author

I tried @djboulia's approach, but it didn't work for me, unfortunately. However, since I submitted this issue, Vercel has updated their @vercel/otel library to allow for more configuration. This is what I needed to configure a Honeycomb.io exporter.

So I ended up with an hybrid of @lewinskimaciej's solution and Next's automated setup.

It looks something like this:

In instrumentation.ts:

import { settings } from '@/config'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { registerOTel } from '@vercel/otel'

export async function register() {
  if (settings.DISABLE_OTEL) {
    console.info('OpenTelemetry is disabled')
    return
  }

  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { getProcessor } = await import('./instrumentation.node')
    registerOTel({
      serviceName: settings.SERVICE_NAME,
      attributes: {
        [SemanticResourceAttributes.SERVICE_NAMESPACE]: settings.PROJECT_NAME
      },
      spanProcessors: [getProcessor()]
    })
  }
}

And in instrumentation.node.ts:

import { settings } from '@/config'
import * as grpc from '@grpc/grpc-js'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
import {
  BatchSpanProcessor,
  ConsoleSpanExporter,
  SimpleSpanProcessor,
  type SpanProcessor
} from '@opentelemetry/sdk-trace-base'

export function getProcessor(): SpanProcessor {
  if (settings.HONEYCOMB_API_KEY) {
    const metadata = new grpc.Metadata()
    metadata.set('x-honeycomb-team', settings.HONEYCOMB_API_KEY)

    const exporter = new OTLPTraceExporter({
      url: 'grpc://api.honeycomb.io:443/',
      credentials: grpc.credentials.createSsl(),
      metadata
    })

    // Values from https://github.com/honeycombio/intro-to-o11y-nodejs/blob/main/src/tracing.js
    return new BatchSpanProcessor(exporter, {
      maxQueueSize: 16000,
      maxExportBatchSize: 1000,
      scheduledDelayMillis: 500
    })
  }

  console.info('Using console exporter')
  return new SimpleSpanProcessor(new ConsoleSpanExporter())
}

I still need to Node-only conditional import, because grpcneeds Node API that are missing on the edge platform.

Hope that can help folks navigate this issue.

@ShubhamPalriwala
Copy link

This saved me! Thank you so much 🚀

@ranshamay
Copy link

ranshamay commented Mar 26, 2024

alternative solution, you might find it useful -

instumentation.ts:

export async function register() {
  console.log('registering instrumentation');
  const { NodeSDK } = await import('@opentelemetry/sdk-node');
  const { ConsoleSpanExporter } = await import('@opentelemetry/sdk-trace-node');
  const { getNodeAutoInstrumentations } = await import(
    '@opentelemetry/auto-instrumentations-node'
  );
  const { PeriodicExportingMetricReader, ConsoleMetricExporter } = await import(
    '@opentelemetry/sdk-metrics'
  );

  const sdk = new NodeSDK({
    traceExporter: new ConsoleSpanExporter(),
    instrumentations: [
      getNodeAutoInstrumentations({
        '@opentelemetry/instrumentation-grpc': { enabled: false },
      }),
    ],
    metricReader: new PeriodicExportingMetricReader({
      exporter: new ConsoleMetricExporter(),
    }),
  });

  sdk.start();
}

next.config.js

const { NormalModuleReplacementPlugin } = require('webpack');
const path = require('path');
const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
  webpack: (config, options) => {
    return {
      ...config,
 plugins: [
        ...config.plugins,
        new NormalModuleReplacementPlugin(
          /@opentelemetry\/exporter-jaeger/,
          path.resolve(path.join(__dirname, './polyfills.js'))
        ),
      ],
      resolve: {
        ...config.resolve,
        fallback: {
          ...config.resolve.fallback,
          stream: false,
          zlib: false,
          http: false,
          tls: false,
          net: false,
          http2: false,
          dns: false,
          os: false,
          fs: false,
          path: false,
          https: false,
        },
      },
    };
  },
};

polyfills.js:

module.exports = () => {};

@Nhollas
Copy link

Nhollas commented May 11, 2024

I tried @djboulia's approach, but it didn't work for me, unfortunately. However, since I submitted this issue, Vercel has updated their @vercel/otel library to allow for more configuration. This is what I needed to configure a Honeycomb.io exporter.

So I ended up with an hybrid of @lewinskimaciej's solution and Next's automated setup.

It looks something like this:

In instrumentation.ts:

import { settings } from '@/config'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { registerOTel } from '@vercel/otel'

export async function register() {
  if (settings.DISABLE_OTEL) {
    console.info('OpenTelemetry is disabled')
    return
  }

  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { getProcessor } = await import('./instrumentation.node')
    registerOTel({
      serviceName: settings.SERVICE_NAME,
      attributes: {
        [SemanticResourceAttributes.SERVICE_NAMESPACE]: settings.PROJECT_NAME
      },
      spanProcessors: [getProcessor()]
    })
  }
}

And in instrumentation.node.ts:

import { settings } from '@/config'
import * as grpc from '@grpc/grpc-js'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
import {
  BatchSpanProcessor,
  ConsoleSpanExporter,
  SimpleSpanProcessor,
  type SpanProcessor
} from '@opentelemetry/sdk-trace-base'

export function getProcessor(): SpanProcessor {
  if (settings.HONEYCOMB_API_KEY) {
    const metadata = new grpc.Metadata()
    metadata.set('x-honeycomb-team', settings.HONEYCOMB_API_KEY)

    const exporter = new OTLPTraceExporter({
      url: 'grpc://api.honeycomb.io:443/',
      credentials: grpc.credentials.createSsl(),
      metadata
    })

    // Values from https://github.com/honeycombio/intro-to-o11y-nodejs/blob/main/src/tracing.js
    return new BatchSpanProcessor(exporter, {
      maxQueueSize: 16000,
      maxExportBatchSize: 1000,
      scheduledDelayMillis: 500
    })
  }

  console.info('Using console exporter')
  return new SimpleSpanProcessor(new ConsoleSpanExporter())
}

I still need to Node-only conditional import, because grpcneeds Node API that are missing on the edge platform.

Hope that can help folks navigate this issue.

I'm not using vercel otel configuration but manual config. Is there a fix for this?

@SnaiNeR
Copy link

SnaiNeR commented May 21, 2024

Solution for me was to add sdk initialization with the condition, inside register function

image

@Nhollas
Copy link

Nhollas commented May 21, 2024

Solution for me was to add sdk initialization with the condition, inside register function

image

What does your opentelemetry file look like?

@kayandra
Copy link

This is how I fixed it. I just had to prevent auto-instrumentation and sdk-node from server components.

/** @type {import("next").NextConfig} */
const config = {
 reactStrictMode: true,
 experimental: {
   instrumentationHook: true,
   serverComponentsExternalPackages: [
     "@opentelemetry/auto-instrumentations-node",
     "@opentelemetry/sdk-node",
   ],
 },
}

1 similar comment
@kayandra
Copy link

This is how I fixed it. I just had to prevent auto-instrumentation and sdk-node from server components.

/** @type {import("next").NextConfig} */
const config = {
 reactStrictMode: true,
 experimental: {
   instrumentationHook: true,
   serverComponentsExternalPackages: [
     "@opentelemetry/auto-instrumentations-node",
     "@opentelemetry/sdk-node",
   ],
 },
}

@shawnmclean
Copy link

experimental: {
    instrumentationHook: true,
    serverComponentsExternalPackages: ["@opentelemetry/sdk-node"],
  }

Does anyone know why this works?

They have a default list here: https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/server-external-packages.json

Should this be included there to prevent further issues for those who are rolling their own without vercel/otel?

@aurorascharff
Copy link

The experimental.serverComponentsExternalPackages has been deprecated and replaced by:
https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages

@shawnmclean
Copy link

@aurorascharff have you gotten next 15 to work?

What's in your array?

I'm getting this error but I don't see anyone talking about it:

 ⚠ ./node_modules/.pnpm/@opentelemetry+instrumentation@0.56.0_@opentelemetry+api@1.9.0/node_modules/@opentelemetry/instrumentation/build/esm/platform/node
Package require-in-the-middle can't be external
The request require-in-the-middle matches serverExternalPackages (or the default list).
The request could not be resolved by Node.js from the project directory.
Packages that should be external need to be installed in the project directory, so they can be resolved from the output files.
Try to install it into the project directory by running npm install require-in-the-middle from the project directory.

@aurorascharff
Copy link

@shawnmclean This is in my array, it's working now in Next.js 15.

serverExternalPackages: [
'@azure/monitor-opentelemetry',
'@azure/opentelemetry-instumentation-azure-sdk',
'@opentelemetry/auto-instrumentations-node',
'@opentelemetry/sdk-node',
'@opentelemetry/instrumentation',
'@opentelemetry/api',
'@opentelemetry/exporter-jaeger',
],

@shawnmclean
Copy link

@aurorascharff Thank you!

My missing one was instrumentation.

So it seems if that is missing, I get the error above and also there will be some HMR issues when recovering from a server crash.

Mine:

  serverExternalPackages: [
    "@opentelemetry/sdk-node",
    "@opentelemetry/instrumentation",
    "pino",
  ],

I didn't want to put things in there thats not needed, I'm not sure what the downside of that will be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working priority:p1 Bugs which cause problems in end-user applications such as crashes, data inconsistencies, etc
Projects
None yet
Development

No branches or pull requests