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

Error: @opentelemetry/api: Attempted duplicate registration of API: trace #12717

Open
3 tasks done
jonmast opened this issue Jul 1, 2024 · 3 comments
Open
3 tasks done

Comments

@jonmast
Copy link

jonmast commented Jul 1, 2024

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/aws-serverless

SDK Version

8.13.0

Framework Version

No response

Link to Sentry event

No response

SDK Setup/Reproduction Example

import * as Sentry from "@sentry/aws-serverless";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SentryPropagator, SentrySpanProcessor } from "@sentry/opentelemetry";
import { SentryContextManager, validateOpenTelemetrySetup } from "@sentry/node";

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.SENTRY_ENVIRONMENT,
  normalizeDepth: 8,
  skipOpenTelemetrySetup: true,
  integrations: [Sentry.extraErrorDataIntegration({ depth: 8 })],
});

declare global {
  // Global hook exposed by otel layer
  function configureTracerProvider(tracerProvider: NodeTracerProvider): void;
}

const oldConfigureTracerProvider = global.configureTracerProvider;

global.configureTracerProvider = (provider) => {
  if (typeof oldConfigureTracerProvider === "function") {
    oldConfigureTracerProvider(provider);
  }

  provider.addSpanProcessor(new SentrySpanProcessor());

  // We need a custom propagator and context manager
  provider.register({
    propagator: new SentryPropagator(),
    contextManager: new SentryContextManager(),
  });

  validateOpenTelemetrySetup();
};

Steps to Reproduce

  1. Set up opentelemetry using aws-otel
  2. Add Sentry 8.x using the setup above

Expected Result

AWS otel w/ xray and Sentry work together in beautiful harmony :)

I attempted to use the manual setup guide in the docs. It's not entirely clear to me from the docs how Sentry is supposed to fit into an existing opentelemetry setup.

Actual Result

The configs clash and log the following error:

