---
title: "Sentry Adapter"
description: "Send wide events to Sentry Logs and turn server errors into Sentry issues that carry the event's trace. Zero-config setup with a DSN."
canonical_url: "https://www.evlog.dev/integrate/adapters/cloud/sentry"
---
# Sentry Adapter

> Send wide events to Sentry Logs and turn server errors into Sentry issues that carry the event's trace. Zero-config setup with a DSN.

[Sentry](https://sentry.io) is an error tracking and performance monitoring platform. The evlog Sentry adapter sends your wide events as **Sentry Structured Logs**, visible in **Explore > Logs** in the Sentry dashboard with high-cardinality searchable attributes. With `issues: true`, which the Quick Start below sets, every server error also opens a **Sentry issue** with its stack and the wide event's trace, so issue alerts and the issue page work on evlog errors and lead back to the full event.

<prompt :actions="["copy","cursor","claude"]" description="Add the Sentry drain adapter" icon="i-simple-icons-sentry">

Add the Sentry drain adapter to send evlog wide events to Sentry Logs and open Sentry issues for server errors.

1. Identify which framework I'm using and follow its evlog integration pattern
2. Install evlog if not already installed
3. Import createSentryDrain from 'evlog/sentry'
4. Wire createSentryDrain({ issues: true }) into my framework's drain configuration. If the app already captures errors with @sentry/node or another Sentry SDK, use createSentryDrain() without issues, so each error is not reported twice
5. Set SENTRY_DSN environment variable
6. Test by triggering a request and checking Sentry > Explore > Logs, then trigger a server error and check Sentry > Issues

Adapter docs: [https://www.evlog.dev/integrate/adapters/cloud/sentry](https://www.evlog.dev/integrate/adapters/cloud/sentry)
Framework setup: [https://www.evlog.dev/integrate/frameworks/overview](https://www.evlog.dev/integrate/frameworks/overview)

</prompt>

## Installation

The Sentry adapter comes bundled with evlog:

```typescript [src/index.ts]
import { createSentryDrain } from 'evlog/sentry'
```

## Quick Start

### 1. Get your Sentry DSN

1. Create a [Sentry account](https://sentry.io)
2. Create a new project (Node.js or JavaScript)
3. Find your DSN in **Settings > Projects > Your Project > Client Keys (DSN)**

### 2. Set environment variables

```bash [.env]
SENTRY_DSN=https://your-public-key@o0.ingest.sentry.io/your-project-id
```

### 3. Wire the drain to your framework

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createSentryDrain } from 'evlog/sentry'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createSentryDrain } from 'evlog/sentry'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createSentryDrain({ issues: true }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createSentryDrain } from 'evlog/sentry'

export const { handle, handleError } = createEvlogHooks({ drain: createSentryDrain({ issues: true }) })
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createSentryDrain } from 'evlog/sentry'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: createSentryDrain({ issues: true }) }),
]
```

```typescript [Hono]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [Express]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [Fastify]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

await app.register(evlog, { drain: createSentryDrain({ issues: true }) })
```

```typescript [Elysia]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [NestJS]src/app.module.ts
import { createSentryDrain } from 'evlog/sentry'

EvlogModule.forRoot({ drain: createSentryDrain({ issues: true }) })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createSentryDrain } from 'evlog/sentry'

const handler = withEvlog(new RPCHandler(router), { drain: createSentryDrain({ issues: true }) })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createSentryDrain } from 'evlog/sentry'

export default withEvlog(handler, { drain: createSentryDrain({ issues: true }) })
```

```typescript [Standalone]
import { createSentryDrain } from 'evlog/sentry'

initLogger({ drain: createSentryDrain({ issues: true }) })
```

</framework-tabs>

Your wide events now appear in **Explore > Logs**, and every `error` or `fatal` event without a 4xx status opens an issue in **Issues**. If the app already captures errors with `@sentry/node`, drop `issues: true`: see [Issues and Alerts](#issues-and-alerts).

## Configuration

The adapter reads configuration from multiple sources (highest priority first):

1. **Overrides** passed to `createSentryDrain()`
2. **Runtime config** at `runtimeConfig.sentry` (Nuxt/Nitro only)
3. **Environment variables** (`SENTRY_*`)

### Environment Variables

<table>
<thead>
  <tr>
    <th>
      Variable
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        SENTRY_DSN
      </code>
    </td>
    
    <td>
      Sentry DSN (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SENTRY_ENVIRONMENT
      </code>
    </td>
    
    <td>
      Environment name override
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SENTRY_RELEASE
      </code>
    </td>
    
    <td>
      Release version override
    </td>
  </tr>
</tbody>
</table>

### Runtime Config (Nuxt only)

Configure via `nuxt.config.ts` for type-safe configuration:

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sentry: {
      dsn: '', // Set via SENTRY_DSN
      environment: 'production',
      release: '1.0.0',
    },
  },
})
```

### Override Options

Pass options directly to override any configuration:

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createSentryDrain } from 'evlog/sentry'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createSentryDrain } from 'evlog/sentry'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createSentryDrain } from 'evlog/sentry'

export const { handle, handleError } = createEvlogHooks({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createSentryDrain } from 'evlog/sentry'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createSentryDrain({
        dsn: 'https://key@o0.ingest.sentry.io/123',
        tags: { team: 'backend' },
        timeout: 10000,
      }),
    }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createSentryDrain } from 'evlog/sentry'

