sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

Express is not instrumented

Open adriaanmeuris opened this issue 1 year ago • 18 comments

Is there an existing issue for this?

  • [X] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
  • [X] I have reviewed the documentation https://docs.sentry.io/
  • [X] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.2.1

Framework Version

No response

Link to Sentry event

No response

SDK Setup

Sentry.init({
  dsn: __DSN_HERE__
});

Steps to Reproduce

import * as Sentry from '@sentry/node';
import express from 'express';

Sentry.init({
  dsn: __DSN_HERE__
});

// Setup Express app
const app = express();

// Add Sentry error handler
Sentry.setupExpressErrorHandler(app);

app.listen(3000);

Expected Result

No error about Express not being instrumented.

Actual Result

Error:

[Sentry] Express is not instrumented. This is likely because you required/imported express before calling `Sentry.init()`.

Initializing Sentry before importing Express doesn't work.

adriaanmeuris avatar May 17 '24 10:05 adriaanmeuris

Hey @adriaanmeuris

the error message tells you to call Sentry.init before requireing express. Why does this not work for you?

Lms24 avatar May 17 '24 10:05 Lms24

I've made this change as follows:

import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: __DSN_HERE__
});

import express from 'express';

But the error message still occurs, I'm not sure why. Am I missing something?

adriaanmeuris avatar May 17 '24 11:05 adriaanmeuris

Hello,

are you running your application in CJS or ESM? Meaning, what is it you do like node src/my-app.js? Or do you have a build step?

If you are running it as ESM app, you need to follow the docs here: https://docs.sentry.io/platforms/javascript/guides/express/install/esm/

mydea avatar May 17 '24 11:05 mydea

hey, i'm having absolutely same issue, and it's annoying AF. And i'm using CJS, not ESM. Double checked it.

