Skip to content

feat(js): Move some configuration pages to "Best Practices" #9571

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

Merged
merged 4 commits into from
Apr 5, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: Browser Extensions
sidebar_order: 50
description: Learn how to use Sentry in shared environments (for example in browser extensions or VSCode extensions).
keywords:
[
"BrowserClient",
"NodeClient",
"shared environment",
"browser extension",
"VSCode extension"
]
---

When setting up Sentry in a shared environment where multiple Sentry instances may run, for example, in a browser extension or similar, you should **not use `Sentry.init()`**, as this will pollute the global state. If your browser extension uses `Sentry.init()`, and the website the extension is running on also uses Sentry, the extension may send events to the website's Sentry project, or vice versa.

For such scenarios, you have to set up a client manually as seen in the example below.
In addition, you should also avoid adding any integrations that use global state, like `Breadcrumbs` or `TryCatch`.
As a rule of thumb, it's best to avoid using any integrations and to rely on manual capture of errors only in such scenarios.

<SignInNote />

```javascript
import {
BrowserClient,
defaultStackParser,
defaultIntegrations,
makeFetchTransport,
Scope,
} from "@sentry/browser";

const client = new BrowserClient({
dsn: "___PUBLIC_DSN___",
transport: makeFetchTransport,
stackParser: defaultStackParser,
integrations: defaultIntegrations,
});

const scope = new Scope();
scope.setClient(client);

client.init() // initializing has to be done after setting the client on the scope

// You can capture exceptions manually for this client like this:
scope.captureException(new Error("example"));

```