await app.register(evlog, {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createSentryDrain } from 'evlog/sentry'

EvlogModule.forRoot({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createSentryDrain } from 'evlog/sentry'

const handler = withEvlog(new RPCHandler(router), {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createSentryDrain } from 'evlog/sentry'

export default withEvlog(handler, {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createSentryDrain } from 'evlog/sentry'

initLogger({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

</framework-tabs>

### Full Configuration Reference

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        dsn
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Sentry DSN (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Event environment
    </td>
    
    <td>
      Environment name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        release
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Event version
    </td>
    
    <td>
      Release version
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tags
      </code>
    </td>
    
    <td>
      <code>
        Record<string, string>
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Additional attributes to attach
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5000
      </code>
    </td>
    
    <td>
      Request timeout in milliseconds
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        issues
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Also send <code>
        error
      </code>
      
       and <code>
        fatal
      </code>
      
       events as Sentry error events
    </td>
  </tr>
</tbody>
</table>

## Log Transformation

evlog wide events are converted to Sentry Logs using `toSentryLog()`:

- **Level mapping**: evlog levels map directly (`debug`, `info`, `warn`, `error`)
- **Severity numbers**: Follow the OpenTelemetry spec (`debug=5`, `info=9`, `warn=13`, `error=17`)
- **Body**: Derived from the event's `message`, `action`, or `path` fields (first available)
- **Attributes**: All wide event fields are sent as typed attributes (string, integer, double, boolean). Complex objects are serialized to JSON strings.
- **Sentry attributes**: `sentry.environment` and `sentry.release` are set automatically
- **Trace ID**: Uses `event.traceId` if available, otherwise generates a random one

## Issues and Alerts

Logs alone do not open Sentry issues. `issues: true` also sends every `error` and `fatal` wide event as a Sentry error event, so it groups into an issue and can trigger issue alerts. It is the recommended setup and the one the Quick Start uses: Sentry is built around issues, and without them an evlog error is only a log line nobody gets alerted on. The option defaults to `false` so that upgrading never starts opening issues on its own:

```typescript [server/plugins/evlog-drain.ts]
createSentryDrain({ issues: true })
```

Events with a 4xx status are not sent as issues. A thrown 404, 401 or validation 400 still marks the wide event as `error` and still reaches Sentry as a log, but it is expected traffic, so it opens no issue. The status is read from `error.status`, then `error.statusCode`, then the event's `status`.

Browser errors sent through the [client transport](https://www.evlog.dev/use-cases/client-logging) (`source: 'client'`) open issues too, and anyone can post to that endpoint, so keep Sentry's spike protection or a project rate limit on.

The error event carries the wide event's trace context: `contexts.trace.trace_id` is the event's `traceId`, the same value as the `trace_id` of its log, so the issue opens on that log. When the `traceId` came from an incoming `traceparent`, the same id links the issue to the other services in the trace. An event with no `traceId` gets one generated inside the Sentry drain, shared by its log and its issue only: other drains in the pipeline do not see it.

The exception type and value come from `error.name` and `error.message`, and `error.stack` becomes the issue's stack frames, which Sentry groups on. An error without a stack is grouped by its type and transaction, so a message carrying an id (`User 8231 not found`) stays one issue. The transaction is `METHOD /path` or `action`, `service` plus your `tags` become issue tags, and `userId` or `user.id` sets the issue's user. Other fields stay on the log.

The logs are sent first, in envelopes of at most 100, and a failed log envelope fails the batch. Issues follow one request at a time. The first failed issue is reported and stops the rest of that batch, so a batch retry never resends logs that already went through when the batch holds 100 logs or fewer. Above 100 logs, a retry can resend the envelopes that were already accepted.

<callout color="warning" icon="i-lucide-triangle-alert">

If the app also runs `@sentry/node` (or another Sentry SDK) with error capture on, leave `issues` off: both would report the same failure, and every issue would open twice.

</callout>

## Querying Logs in Sentry

evlog sends wide events as structured logs. In the Sentry dashboard:

- **Explore > Logs**: View all evlog wide events with full attribute search
- **Filter by attributes**: `service:my-app`, `level:error`, or any wide event field
- **Trace correlation**: Logs are linked to traces via `trace_id` for cross-referencing

<callout color="info" icon="i-lucide-info">

Sentry Structured Logs support high-cardinality attributes, making them a great fit for evlog's wide events. Every field in your wide event becomes a searchable attribute in Sentry.

</callout>

## Troubleshooting

### Missing DSN error

```text [Console]
[evlog/sentry] Missing DSN. Set SENTRY_DSN env var or pass to createSentryDrain()
```

Make sure your environment variable is set and the server was restarted after adding it.

### Invalid DSN

If the DSN is malformed (missing public key or project ID), the adapter will throw an error. Verify your DSN format:

```text [Sentry DSN format]
https://<public-key>@<host>/<project-id>
```

### 401 Unauthorized

Your DSN may be revoked or invalid. Generate a new DSN in **Settings > Projects > Client Keys (DSN)**.

## Direct API Usage

For advanced use cases, you can use the lower-level functions:

```typescript [server/utils/sentry.ts]
import { sendToSentry, sendBatchToSentry } from 'evlog/sentry'

// Send a single event as a Sentry log
await sendToSentry(event, {
  dsn: process.env.SENTRY_DSN!,
})

// Send multiple events in one request
await sendBatchToSentry(events, {
  dsn: process.env.SENTRY_DSN!,
})
```

## Next Steps

- [Axiom Adapter](https://www.evlog.dev/integrate/adapters/cloud/axiom) - Send logs to Axiom for querying and dashboards
- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp) - Send logs via OpenTelemetry Protocol
- [PostHog Adapter](https://www.evlog.dev/integrate/adapters/cloud/posthog) - Send logs to PostHog
- [Custom Adapters](https://www.evlog.dev/extend/custom-drains) - Build your own adapter

---

- [Sentry Dashboard](https://sentry.io)
- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp)


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