I spent 5 hours in total trying to modify my application initialization. Even tried using some workaround with setTimeout. None of that helped. Still having this annoyind AG red text saying [Sentry] Express is not instrumented. This is likely because you required/imported express before calling `Sentry.init().

I don't even have any other express related import other than Sentry.expressIntegration(), can you guys please bring back possiblity to initialise the Sentry init not at the top, it's really frustrating not being able to init how you want. I have a microservice app, and there i have one common function that initializes microservice. I used to Sentry inside that microservice initializing function, but now, after migrating to 8 i had to take sentry init out of that function and init manually inside every of microservice to make sure it's the fitst import, but even after that I'm having this error.

It's such a drawback in developer experience, it's hard to believe that you had to do that. I would assuke that you had technical reasons to do so, but it was working perfectly fine in v7. Why did you guys had to remove that flexibility?

Sorry that i'm being emotional, but spent way too much hours trying to fix error, by following exactly what you wrote to do, and still this error is there. You could write some better error message, other than "most likely"

amiranvarov avatar May 18 '24 15:05 amiranvarov

update: I noticed that this error is happening when i add Sentry.setupExpressErrorHandler(app); at the end of all midlewares and controllers, right before my app.listen() call

// initSentry.ts

import * as Sentry from "@sentry/node";
import { nodeProfilingIntegration } from "@sentry/profiling-node";

export function initSentry({
	name,
	dsn,
	env,
	debug = false,
	integrations,
	enabled = true,
	includeLocalVariables = false,
	callback,
}: {
	name: string;
	enabled?: boolean;
	dsn: string;
	env: { node_env: string };
	debug: boolean;
	integrations: any[];
	includeLocalVariables?: boolean;
	callback?: () => void;
}) {
	const defaultIntegrations = [
		Sentry.httpIntegration(),
		Sentry.expressIntegration(),
		nodeProfilingIntegration(),
		Sentry.localVariablesIntegration(),
	];

	Sentry.init({
		serverName: name,
		dsn: dsn,
		environment: env.node_env,
		enabled: enabled,
		debug: debug,
		integrations: [...(includeLocalVariables ? [Sentry.localVariablesIntegration()] : []), ...defaultIntegrations, ...integrations],
		// Set tracesSampleRate to 1.0 to capture 100%
		// of transactions for performance monitoring.
		// We recommend adjusting this value in production

		beforeSend(event, hint) {
			const error = hint.originalException as Error;
			if (error?.message?.match(/database unavailable/i)) {
				event.fingerprint = ["database-unavailable"];
			}

			// Modify or drop the event here
			if (event.user) {
				// Don't send user's email address
				event.user.email = undefined;
			}

			return event;
		},

		// Called for transaction events
		beforeSendTransaction(event) {
			// Modify or drop the event here
			if (event.transaction === "/unimportant/route") {
				// Don't send the event to Sentry
				return null;
			}
			return event;
		},
		beforeBreadcrumb(breadcrumb, hint) {
			// Check if the breadcrumb has sensitive data like user email
			if (breadcrumb.data?.["user"]?.email) {
				// Remove the user email from the breadcrumb
				breadcrumb.data["user"].email = undefined;
			}
			return breadcrumb;
		},
		includeLocalVariables: includeLocalVariables,
		attachStacktrace: true,
		tracesSampleRate: 1.0,
		profilesSampleRate: 1.0,
	});

	callback?.();
}



// main.ts

mport * as Sentry from "@sentry/node";

import { initSentry } from "@myorg/sentry-init";

initSentry({
	name: "auth-service",
	dsn: process.env.SENTRY_DSN,
	env: { node_env: process.env.NODE_ENV },
	debug: false,
	integrations: [],
});
import { MicroserviceName, TRPC_ENDPOINTS_MAP } from "@myorg/constants";

const MICROSERVICE_NAME = MicroserviceName.AuthService;
import { authRouter, prisma, authjsPlugin, envServer, logger } from "@myorg/auth-service";
import { natsWrapper } from "@myorg/auth-service/lib/nats-wrapper";
import { initRestRouter } from "@myorg/auth-service";

import { createMicroserviceApp } from "@myorg/utils";

createMicroserviceApp({
	name: MICROSERVICE_NAME,
	env: {
		port: envServer.TRPC_PORT,
		host: envServer.HOST,
		node_env: envServer.NODE_ENV,
	},
	trpcRouter: {
		path: TRPC_ENDPOINTS_MAP[MicroserviceName.AuthService],
		router: authRouter,
	},
	logger,
	nats: {
		natsWrapper: natsWrapper,
		listeners: [],
	},

	initRestRouter,
	plugins: [authjsPlugin],
});
createMicroservice.ts
// skipped some imports 

type MicroserviceConfig = {
	name: NeiraOwnMicroservicesNames;
	trpcRouter?: {
		path: string;
		router: any;
	};

	logger: LoggerInstance;
	env: {
		port: number;
		host: string;
		node_env: string;
	};
	nats?: {
		natsWrapper: AbstractNatsWrapper;
		listeners: (typeof NatsListener)[];
	};
	initRestRouter?: (app: express.Express) => void;
	plugins?: ((app: express.Express) => void)[] | ((app: express.Express) => Promise<void>)[];
	testConnectionWithMicroservices?: boolean;
};
export async function createMicroserviceApp<T>(
	{
		name,
		env,
		// sentry: { debug = false, dsn, integrations = [] },
		trpcRouter,
		logger,
		nats,
		initRestRouter,
		plugins = [],
		testConnectionWithMicroservices = false,
	}: MicroserviceConfig,
	callback?: () => void,
) {
	if (env.node_env === "production") {
		logger.info(`Starting [${name}] Microservice`);
	}

	const app = express();

	app.use(bodyParser.urlencoded({ extended: true }));
	app.use(cors());
	app.use(bodyParser.json());

	app.use(makeWinstonLogger(name));

	app.use(createTransactionIdExpressMiddleware);
	listenForConnectionCheck(app, name);

	if (initRestRouter) {
		initRestRouter(app);
	}

	if (plugins) {
		for (const plugin of plugins) {
			await plugin(app);
		}
	}
	// important! trpcRouter must be after plugins!
	if (trpcRouter) {
		app.use(
			trpcRouter.path,
			trpcExpress.createExpressMiddleware({
				router: trpcRouter.router,
				createContext: createExpressTrpcContext,
			}),
		);
	}

	app.use(expressCustomErrorHandler);
	Sentry.setupExpressErrorHandler(app);

	app.listen(env.port, env.host, () => {
		logger.info(`[${name}] tRPC server is listening on http://${env.host}:${env.port}`);

		if (callback) {
			callback();
		}
	});
	// rest of the app