Error: @opentelemetry/api: Attempted duplicate registration of API: trace
at registerGlobal (/opt/nodejs/node_modules/@opentelemetry/api/build/src/internal/global-utils.js:32:21)
at TraceAPI.setGlobalTracerProvider (/opt/nodejs/node_modules/@opentelemetry/api/build/src/api/trace.js:54:59)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-base/build/src/BasicTracerProvider.js:92:21)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-node/build/src/NodeTracerProvider.js:43:15)
at initializeProvider (/opt/wrapper.js:86:20)
at Object.<anonymous> (/opt/wrapper.js:105:1)
at Module._compile (node:internal/modules/cjs/loader:1364:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
at Module.load (node:internal/modules/cjs/loader:1203:32)
at Module._load (node:internal/modules/cjs/loader:1019:12)
Error: @opentelemetry/api: Attempted duplicate registration of API: context
at registerGlobal (/opt/nodejs/node_modules/@opentelemetry/api/build/src/internal/global-utils.js:32:21)
at ContextAPI.setGlobalContextManager (/opt/nodejs/node_modules/@opentelemetry/api/build/src/api/context.js:43:50)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-base/build/src/BasicTracerProvider.js:97:27)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-node/build/src/NodeTracerProvider.js:43:15)
at initializeProvider (/opt/wrapper.js:86:20)
at Object.<anonymous> (/opt/wrapper.js:105:1)
at Module._compile (node:internal/modules/cjs/loader:1364:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
at Module.load (node:internal/modules/cjs/loader:1203:32)
at Module._load (node:internal/modules/cjs/loader:1019:12)
Error: @opentelemetry/api: Attempted duplicate registration of API: propagation
at registerGlobal (/opt/nodejs/node_modules/@opentelemetry/api/build/src/internal/global-utils.js:32:21)
at PropagationAPI.setGlobalPropagator (/opt/nodejs/node_modules/@opentelemetry/api/build/src/api/propagation.js:52:50)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-base/build/src/BasicTracerProvider.js:100:31)
at NodeTracerProvider.register (/opt/nodejs/node_modules/@opentelemetry/sdk-trace-node/build/src/NodeTracerProvider.js:43:15)
at initializeProvider (/opt/wrapper.js:86:20)
at Object.<anonymous> (/opt/wrapper.js:105:1)
at Module._compile (node:internal/modules/cjs/loader:1364:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
at Module.load (node:internal/modules/cjs/loader:1203:32)
at Module._load (node:internal/modules/cjs/loader:1019:12)
@mydea
Copy link
Member

mydea commented Jul 2, 2024

Hey,

I do not think we have a great setup yet for AWS + Custom OTEL setup. Just so I understand, what's the reason for going with a custom setup vs. just using @sentry/aws-serverless? Do you already have a working otel setup? If so, could you share the code of your setup "before sentry"?

If you have a manual setup, you do not need to add @opentelemetry/instrumentation-aws-sdk or @opentelemetry/instrumentation-aws-lambda because they are auto-added for you by @sentry/aws-serverless. If you do not want this, you have to filter this from the default integrations (see https://docs.sentry.io/platforms/javascript/guides/aws-lambda/configuration/integrations/#modifying-default-integrations).

Generally, if you set skipOpenTelemetrySetup: true the Sentry SDK will not register any tracer etc. for OTEL. So I don't think the error comes from Sentry then 🤔 You can see in the stack trace that this comes from @opentelemetry/sdk-trace-node which we do not even use in the Sentry SDK, this only uses sdk-trace-base.

@jonmast
Copy link
Author

jonmast commented Jul 2, 2024

Do you already have a working otel setup?

Yes. We are using the AWS provided lambda layer from https://aws-otel.github.io/docs/getting-started/lambda/lambda-js, there's no custom setup code on our side. It uses the stock otel layer under the hood with some custom config for AWS. Most of the setup happens in this wrapper.ts file which you can see in the stacktrace. It calls the global.configureTracerProvider function as an extension mechanism.

Generally, if you set skipOpenTelemetrySetup: true the Sentry SDK will not register any tracer etc. for OTEL. So I don't think the error comes from Sentry then 🤔

You are correct, but what is happening is that the stock initialization in wrapper.ts is running after Sentry's initialization which is resulting in the duplication errors.

@mydea
Copy link
Member

mydea commented Jul 3, 2024

OK, I see, I think!

I think the problem is that you are calling provider.register() again, but it has already been registered before. This is what leads to the duplicate registration issue. So you cannot call register in there, but must configure the propagator and context manager differently. You can use configureSdkRegistration for this, I believe, looking at their code. so something like this could work:

import * as Sentry from "@sentry/aws-serverless";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SentryPropagator, SentrySpanProcessor } from "@sentry/opentelemetry";
import { SentryContextManager, validateOpenTelemetrySetup } from "@sentry/node";

const sentryClient = Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.SENTRY_ENVIRONMENT,
  normalizeDepth: 8,
  skipOpenTelemetrySetup: true,
  integrations: [Sentry.extraErrorDataIntegration({ depth: 8 })],
});

declare global {
  // Global hook exposed by otel layer
  function configureTracerProvider(tracerProvider: NodeTracerProvider): void;
  function configureSdkRegistration(config: Record<string, any>): Record<string, any>;
  function configureInstrumentations(): any[];
  function configureTracer(config: any): any;
}

const oldConfigureTracerProvider = global.configureTracerProvider;
const oldConfigureSdkRegistration = global.configureSdkRegistration;

// We let Sentry register instrumentation
global.configureInstrumentation = function() {
  return [];
}

global.configureTracer = (config) => {
  config.sampler = sentryClient ? new SentrySampler(sentryClient) : undefined,
  return config;
}

global.configureSdkRegistration = (config) => {
  config.propagator = new SentryPropagator();
  config.contextManager = new SentryContextManager();

  if (typeof oldConfigureSdkRegistration === 'function') {
    return oldConfigureTracerProvider(config);
  }

   return config;
};

global.configureTracerProvider = (provider) => {
  if (typeof oldConfigureTracerProvider === "function") {
    oldConfigureTracerProvider(provider);
  }

  provider.addSpanProcessor(new SentrySpanProcessor());
};

Something like this should work, I believe!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests

2 participants