Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 72 additions & 27 deletions docs/platforms/javascript/common/install/esm-without-import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,15 @@ supported:
[installation methods](../).
</Alert>

When running your application in ESM mode, you'll most likely want to <PlatformLink to="/install/esm">follow the ESM instructions</PlatformLink>.

When running your application in ESM mode, you will most likely want to <PlatformLink to="/install/esm">follow the ESM instructions</PlatformLink>. However, if you can't use the `--import` command line option, you can either use [direct imports](#direct-imports) or [SEA bootstrap setup](#nodejs-single-executable-applications) if you are using a Node.js Single Executable Application (SEA).
If you can't pass the `--import` command line option, pick one of these instead:

## Direct Imports
- Use a [bundler plugin with a top-level import](#bundler-plugin-and-top-level-import) if you bundle your server code.
- Use a [deferred entry point](#deferred-entry-point) if you don't bundle it.
- Use the [SEA bootstrap setup](#nodejs-single-executable-applications) if you build a Node.js Single Executable Application.


<Alert level='warning' title='Restrictions of this installation method'>

This installation method has the fundamental restriction that only native Node.js APIs can be instrumented (such as `fetch` and the `http` module).

As a result, the Sentry SDK will not capture data from database calls, queues, ORMs, third-party libraries, or other framework-specific data.

We recommend using this only if the `--import` flag is not an option for you.

</Alert>

You need to create a file named `instrument.mjs` that imports and initializes Sentry:
All three need an `instrument.mjs` file that initializes Sentry:

```javascript {tabTitle:ESM} {filename: instrument.mjs}
import * as Sentry from "@sentry/node";
Expand All @@ -48,18 +40,80 @@ Sentry.init({
});
```

You need to import the `instrument.mjs` file before importing any other modules in your application. This is necessary to ensure that Sentry can automatically instrument all modules in your application:
## Why a Top-Level Import Alone Isn't Enough

ESM evaluates every `import` in a file before it runs the first line of that file. So even when `import "./instrument.mjs"` is written above your other imports, the modules below it are already loaded by the time `Sentry.init()` runs, and the SDK can no longer wrap them.

What that costs depends on the library. Express, Fastify, Hapi and Hono register their instrumentation at runtime, so they still report spans and errors. Koa does not: it loses its router and middleware spans, its route names, and, without the deprecated `Sentry.setupKoaErrorHandler(app)` call, its error capture.

The patterns below work around this. The bundler plugin instruments your dependencies at build time, so the order no longer matters. The deferred entry point loads your application only after `Sentry.init()` has run.

## Bundler Plugin and Top-Level Import

Instrument your dependencies at build time with the Sentry bundler plugin, then import `instrument.mjs` at the top of your entry point. Because the instrumentation is baked into the bundle, it no longer depends on `Sentry.init()` running before the imports.

The plugin ships for Vite, Rollup, webpack and esbuild:

```javascript {tabTitle:Vite} {filename: vite.config.mjs}
import { sentryVitePlugin } from "@sentry/node/vite";

export default {
plugins: [sentryVitePlugin()],
};
```

```javascript {tabTitle:Rollup} {filename: rollup.config.mjs}
import { sentryRollupPlugin } from "@sentry/node/rollup";

export default {
plugins: [sentryRollupPlugin()],
};
```

```javascript {tabTitle:webpack} {filename: webpack.config.mjs}
import { sentryWebpackPlugin } from "@sentry/node/webpack";

export default {
plugins: [sentryWebpackPlugin()],
};
```

```javascript {tabTitle:esbuild} {filename: build.mjs}
import { sentryEsbuildPlugin } from "@sentry/node/esbuild";

await esbuild.build({
plugins: [sentryEsbuildPlugin()],
});
```

Then import your instrument file first:

```javascript {filename: app.mjs}
// Import this first!
import "./instrument";
import "./instrument.mjs";

// Now import other modules
import http from "http";
import express from "express";

// Your application code goes here
```

## Deferred Entry Point

Without a bundler, split the entry point in two. The static import runs `Sentry.init()`, and the dynamic `import()` loads your application afterwards, so your application's modules are wrapped:

```javascript {filename: main.mjs}
import "./instrument.mjs";

await import("./app.mjs");
```

Your application code stays in `app.mjs` and needs no changes. Start it as usual:

```bash
node main.mjs
```

## Node.js Single Executable Applications

Node.js Single Executable Applications (SEA) may not load your Sentry instrumentation early enough, so you need to package a small bootstrap file as the SEA main instead of packaging your app entrypoint directly.
Expand All @@ -85,16 +139,7 @@ async function startApp() {
startApp();
```

Keep your Sentry setup in `instrument.mjs`:

```javascript {tabTitle:ESM} {filename: instrument.mjs}
import * as Sentry from "@sentry/node";

Sentry.init({
dsn: "___PUBLIC_DSN___",
tracesSampleRate: 1.0,
});
```
This is the deferred entry point pattern above, packaged for SEA. Keep your Sentry setup in `instrument.mjs`.

Then configure SEA to use `sea-main.cjs` as its main script:

Expand Down
28 changes: 1 addition & 27 deletions docs/platforms/javascript/common/install/esm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Sentry.init({
});
```

Adjust the Node.js call for your application to use the [--import](https://nodejs.org/api/cli.html#--importmodule) parameter and point it at `instrument.js`, which contains your `Sentry.init()` code:
Adjust the Node.js call for your application to use the [--import](https://nodejs.org/api/cli.html#--importmodule) parameter and point it at `instrument.mjs`, which contains your `Sentry.init()` code:

```bash
# Note: This is only available for Node v18.19.0 onwards.
Expand All @@ -52,29 +52,3 @@ on `--import` or `NODE_OPTIONS`, use the <PlatformLink to="/install/esm-without-
bootstrap setup</PlatformLink> instead.

We do not support ESM in Node versions before 18.19.0.

## Troubleshooting instrumentation

By default, all packages are wrapped under the hood by
[import-in-the-middle](https://www.npmjs.com/package/import-in-the-middle) to
aid instrumenting them.

If `import-in-the-middle` encounters problems wrapping a package, you may see
syntax errors at runtime or logged errors in your console:

```logs
SyntaxError: The requested module '...' does not provide an export named '...'
(node:3368) Error: 'import-in-the-middle' failed to wrap 'file://../../path/to/file.js'
```

To confirm that these errors are caused by `import-in-the-middle`,
disable it by setting `registerEsmLoaderHooks` to false. Note, this will also
disable tracing instrumentation:

```javascript {tabTitle:ESM} {filename: instrument.mjs} {4}
import * as Sentry from "@sentry/node";

Sentry.init({
registerEsmLoaderHooks: false,
});
```
37 changes: 25 additions & 12 deletions docs/platforms/javascript/common/install/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,41 @@ notSupported:

## How To Decide Which Installation Method To Use

Most node applications today are either written in CommonJS (CJS), or compiled to CJS before running them.
CommonJS uses `require()` to load modules. Our recommended installation method when using CommonJS is to require the `instrument.js` file at the top of your application. However, if your application is run in ESM mode, this will not work. In this case, you can follow the [ESM docs](./esm).
The SDK has to wrap the modules your application uses before your application imports them. How you arrange that is the only difference between the methods below. `--import` works for both ESM and CommonJS applications, so the module system your application uses no longer decides the method.

Note that even if your application is written in ESM (using `import`), it may still be _run_ in CJS. In this case, you should follow the [CommonJS instructions](./commonjs).
### Load Your Instrument File With `--import` (Recommended)

### My application is in TypeScript
Put `Sentry.init()` in an instrument file and load it with the `--import` flag:

If you're using TypeScript, your application is likely compiled to CommonJS before running it. In this case, you should follow the [CommonJS instructions](./commonjs).
```bash
node --import ./instrument.mjs app.mjs
```

### My application uses `require`
This runs `Sentry.init()` before any of your application's modules load, so every instrumented library is wrapped and every error during startup is captured. Use this unless one of the cases below applies to you.

If you are using `require()` in your application, you should follow the [CommonJS instructions](./commonjs).
`--import` works for CommonJS applications too, with an `instrument.js` file:

### My application uses `import`
```bash
node --import ./instrument.js app.js
```

If you are using `import` in your application, your installation method depends on how your application is _run_. If you compile your application (e.g. into a `/dist` folder or similar) before running this, you need to check how the compiled code looks like. Is the compiled code using `require`? Then you should follow the [CommonJS instructions](./commonjs). If the compiled code is using `import`, you should follow the [ESM instructions](./esm).
Note that `--require` is no longer supported for initialization. See <PlatformLink to="/install/esm">ESM (MJS)</PlatformLink> and <PlatformLink to="/install/commonjs">CommonJS (CJS)</PlatformLink>.

If you do not compile your code, you'll need to follow the [ESM instructions](./esm).
### Bundler Plugin and a Top-Level Import

### I don't need automatic spans/transactions
If you can't pass a flag to the Node.js binary, instrument your dependencies at build time with the Sentry bundler plugin and import `instrument.mjs` at the top of your entry file. See <PlatformLink to="/install/esm-without-import">ESM without CLI Flag</PlatformLink>.

If you don't need spans emitted by OpenTelemetry instrumentation, you can use `@sentry/node-core` in [Lightweight Mode](./lightweight) without OpenTelemetry dependencies. You still get errors, logs, metrics, breadcrumbs, and more. This mode is experimental.
A top-level import on its own isn't equivalent to `--import`, because ESM evaluates every import in a file before it runs the first line of that file. That page explains what you lose and how to avoid it.

### Load the SDK With `--import` and Initialize Later

If you can't call `Sentry.init()` at startup, for example because you fetch your DSN from an external source, `--import` the SDK itself and call `Sentry.init()` at a later point:

```bash
node --import @sentry/node/import main.mjs
```

See <PlatformLink to="/install/late-initialization">Late Initialization</PlatformLink>.

</PlatformCategorySection>

Expand Down
107 changes: 21 additions & 86 deletions docs/platforms/javascript/common/install/late-initialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,67 +17,30 @@ supported:
[installation methods](../).
</Alert>

In order for auto-instrumentation to work, it is generally required to run `Sentry.init()` as early as possible, before anything else is imported in your application.
For auto-instrumentation to work, `Sentry.init()` normally has to run before anything else is imported in your application.

However, in some cases this may not be possible to do—for example, if you are fetching your DSN from an external source. In this case, you can use the `@sentry/node/preload` hook to ensure modules are wrapped early, which allows you to call `Sentry.init()` later at a time of your choosing.

<Alert>
We recommend to only use this method if strictly necessary. In most cases, it
is better to find a way to run `Sentry.init()` early in your application, in
order to ensure that no error can go unreported.
</Alert>

This initialization method is available starting in version **8.5.0**.

## Late Initialization with CommonJS (CJS)

In your CJS application, use the `@sentry/node/preload` hook with `--require` to ensure modules are wrapped early:
In some cases that isn't possible, for example when you fetch your DSN from an external source. For these cases, `--import` the SDK itself instead of your own instrument file. The SDK then wraps the modules your application imports, and you call `Sentry.init()` later:

```bash
node --require @sentry/node/preload app.js
node --import @sentry/node/import main.mjs
```

Then, in your application you can call `Sentry.init()` at a later point:

```javascript {filename: main.js}
const startApp = require("./app");
const fetchDsn = require("./utils/fetchDsn");
const Sentry = require("@sentry/node");

startApp();

const dsn = fetchDsn();
Sentry.init({
dsn,

// Add Tracing by setting tracesSampleRate
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
});

// From now on, Sentry is initialized,
// but the app is still auto-instrumented
```

## Late Initialization with ESM

In your ESM application, use the `@sentry/node/preload` hook with `--import` to ensure modules are wrapped early:
This works for CommonJS applications as well. `--require` is no longer supported, so use `--import` there too:

```bash
# Note: This is only available for Node v18.19.0 onwards.
node --import @sentry/node/preload app.js
node --import @sentry/node/import app.js
```

Then, in your application you can call `Sentry.init()` at a later point:
With the flag in place, call `Sentry.init()` at the point of your choosing:

```javascript {filename: main.js}
import startApp from "./app";
import fetchDsn from "./utils/fetchDsn";
```javascript {filename: main.mjs}
import * as Sentry from "@sentry/node";
import startApp from "./app.mjs";
import fetchDsn from "./utils/fetchDsn.mjs";

startApp();

const dsn = fetchDsn();
const dsn = await fetchDsn();
Sentry.init({
dsn,

Expand All @@ -87,47 +50,19 @@ Sentry.init({
});

// From now on, Sentry is initialized,
// but the app is still auto-instrumented
```

## What does Preloading mean?

Integrations that are preloaded ensure that the necessary modules are wrapped early, before they can be imported by your application. At this point, the modules are wrapped, but will not do anything—nothing will be emitted or captured from them.

Once you call `Sentry.init()`, the wrapped modules will automatically start emitting performance data which will be sent to Sentry.

## What is Preloaded?

By default, all performance instrumentation is preloaded when using the `@sentry/node/preload` hook.

You can optionally configure to only preload certain integrations by defining a `SENTRY_PRELOAD_INTEGRATIONS` environment variable. This variable should be a comma-separated list of integrations to preload. For example, to only preload the `Http` and `Express` integrations, you can set the environment variable as follows:

```bash
SENTRY_PRELOAD_INTEGRATIONS="Http,Express" node --require @sentry/node/preload app.js
// and the app is still auto-instrumented
```

You can pass the names of any of the following integrations:

- `Http`
- `Express`
- `Connect`
- `Fastify`
- `Hapi`
- `Koa`
- `Nest`
- `Mongo`
- `Mongoose`
- `Mysql`
- `Mysql2`
- `Postgres`
- `Graphql`

Note that it is not necessary to preload `NodeFetch`, this will always be instrumented.
<Alert>
Use this method only if it's strictly necessary. Anything that happens before
`Sentry.init()` runs isn't captured, so an error thrown during startup goes
unreported. In most cases it's better to find a way to run `Sentry.init()`
early, as described in{" "}
<PlatformLink to="/install/esm">ESM (MJS)</PlatformLink>.
</Alert>

## Debugging Preload
## What Wrapping Means

You can also define a `SENTRY_DEBUG` environment variable in order to get debug logs from the preload hook. This can be useful to understand what is happening during the preload process.
`--import @sentry/node/import` wraps the modules your application imports before your code runs. At that point the modules are wrapped but inactive: nothing is emitted or captured from them.

```bash
SENTRY_DEBUG=1 node --require @sentry/node/preload app.js
```
Once you call `Sentry.init()`, the wrapped modules start emitting data to Sentry.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Sentry.init({

```javascript {filename: main.ts}
// Import this first!
import "./instrument";
import "./instrument.mjs";

// Now import other modules
import { NestFactory } from "@nestjs/core";
Expand Down
4 changes: 2 additions & 2 deletions platform-includes/getting-started-run/javascript.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
```bash
# If you are using CommonJS (CJS)
node --require ./instrument.js app.js
node --import ./instrument.js app.js

# If you are using ECMAScript Modules (ESM)
# Note: This is only available for Node v18.19.0 onwards.
Expand All @@ -9,6 +9,6 @@ node --import ./instrument.mjs app.mjs

<Alert level="warning" title="Alternative">

If you can't run node with `--require` or `--import`, <PlatformLink to="/initialize-sentry#importing-the-sentry-initialization-file-directly">import the Sentry Initialization file directly</PlatformLink>.
If you can't run node with `--import`, <PlatformLink to="/initialize-sentry#importing-the-sentry-initialization-file-directly">import the Sentry Initialization file directly</PlatformLink>.

</Alert>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ app.listen(3030);

```javascript {tabTitle:ESM}
// Import this first!
import "./instrument";
import "./instrument.mjs";

// Now import other modules
import connect from "connect";
Expand Down
Loading
Loading