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
2 changes: 1 addition & 1 deletion docs/platforms/android/troubleshooting/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ If you set up the Sentry SDK and it's not sending any data to Sentry:

- Check that you have configured a DSN and that you are passing it to the `dsn` option in `SentryAndroid.init()` or `AndroidManifest`.
- When a crash happens or the app closes, the system allows only a short time to handle it before terminating the process. The SDK might not have enough time to send the event, spans, profile, or replay data. In these cases, simply relaunching the app should allow the SDK to send the remaining data.
- An ad-blocker (e.g. [DuckDuckGo's App Tracking Protection](https://duckduckgo.com/duckduckgo-help-pages/p-app-tracking-protection/what-is-app-tracking-protection)) or network restrictions might be preventing the SDK from reaching Sentry's servers. As a workaround, you can use [tunneling](/platforms/javascript/troubleshooting/#using-the-tunnel-option) to proxy Sentry events through your own server. Then you can add the [proxy option](/platforms/android/configuration/options/#proxy) to your SDK configuration.
- An ad-blocker (e.g. [DuckDuckGo's App Tracking Protection](https://duckduckgo.com/duckduckgo-help-pages/p-app-tracking-protection/what-is-app-tracking-protection)) or network restrictions might be preventing the SDK from reaching Sentry's servers. As a workaround, you can use [tunneling](/platforms/javascript/configuration/tunneling/) to proxy Sentry events through your own server. Then you can add the [proxy option](/platforms/android/configuration/options/#proxy) to your SDK configuration.
- Set `debug: true` in the `SentryAndroid.init()` or the manifest options and observe your Logcat output when you start your application. The SDK may tell you why it is not sending any data.
- Check the [Stats](https://sentry.io/orgredirect/organizations/:orgslug/stats/) and [Subscription](https://sentry.io/orgredirect/organizations/:orgslug/settings/billing/overview/) pages in Sentry. You may be out of quota.

Expand Down
2 changes: 1 addition & 1 deletion docs/platforms/dotnet/guides/aspnetcore/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ Opt-in to capture values from [System.Diagnostic.Activity](https://github.com/do

### Tunnel

If events are being logged to Sentry from the client-side, then you should use a [tunnel](/platforms/javascript/troubleshooting/#using-the-tunnel-option).
If events are being logged to Sentry from the client-side, then you should use a [tunnel](/platforms/javascript/configuration/tunneling/).

A tunnel is an HTTP endpoint that acts as a proxy between Sentry and your application. Because you control this server, there is no risk of any requests sent to it being blocked.

Expand Down
Comment thread
logaretm marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
---
title: ServeTunnel
description: "Serves a tunnel on your server so Sentry browser SDK events reach Sentry through your own origin, past ad blockers."
supportedCategories:
- server
notSupported:
- javascript.nextjs
- javascript.solidstart
---

<AvailableSince version="11.10.0" />

_Import name: `Sentry.serveTunnelIntegration`_

Ad blockers often drop requests to `*.ingest.sentry.io`. With a tunnel on your own origin, the browser SDK sends its events to your server instead, and your server forwards them to Sentry. This integration configures that tunnel on the server.

For an overview of tunneling, including how to set up your own tunnel endpoint, see <PlatformLink to="/configuration/tunneling/">Tunneling</PlatformLink>.

Set it up on both sides. Add the integration to your server `Sentry.init`, and set `tunnel: true` in your browser `Sentry.init`:

```javascript {tabTitle:Server}
Sentry.init({
dsn: "___PUBLIC_DSN___",
integrations: [Sentry.serveTunnelIntegration()],
});
```

```javascript {tabTitle:Browser}
Sentry.init({
dsn: "___PUBLIC_DSN___",
tunnel: true,
});
```

The browser and the server both derive the tunnel path from the DSN, for example `/qpo967`. You don't need a route file, a build step, or an environment variable. If your app is served under a base path, the SDK adds it on both sides.

<PlatformSection supported={["javascript.sveltekit", "javascript.nuxt", "javascript.nitro", "javascript.astro", "javascript.tanstackstart-react"]}>

The SDK serves the tunnel from its request hooks, so this is all the setup you need.

</PlatformSection>

<PlatformSection supported={["javascript.react-router"]}>

In framework mode, the SDK serves the tunnel from its request hooks, so this is all the setup you need.

</PlatformSection>

<PlatformSection supported={["javascript.remix"]}>

On Remix 3, the SDK serves the tunnel from its request hooks, so this is all the setup you need. On earlier versions, you also need to [mount the tunnel handler](#mounting-the-tunnel-handler).

</PlatformSection>

<PlatformSection notSupported={["javascript.sveltekit", "javascript.nuxt", "javascript.nitro", "javascript.astro", "javascript.tanstackstart-react", "javascript.react-router", "javascript.remix"]}>

The integration doesn't handle requests by itself, so you also need to [mount the tunnel handler](#mounting-the-tunnel-handler).

</PlatformSection>

<PlatformSection supported={["javascript.cloudflare"]}>

On Cloudflare Workers, add the integration to your Sentry options, and call `handleTunnelRequestIfMatched` at the start of your `fetch` handler:

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

export default Sentry.withSentry(
(env) => ({
dsn: "___PUBLIC_DSN___",
integrations: [Sentry.serveTunnelIntegration()],
}),
{
async fetch(request, env, ctx) {
return (
(await Sentry.handleTunnelRequestIfMatched(request)) ??
new Response("Hello")
);
},
}
);
```

</PlatformSection>

## How It Works

- Framework SDKs answer tunnel requests before your app's own middleware, so auth or CSRF middleware doesn't block them. When you mount the tunnel handler yourself, put it before that middleware too.
- It only forwards envelopes whose DSN is allowed. By default, that's the server's own DSN. Other envelopes get a `403`, so the tunnel can't be used as an open proxy.
- It forwards only the user agent, client hint, `Origin`, and `Referer` headers. Cookies and auth headers never reach Sentry. The visitor's IP address is forwarded from the `cf-connecting-ip`, `x-vercel-forwarded-for`, or `x-forwarded-for` header.
- Tunnel requests don't create server spans.
- The tunnel is only served while the server SDK is enabled. Without a DSN, or with `enabled: false`, requests to the tunnel path get your app's 404 page, and the browser's events are lost.

## Options

Pass these options to `serveTunnelIntegration`. Most apps don't need any of them.

### `allowedDsns`

_Type: `string[]`_

DSNs to forward besides the server's own. Use this when the browser uses a different DSN than the server. The server's own DSN is always allowed.

```javascript
Sentry.serveTunnelIntegration({
allowedDsns: ["___PUBLIC_DSN___"],
});
```

### `path`

_Type: `string`_

A fixed tunnel path instead of the one derived from the DSN. The SDK serves it as written, without the base path.

Set the same path in the browser with the object form. Don't pass it as a string: the browser SDK then sends `text/plain`, which the CSRF protection in SvelteKit and Astro rejects.

```javascript {tabTitle:Server}
Sentry.serveTunnelIntegration({ path: "/monitoring" });
```

```javascript {tabTitle:Browser}
Sentry.init({
dsn: "___PUBLIC_DSN___",
tunnel: { path: "/monitoring" },
});
```

### `basePath`

_Type: `string`_

The path prefix your app is served under, for example `/docs`. The SDK reads it from your framework config, so you only need this option when it can't. Set the same value in the browser `tunnel` option.

## Mounting the Tunnel Handler

On servers without a framework SDK hook, mount `handleTunnelRequestIfMatched` before your routes and body parsers. It forwards tunnel requests and returns `undefined` for everything else:

```javascript {tabTitle:Express}
const express = require("express");
const Sentry = require("@sentry/node");

const app = express();

app.use(
(req, res, next) => Sentry.handleTunnelRequestIfMatched(req, res) ?? next()
);
```

```javascript {tabTitle:Fastify}
const Fastify = require("fastify");
const Sentry = require("@sentry/node");

const app = Fastify();

app.addHook("onRequest", async (request, reply) => {
const tunnelResponse = Sentry.handleTunnelRequestIfMatched(
request.raw,
reply.raw
);
if (tunnelResponse) {
await tunnelResponse;
return reply;
}
});
```

```javascript {tabTitle:Fetch API}
export default {
fetch(request) {
return (
Sentry.handleTunnelRequestIfMatched(request) ?? new Response("Hello")
);
},
};
```

`handleTunnelRequestIfMatched` takes a fetch `Request`, or Node's `req` and `res`, and the same options as the integration. Without options, `handleTunnelRequestIfMatched` uses the options of `serveTunnelIntegration`, or the server's own DSN if the integration isn't set up. To check a request without reading its body, use `isTunnelRequest(request)`.

If you'd rather use a fixed path, register it as a route with `handleTunnelRequest`. Pass the same path to the integration, so tunnel requests don't create server spans:

```javascript
Sentry.init({
dsn: "___PUBLIC_DSN___",
integrations: [Sentry.serveTunnelIntegration({ path: "/monitoring" })],
});

app.post("/monitoring", (req, res) => Sentry.handleTunnelRequest(req, res));
```

Set the same path in the browser, as shown in [`path`](#path). `handleTunnelRequest` also takes a fetch `Request`, and an `allowedDsns` option for DSNs besides the server's own.

Both helpers work without `serveTunnelIntegration`. Add it anyway, so tunnel requests don't create server spans.
87 changes: 72 additions & 15 deletions docs/platforms/javascript/common/configuration/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,55 @@ Environments are case-sensitive. The environment name can't contain newlines, sp

</SdkOption>

<PlatformCategorySection notSupported={['browser']}>

<SdkOption name="tunnel" type='string'>

Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in [Dealing with Ad-Blockers](/platforms/javascript/troubleshooting/#dealing-with-ad-blockers).
Sets the URL that will be used to transport captured events. This can be used to work around ad-blockers or to have more granular control over events sent to Sentry. Adding your DSN is still required when using this option so necessary attributes can be set on the generated Sentry data. This option **requires the implementation** of a custom server endpoint. Learn more and find examples in <PlatformLink to="/configuration/tunneling/">Tunneling</PlatformLink>.

The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in <PlatformLink to="/configuration/tunneling/#setting-up-your-own-tunnel">Setting Up Your Own Tunnel</PlatformLink>.

</SdkOption>

</PlatformCategorySection>

<PlatformCategorySection supported={['browser']}>

<SdkOption name="tunnel" type='string | true | object'>

Where the browser SDK sends captured events instead of the Sentry ingest URL. Use it to work around ad blockers or to control which events reach Sentry. The DSN is still required, so the SDK can initialize correctly and forward the Sentry ingest URL to the tunnel.

The `tunnel` option can be set to the following values:

- A string (URL or relative path) sends events to it as written. This **requires the implementation** of a custom server endpoint. The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in <PlatformLink to="/configuration/tunneling/#setting-up-your-own-tunnel">Setting Up Your Own Tunnel</PlatformLink>.

<PlatformSection notSupported={["javascript.nextjs", "javascript.solidstart"]}>

<PlatformCategorySection supported={["server"]}>

- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server needs to serve it, as described in <PlatformLink to="/configuration/integrations/servetunnel/">`serveTunnelIntegration`</PlatformLink>. Available since version `11.10.0`.

</PlatformCategorySection>

<PlatformCategorySection notSupported={["server"]}>

- `true` sends events to the managed tunnel, a path on your own origin derived from the DSN. Your server needs to serve it, as described in [`serveTunnelIntegration`](/platforms/javascript/guides/node/configuration/integrations/servetunnel/). Available since version `11.10.0`.

</PlatformCategorySection>

- An object works like `true`, with these options:
- `path`: a fixed tunnel path, used as written without the base path.
- `basePath`: the path prefix your app is served under, for example `/docs`. Most framework SDKs set it for you.
- `trailingSlash`: appends a trailing slash to the tunnel path, for servers that redirect paths without one.

The endpoint must forward only the request body to Sentry and accept only your own DSNs. Learn more in [Using the `tunnel` Option](/platforms/javascript/troubleshooting/#using-the-tunnel-option).
On the server, `tunnel` only takes a URL. It's where the server SDK sends its own events, and it's independent from `serveTunnelIntegration`.

</PlatformSection>

</SdkOption>

</PlatformCategorySection>

<PlatformSection supported={['javascript.aws-lambda']}>

<SdkOption name="useLayerExtension" type='boolean' defaultValue='true' envVar='SENTRY_LAYER_EXTENSION'>
Expand All @@ -108,7 +149,7 @@ Set to `false` to opt out, for example, when using a custom `tunnel` or when run
<SdkOption name="dataCollection" type='DataCollection' availableSince="10.57.0">

Controls which categories of data the SDK collects automatically. All fields are optional.
By default the SDK collects rich debugging context (including user identity, request/response bodies, and generative AI content) and scrubs values whose keys match the built-in sensitive denylist (`auth`, `token`, `password`, and similar).
By default the SDK collects rich debugging context (including user identity, request/response bodies, and generative AI content) and scrubs values whose keys match the built-in sensitive denylist (`auth`, `token`, `password`, and similar).

<PlatformSection notSupported={["javascript.electron"]}>

Expand Down Expand Up @@ -299,21 +340,35 @@ The callback gets a second argument (called a "hint") which contains the origina
<SdkOption name="transport" type="(transportOptions: TransportOptions) => Transport">

<PlatformSection notSupported={["javascript.electron"]}>
The JavaScript SDK uses a transport to send events to Sentry.
Transports will drop an event if it fails to send due to a lack of connection.
The JavaScript SDK uses a transport to send events to Sentry. Transports will
drop an event if it fails to send due to a lack of connection.
</PlatformSection>

<PlatformCategorySection supported={["browser"]}>
In the browser, a `fetch`-based transport is used by default.
</PlatformCategorySection>

<PlatformSection notSupported={["javascript.bun", "javascript.elysia", "javascript.deno", "javascript.cloudflare"]}>
<PlatformCategorySection supported={["server", "serverless"]}>
On the server, a `https`-based transport is used by default.
</PlatformCategorySection>
<PlatformSection
notSupported={[
"javascript.bun",
"javascript.elysia",
"javascript.deno",
"javascript.cloudflare",
]}
>
<PlatformCategorySection supported={["server", "serverless"]}>
On the server, a `https`-based transport is used by default.
</PlatformCategorySection>
</PlatformSection>

<PlatformSection supported={["javascript.bun", "javascript.elysia", "javascript.deno", "javascript.cloudflare"]}>
<PlatformSection
supported={[
"javascript.bun",
"javascript.elysia",
"javascript.deno",
"javascript.cloudflare",
]}
>
A `fetch`-based transport is used by default.
</PlatformSection>

Expand Down Expand Up @@ -551,7 +606,8 @@ The `samplingContext` object passed to the function has the following properties
- `name`: The name of the span as it was started.
- `attributes`: The initial attributes of the span.
<PlatformCategorySection supported={["browser"]}>
- `location`: The current page or worker URL, if available. Provided by the BrowserTracing integration.
- `location`: The current page or worker URL, if available. Provided by the
BrowserTracing integration.
</PlatformCategorySection>
<PlatformCategorySection supported={["server", "serverless"]}>
- `normalizedRequest`: Request information of the currently active HTTP
Expand Down Expand Up @@ -605,7 +661,7 @@ The SDK skips storage operation spans for Durable Object keys whose names start

Controls which bindings on `env` receive tracing data over [RPC calls](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/) to Durable Objects and other Workers. RPC has no headers, so the SDK appends the trace context as a trailing argument that an instrumented receiver strips again before your method runs.

String entries must match a binding name exactly, so `"DB"` matches `"DB"` but not `"MY_DB"`. Use a regular expression for pattern matching.
String entries must match a binding name exactly, so `"DB"` matches `"DB"` but not `"MY_DB"`. Use a regular expression for pattern matching.

This option controls outgoing RPC trace propagation, which is disabled by default. Add bindings to enable it for those receivers. Sentry-instrumented receivers automatically continue incoming traces without configuring this option.

Expand Down Expand Up @@ -860,7 +916,8 @@ Controls whether the SDK installs runtime hooks that instrument supported depend
</PlatformSection>

<Alert>
On SDK versions below `10.71.0`, logs are opt-in. Set `enableLogs: true` in your `Sentry.init` to send them.
On SDK versions below `10.71.0`, logs are opt-in. Set `enableLogs: true` in
your `Sentry.init` to send them.
</Alert>

<SdkOption name="beforeSendLog" type='(log: Log) => Log | null'>
Expand All @@ -872,8 +929,8 @@ This function is called with a log object, and can return a modified log object,
## Metrics Options

<PlatformSection supported={["javascript.electron"]}>
**Note:** For Electron, metric options apply to the process where these options
are set.
**Note:** For Electron, metric options apply to the process where these
options are set.
</PlatformSection>

<SdkOption name="beforeSendMetric" type='(metric: Metric) => Metric | null'>
Expand Down
Loading
Loading