If you notice that Sentry is not capturing error events from the browser extension,
try disabling the Browser extension errors option in the Inbound Filters of your Sentry configuration.
Read more about Inbound Filters in the product documentation under [Inbound filters](/product/data-management-settings/filtering/#inbound-data-filters).
7 changes: 7 additions & 0 deletions docs/platforms/javascript/common/best-practices/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Best Practices
description: "Learn how to set up Sentry in specific scenarios with those best practice guides"
sidebar_order: 3
---

<PageGrid />
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Micro Frontend Support
title: Micro Frontends
sidebar_order: 200
description: Learn how to identify the source of errors and route events to different Sentry projects when using micro frontend or module federation.
keywords:
Expand All @@ -14,7 +14,7 @@ keywords:
If your frontend includes JavaScript bundles from multiple sources with
different release cycles, you may want to identify these or route events to specific projects. This is especially useful if you've set up [module federation](https://module-federation.github.io/) or a similar frontend architecture.

Below we offer two approaches.
Below we offer two approaches.

<Note>
In all cases `Sentry.init()` must never be called more than once, doing so will result in undefined behavior.
Expand Down Expand Up @@ -60,7 +60,7 @@ module.exports = {
};
```

Once metadata has been injected into modules, the `moduleMetadataIntegration`
Once metadata has been injected into modules, the `moduleMetadataIntegration`
can be used to look up that metadata and attach it to stack frames with
matching file names. This metadata is then available in the `beforeSend` callback
as the `module_metadata` property on each `StackFrame`. This can be used to identify
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
title: Multiple Sentry Instances
sidebar_order: 300
description: Learn how to manage several Sentry instances by creating your own clients.
keywords:
[
"multiple instances",
"multiple clients",
"BrowserClient",
"NodeClient",
"monorepo"
]
---

<Note>

Creating multiple Sentry clients is not recommended in general, as it can lead to unexpected behavior. If you are using Micro Frontends or similar, multiplexing might be a better solution that using multiple clients. Check out [Micro Frontends](/platforms/javascript/best-practices/micro-frontends) in the Best Practices for more information.

</ Note>

To be able to manage several Sentry instances without any conflicts between them you need to create your own `Client`.
This also helps to prevent tracking of any parent application errors in case your application is integrated
inside of it. In this example we use `BrowserClient` from `@sentry/browser` but it's also applicable to `NodeClient` from `@sentry/node`.

<SignInNote />

```javascript
import {
BrowserClient,
defaultStackParser,
defaultIntegrations,
makeFetchTransport,
Scope,
} from "@sentry/browser";

const client = new BrowserClient({
dsn: "___PUBLIC_DSN___",
transport: makeFetchTransport,
stackParser: defaultStackParser,
integrations: defaultIntegrations,
});

const scope = new Scope();
scope.setClient(client);

client.init() // initializing has to be done after setting the client on the scope

// You can capture exceptions manually for this client like this:
scope.captureException(new Error("example"));
```

You can now customize the scope to your liking, without affecting other hubs/clients.

### Dealing with Integrations

Integrations are setup on the `Client`, if you need to deal with multiple clients and hubs you have to make sure to also do the integration handling correctly.

We do not recommend doing this if you are using Sentry in a browser extension or in similar scenarios.
If you can't avoid using global integrations (e.g. in a micro frontend application), here is a working example of how to use multiple clients with multiple scopes running global integrations.

<SignInNote />

```javascript
import * as Sentry from "@sentry/browser";

// Very happy integration that'll prepend and append very happy stick figure to the message
function happyIntegration() {
return {
name: 'Happy',
setupOnce() {
Sentry.addGlobalEventProcessor((event) => {
const self = Sentry.getClient().getIntegration(HappyIntegration);
// Run the integration ONLY when it was installed on the current Hub
if (self) {
event.message = `\\o/ ${event.message} \\o/`;
}
return event;
});
}
}
}

const client1 = new Sentry.BrowserClient({
dsn: "___PUBLIC_DSN___",
transport: Sentry.makeFetchTransport,
stackParser: Sentry.defaultStackParser,
integrations: [...Sentry.defaultIntegrations, happyIntegration()],
beforeSend(event) {
console.log("client 1", event);
return null; // Returning `null` prevents the event from being sent
},
});
const scope1 = new Sentry.Scope(client1);

const client2 = new Sentry.BrowserClient({
dsn: "___PUBLIC_DSN___", // Can be a different DSN
transport: Sentry.makeFetchTransport,
stackParser: Sentry.defaultStackParser,
integrations: [...Sentry.defaultIntegrations, happyIntegration()],
beforeSend(event) {
console.log("client 2", event);
return null; // Returning `null` prevents the event from being sent
},
});
const scope2 = new Sentry.Scope(client2);

scope1.captureMessage("a");
scope1.setTag("a", "b");

scope2.captureMessage("x");
scope2.setTag("c", "d");
```


109 changes: 0 additions & 109 deletions docs/platforms/javascript/common/troubleshooting/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,115 +284,6 @@ If you would like to copy and paste the snippet directly, here it is minified:
</script>
```

## Using a Client directly

To be able to manage several Sentry instances without any conflicts between them you need to create your own `Client`.
This also helps to prevent tracking of any parent application errors in case your application is integrated
inside of it. In this example we use `@sentry/browser` but it's also applicable to `@sentry/node`.

<SignInNote />

```javascript
import {
BrowserClient,
defaultStackParser,
defaultIntegrations,
makeFetchTransport,
Scope,
} from "@sentry/browser";

const client = new BrowserClient({
dsn: "___PUBLIC_DSN___",
transport: makeFetchTransport,
stackParser: defaultStackParser,
integrations: defaultIntegrations,
});

const scope = new Scope();
scope.setClient(client);

client.init() // initializing has to be done after setting the client on the scope

// You can capture exceptions manually for this client like this:
scope.captureException(new Error("example"));
```

You can now customize the hub to your liking, without affecting other hubs/clients.

### Setting up Sentry in shared environments (e.g. Browser Extensions)

When setting up Sentry in a shared environment where multiple Sentry instances may run, for example, in a browser extension or similar, you should **not use `Sentry.init()`**, as this will pollute the global state. If your browser extension uses `Sentry.init()`, and the website the extension is running on also uses Sentry, the extension may send events to the website's Sentry project, or vice versa.

For such scenarios, you have to set up a client manually as seen in the example above.
In addition, you should also avoid adding any integrations that use global state, like `Breadcrumbs` or `TryCatch`.
As a rule of thumb, it's best to avoid using any integrations and to rely on manual capture of errors only in such scenarios.

### Dealing with Integrations

Integrations are setup on the `Client`, if you need to deal with multiple clients and hubs you have to make sure to also do the integration handling correctly.

We do not recommend doing this if you are using Sentry in a browser extension or in similar scenarios.
If you can't avoid using global integrations (e.g. in a micro frontend application), here is a working example of how to use multiple clients with multiple hubs running global integrations.

<SignInNote />

```javascript
import * as Sentry from "@sentry/browser";

// Very happy integration that'll prepend and append very happy stick figure to the message
function happyIntegration() {
return {
name: 'Happy',
setupOnce() {
Sentry.addGlobalEventProcessor((event) => {
const self = Sentry.getClient().getIntegration(HappyIntegration);
// Run the integration ONLY when it was installed on the current Hub
if (self) {
event.message = `\\o/ ${event.message} \\o/`;
}
return event;
});
}
}
}

const client1 = new Sentry.BrowserClient({
dsn: "___PUBLIC_DSN___",
transport: Sentry.makeFetchTransport,
stackParser: Sentry.defaultStackParser,
integrations: [...Sentry.defaultIntegrations, happyIntegration()],
beforeSend(event) {
console.log("client 1", event);
return null; // Returning `null` prevents the event from being sent
},
});
const hub1 = new Sentry.Hub(client1);

const client2 = new Sentry.BrowserClient({
dsn: "___PUBLIC_DSN___", // Can be a different DSN
transport: Sentry.makeFetchTransport,
stackParser: Sentry.defaultStackParser,
integrations: [...Sentry.defaultIntegrations, happyIntegration()],
beforeSend(event) {
console.log("client 2", event);
return null; // Returning `null` prevents the event from being sent
},
});
const hub2 = new Sentry.Hub(client2);

hub1.run((currentHub) => {
// The `hub.run` method makes sure that `Sentry.getCurrentHub()` returns this hub during the callback
currentHub.captureMessage("a");
Sentry.getCurrentScope().setTag("a", "b");
});

hub2.run((currentHub) => {
// The `hub.run` method makes sure that `Sentry.getCurrentHub()` returns this hub during the callback
currentHub.captureMessage("x");
Sentry.getCurrentScope().setTag("c", "d");
});
```

## Third Party Promise Libraries

When you include and configure Sentry, our JavaScript SDK automatically attaches global handlers to _capture_ uncaught exceptions and unhandled promise rejections. You can disable this default behavior by changing the `onunhandledrejection` option to `false` in your GlobalHandlers integration and manually hook into each event handler, then call `Sentry.captureException` or `Sentry.captureMessage` directly.
Expand Down
12 changes: 12 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,18 @@ const REDIRECTS: {from: PathWithTrailingSlash; to: string}[] = [
from: '/product/sentry-basics/search/searchable-properties/session-replay/',
to: '/product/reference/search/searchable-properties/session-replay/',
},
{
from: '/platforms/javascript/configuration/micro-frontend-support/',
to: '/platforms/javascript/best-practices/micro-frontends/',
},
{
from: '/platforms/javascript/configuration/sentry-testkit/',
to: '/platforms/javascript/best-practices/sentry-testkit/',
},
{
from: 'platforms/javascript/configuration/webworkers/',
to: '/platforms/javascript/best-practices/web-workers/',
},
];

const redirectMap = new Map(REDIRECTS.map(r => [r.from as string, r.to]));
12 changes: 12 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,18 @@
"source": "/platforms/javascript/sourcemaps/tools/",
"destination": "/platforms/javascript/sourcemaps/"
},
{
"source": "/platforms/javascript/guides/([^/]*)/configuration/micro-frontend-support/",
"destination": "/platforms/javascript/guides/$1/best-practices/micro-frontends/"
},
{
"source": "/platforms/javascript/guides/([^/]*)/configuration/sentry-testkit/",
"destination": "/platforms/javascript/guides/$1/best-practices/sentry-testkit/"
},
{
"source": "/platforms/javascript/guides/([^/]*)/configuration/webworkers/",
"destination": "/platforms/javascript/guides/$1/best-practices/web-workers/"
},
{
"source": "/platforms/apple/guides/([^/]*)/configuration/integrations/([^/]*)/",
"destination": "/platforms/apple/guides/$1/integrations/$2/"
Expand Down