amiranvarov avatar May 18 '24 15:05 amiranvarov

For context, i'm also using Sentry 8.2.1. Node v20.13.1.

amiranvarov avatar May 18 '24 15:05 amiranvarov

any updates? :) I can provide more info if needed

amiranvarov avatar May 18 '24 19:05 amiranvarov

Same here. Error happens when i add Sentry.setupExpressErrorHandler(app);. Even though i have init at the very top of my index.js.

My setup is ESM, so i guess i have to use the --import step. However i cant, cause then i get the import-in-the-middle bug and the app crashes.

rnenjoy avatar May 20 '24 07:05 rnenjoy

Just to note these here too (this was figured out in https://github.com/getsentry/sentry-javascript/issues/12056), the warning will be incorrectly logged today if tracing is not enabled. We will fix this in an upcoming release. For now, you can ignore the warning if tracing is not enabled!

So some notes @amiranvarov:

  1. You need to call Sentry.setupExpressErrorHandler() before any other error handling middleware, but after all routes are registered. We are currently updating docs to be more explicit about this, sorry about the confusion there.
  2. The only integration you have to manually add in v8 is nodeProfilingIntegration - no need to add either httpIntegration, expressIntegrationorlocalVariablesIntegration`. All of these are automatically added for you.
  3. Your init seems correct to me, other than the nits I posted above (both of these should not really affect the basic instrumentation). Could you share the full startup logs you get when you run this with debug: true?

@rnenjoy: For now, without --import you can use Sentry, and everything error-related should work. But for performance, only basic instrumentation (=incoming and outgoing http/fetch instrumentation) will work without --import. See https://docs.sentry.io/platforms/javascript/guides/node/install/esm-without-import/ for details.

mydea avatar May 21 '24 13:05 mydea

We're having the same error with our app. We're using ESM compiled from typescript. When trying to use the --import flag as shown, we get a separate issue: image

the init function call is pretty barebones, and the express error handler is called before any other error handling middleware as well

jorgeavaldez avatar May 21 '24 17:05 jorgeavaldez

We're having the same error with our app. We're using ESM compiled from typescript. When trying to use the --import flag as shown, we get a separate issue

This is the issue I'm experiencing as well. It appears that if your app and Sentry's SDK share a dependency, that dependency will try to be imported twice, causing this error.

xmunoz avatar May 21 '24 21:05 xmunoz

Could you share your instrument.js file - what do you have in there?

mydea avatar May 22 '24 08:05 mydea

import * as Sentry from '@sentry/node';

import config from 'src/config/index.js';

Sentry.init({
  dsn: config.sentryDSN,
  environment: config.sentryEnv,
  tracesSampleRate: 1.0,
});

xmunoz avatar May 22 '24 13:05 xmunoz

Could you share the gist of src/config/index.js? Is this importing other modules, or just exporting some static config stuff?

mydea avatar May 22 '24 14:05 mydea

I have modified the code as follows and it still crashes the app in the same way.

import * as Sentry from '@sentry/node';                                                                                                             

const { NODE_ENV } = process.env;

Sentry.init({
  dsn: 'actualDSNstring',
  environment: NODE_ENV,
  tracesSampleRate: 1.0,
});

Run command: NODE_ENV=development nodemon --inspect=0.0.0.0:9229 --import ./src/instrument.js ./src/index.js | pino-colada

Crash:

SyntaxError: Identifier '$longFormatters' has already been declared
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:169:18)
    at callTranslator (node:internal/modules/esm/loader:272:14)
    at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:278:30)
[nodemon] app crashed - waiting for file changes before starting...

Our prod run command is slightly different, but it also crashes like this on prod.

xmunoz avatar May 22 '24 15:05 xmunoz

I removed one of our dependencies in package.json, and it works now. The dependency in question is "date-fns": "^3.6.0". However, eliminating this dependency in order to use Sentry SDK v8 is not a compromise we can make at this time.

xmunoz avatar May 22 '24 16:05 xmunoz

our application also relies on date-fns. unfortunately we're in a monorepo so removing it may not be that simple, but that's good to know!

jorgeavaldez avatar May 22 '24 17:05 jorgeavaldez

Ah, I see, we've seen this before, there seems to be a problem with date-fns: https://github.com/date-fns/date-fns/pull/3770

This is related/a "duplicate" of https://github.com/getsentry/sentry-javascript/issues/12154 then, which runs into the same problem. We'll try to come up with a fix!

mydea avatar May 23 '24 08:05 mydea

We released https://github.com/getsentry/sentry-javascript/releases/tag/8.8.0 which should fix this problem. Please give it a try!

AbhiPrasad avatar Jun 07 '24 14:06 AbhiPrasad

This is still a problem on "@sentry/node": "^8.33.1"

ozozozd avatar Oct 10 '24 11:10 ozozozd

I too have this issue in a CJS app... followed everything for the CJS setup... it's literally the first thing I import and I have logs saying "sentry init" "express init" [Sentry] express is not instrumented...

sentry 8.42 express 4.21

pjs355 avatar Dec 05 '24 05:12 pjs355

I encountered the same issue, and here is how I resolved it:

1. Create a dedicated file called instrument.js:

import * as Sentry from "@sentry/node";
import { ProfilingIntegration } from "@sentry/profiling-node";

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [new ProfilingIntegration()],
  tracesSampleRate: 1.0,
  profilesSampleRate: 1.0,
});

export { Sentry };

2. Import this file at the very top of your app.js:

import { Sentry } from "./instrument.js";                                   
const app = express();
Sentry.setupExpressErrorHandler(app);

After implementing these changes, the error was resolved.

esmael-Abdlkadr avatar Jan 03 '25 22:01 esmael-Abdlkadr

Did you guys fix the error? I have the same issue and nothing commented here works 😿

Eduardo-Miguel avatar Jan 26 '25 05:01 Eduardo-Miguel

Still having this issue too...

Sentry: 8.51.0

Running typescript and Express and tsx

Found this comment and it removes my error too when I comment out tracesSampleRate https://github.com/hiroshinishio/sentry-javascript/issues/17#issuecomment-2497370829

GwFreak01 avatar Jan 26 '25 19:01 GwFreak01

Same here, moving Sentry.init before every other import does not help, removingtracesSampleRate fixes the issue.

"@sentry/node": "8.52.0" "express": "4.21.2"

mschmid avatar Jan 29 '25 21:01 mschmid

If you use express 4x, and have top-level import, and you are still getting this warning: The most probable cause is that your app is running in ESM mode. When you run with debug: true you should see a log about this (saying running in CommonJS or ESM mode). If it is ESM, you have to follow this guide to get rid of this warning: https://docs.sentry.io/platforms/javascript/guides/express/install/esm/

Commenting out the tracesSampleRate removes the warning because it then does not add the express tracing instrumentation. If you do not care about tracing this is fine, but if you want proper tracing and are using ESM, sadly --import is the only way to get this working as of today.

mydea avatar Jan 30 '25 13:01 mydea