> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/configuration/error-monitoring.md).

# Error Monitoring (Sentry)

MyIP ships with optional [Sentry](https://sentry.io/) instrumentation on both halves of the app. It is entirely env-gated.

{% hint style="success" %}
**If you set none of these variables, your deployment behaves exactly like a version that never had Sentry.** No Sentry code is included in the frontend bundle, `@sentry/node` is never imported by the backend, and the tunnel route is not mounted. Nothing is sent anywhere.
{% endhint %}

| Variable                   | Half               | When it is read                                       |
| -------------------------- | ------------------ | ----------------------------------------------------- |
| `VITE_SENTRY_DSN_FRONTEND` | Frontend + backend | **Build time** for the bundle, runtime for the tunnel |
| `SENTRY_DSN_BACKEND`       | Backend            | Runtime                                               |
| `SENTRY_ENVIRONMENT`       | Backend            | Runtime (and build time, for source maps)             |
| `SENTRY_ORG`               | Build tooling      | Build time                                            |
| `SENTRY_PROJECT_FRONTEND`  | Build tooling      | Build time                                            |
| `SENTRY_AUTH_TOKEN`        | Build tooling      | Build time                                            |

The two halves are independent. Enabling backend monitoring alone is a perfectly normal setup, and the simplest one for Docker users.

## How the pieces fit

```
Browser (Vue SPA)
   │  errors, traces, replays
   ▼
POST /api/monitoring   ← first-party tunnel, same origin
   │  (backend validates the envelope's DSN, then forwards)
   ▼
Sentry  ◀── direct connection ── Express backend (errors, traces, warn+ logs)
```

## Backend — `SENTRY_DSN_BACKEND`

Set the DSN and restart. That is the whole setup.

{% code title=".env" %}

```bash
SENTRY_DSN_BACKEND="https://<key>@oNNNNN.ingest.sentry.io/<project-id>"
```

{% endcode %}

What turns on:

* **Uncaught errors and 5xx responses** from any `/api/*` route, via Sentry's Express error handler.
* **Performance tracing** at 100% sampling — per-route latency, throughput, and error rate.
* **Log forwarding.** The shared pino logger mirrors `warn` and above into Sentry Logs; `error` and above additionally become a grouped, alertable Issue. So the `logger.error({ err }, '…')` calls throughout `api/` are deliberate signals, not just log lines. See [Logging](/developer/configuration/logging.md).
* **Cron monitoring** for the scheduled dataset jobs (MaxMind auto-update, CAIDA refresh, service-status polling). Monitors are created on the first check-in — nothing to pre-create in the Sentry UI.

The SDK is bootstrapped through `node --import ./sentry-instrument.js`, which registers loader hooks before Express loads. The `dev`, `start`, and pm2 start commands already pass that flag, so there is nothing to add.

{% hint style="info" %}
Startup prints `🛰️ Sentry backend monitoring enabled` when the DSN was picked up. No line means no DSN was seen.
{% endhint %}

## Frontend — `VITE_SENTRY_DSN_FRONTEND`

This one is a **build-time** variable. Vite inlines it as a constant, and the dynamic import of the Sentry module sits behind that constant. Without it, dead-code elimination removes the branch and the SDK chunk is never even emitted.

```bash
VITE_SENTRY_DSN_FRONTEND="https://<key>@oNNNNN.ingest.sentry.io/<project-id>"
pnpm run build
```

What turns on:

* Uncaught exceptions and `console.error()` calls, grouped per message.
* Route-level performance tracing at 10% sampling, with Web Vitals.
* Error-only Session Replay — nothing is recorded unless an error fires.

{% hint style="warning" %}
**The same value must also be present at runtime.** The backend reads `VITE_SENTRY_DSN_FRONTEND` to decide whether to mount `/api/monitoring` and to know which DSN it is allowed to relay. Build with it but forget to pass it to the running process, and the browser SDK posts into a `404`.
{% endhint %}

### Replay and privacy

Page text is deliberately **not** masked in replays: this app's entire UI is the visitor's own network information, which is exactly the context needed to debug it. Typed input stays masked. On the public IPCheck.ing instance this is disclosed in the privacy policy — if you turn frontend monitoring on for your own users, disclose it too.

Both halves run with `sendDefaultPii: false`, so visitor IPs and headers are not attached automatically by the SDKs. Query parameters that look like credentials (`key`, `api_key`, `token`, `secret`, `password`, `auth`) are stripped from breadcrumbs, spans, and request contexts before anything is sent — upstream URLs carry your API keys, and Sentry records URLs in several places.

## The `/api/monitoring` tunnel

Ad blockers and privacy extensions block requests to `*.ingest.sentry.io`. For an audience of network-savvy users, that is a large share of visitors, and it silently deletes most of your error data.

So the browser SDK does not talk to Sentry directly. It POSTs its envelopes to `/api/monitoring` on your own origin, and the backend relays them.

How the route behaves:

* **Mounted only when `VITE_SENTRY_DSN_FRONTEND` is set** at runtime. Otherwise the path is a plain `404`.
* **Not an open relay.** The envelope header carries the DSN the browser SDK was configured with. If it does not exactly match `VITE_SENTRY_DSN_FRONTEND`, the request is rejected with `403`. Without that check, anyone could use your server to post into arbitrary Sentry accounts.
* **Its own rate limit**: 600 requests per IP per 20 minutes, and it is **exempt from the global `/api` limiter**. Telemetry sharing the app's quota is how reporting silently dies — one `429` and the browser SDK drops every event for the next minute.
* **Visitor IP stamping**: once an envelope leaves the relay, Sentry only sees your server's address. The backend writes the visitor's real IP (from `CF-Connecting-IP`, when present and valid) into event items before forwarding.
* **Failures are soft**: a relay error answers `502` and logs a `warn`. It never breaks the page.

## `SENTRY_ENVIRONMENT`

Tags backend events so you can filter production from development in the Sentry UI.

* Unset means `production`.
* Set `SENTRY_ENVIRONMENT="development"` on dev machines. That also disables cron check-ins, so closing your laptop does not page you with "missed" alerts.
* The frontend tags itself automatically from the Vite build mode — there is nothing to set.

{% hint style="info" %}
"Why is there no data in Sentry?" is, more often than not, the environment filter in the Sentry UI rather than a broken setup. Check it before you check your config.
{% endhint %}

## Source maps — `SENTRY_ORG` / `SENTRY_PROJECT_FRONTEND` / `SENTRY_AUTH_TOKEN`

Without source maps, frontend stack traces point at minified bundle offsets. The build can upload them to Sentry so traces resolve back to real source files.

{% code title=".env" %}

```bash
SENTRY_ORG="your-org-slug"
SENTRY_PROJECT_FRONTEND="your-frontend-project-slug"
SENTRY_AUTH_TOKEN="sntrys_..."
```

{% endcode %}

Upload runs only when **both** conditions hold:

1. `SENTRY_AUTH_TOKEN` is set, and
2. `SENTRY_ENVIRONMENT` is `production` (or unset, which means production).

Dev and test builds therefore neither generate nor upload maps.

{% hint style="danger" %}
`SENTRY_AUTH_TOKEN` is a real secret and a build-time-only one. It is never inlined into the bundle and never reaches the browser. Keep it out of your image, out of your repo, and scoped to source-map upload.
{% endhint %}

Maps are generated as hidden source maps, uploaded, and then deleted from `dist/` — visitors cannot download them.

## Docker

Backend monitoring is easy; frontend monitoring is not, and the reason is worth understanding.

{% tabs %}
{% tab title="Backend only (recommended)" %}

```bash
docker run -d -p 18966:18966 \
  -e SENTRY_DSN_BACKEND="https://<key>@oNNNNN.ingest.sentry.io/<id>" \
  -e SENTRY_ENVIRONMENT="production" \
  --name myip \
  jason5ng32/myip:latest
```

Read at runtime. Works with the official prebuilt image, no rebuild involved.
{% endtab %}

{% tab title="Frontend (needs a custom build)" %}
`VITE_SENTRY_DSN_FRONTEND` is consumed by `pnpm run build` **inside** the image build. Passing it with `docker run -e` therefore cannot put Sentry into an already-built bundle.

The official image is built without any DSN, so its frontend is permanently Sentry-free. To enable frontend monitoring you must build your own image and make the value visible to the build stage — for example by adding an `ARG` / `ENV` pair to the `Dockerfile` before `RUN pnpm run build`.

Then pass the same DSN at runtime as well, so the tunnel route mounts:

```bash
docker run -d -p 18966:18966 \
  -e VITE_SENTRY_DSN_FRONTEND="https://<key>@oNNNNN.ingest.sentry.io/<id>" \
  --name myip \
  your-image:latest
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
`.env` is excluded from the Docker build context, so a plain `docker build` never picks up a DSN from it by accident.
{% endhint %}

## Verifying your setup

* **Backend**: look for `🛰️ Sentry backend monitoring enabled` in the startup log.
* **Tunnel**: `POST /api/monitoring` should not return `404`. A `404` means the runtime does not see `VITE_SENTRY_DSN_FRONTEND`.
* **Frontend bundle**: if you built without a DSN, no Sentry chunk exists in `dist/assets/` at all.
* **Nothing arriving**: check the environment filter, the project selector, and the time range in the Sentry UI, in that order.

## Related pages

* [Logging](/developer/configuration/logging.md) — the pino levels that feed Sentry Logs and Issues
* [Security Options](/developer/configuration/security-options.md) — why the tunnel is exempt from the global limiter
* [Environment Variables](/developer/reference/environment-variables.md) — the full list
* [Deploy with Docker](/developer/getting-started/deploy-with-docker.md) — build and runtime basics


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/configuration/error-monitoring.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
