---
title: "Astro"
description: "Wide events and structured errors in Astro server middleware."
canonical_url: "https://www.evlog.dev/integrate/frameworks/astro"
---
# Astro

> Wide events and structured errors in Astro server middleware.

Astro doesn't have a dedicated evlog integration. Instead, use the core `evlog` package with Astro's middleware to create request-scoped loggers manually.

<prompt :actions="["copy","cursor","claude"]" description="Set up evlog in my Astro app" icon="i-simple-icons-astro">

Set up evlog in my Astro app.

- Install evlog: pnpm add evlog
- Import initLogger and createRequestLogger from 'evlog'
- Create evlog.config.ts with export default defineEvlog({ service: 'my-astro-app' })
- Call initLogger(toLoggerConfig(config)) in Astro middleware
- Create a request logger with createRequestLogger({ method, path }) per request
- Use log.set() in API routes and middleware to accumulate context
- Call log.emit() before returning the response (no auto-emit lifecycle)

Docs: [https://www.evlog.dev/integrate/frameworks/astro](https://www.evlog.dev/integrate/frameworks/astro)
Adapters: [https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

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

This is a guide-level integration. It uses the generic `createRequestLogger` API rather than a framework-specific module.

</callout>

<callout color="warning" icon="i-lucide-cloud">

On **Cloudflare Workers** (including Astro with `@astrojs/cloudflare`), set `waitUntil` on `createRequestLogger` to your `ExecutionContext#waitUntil` (properly bound), or use [`defineWorkerFetch`](https://www.evlog.dev/integrate/frameworks/cloudflare-workers) / [`createWorkersLogger`](https://www.evlog.dev/integrate/frameworks/cloudflare-workers) with `{ executionCtx }` on a **Worker fetch entry**. Otherwise async drains may never finish after the response is returned.

For Astro **middleware** (not the raw Worker handler), there is no `defineWorkerFetch`; you still pass `waitUntil` from the adapter-exposed context.

The exact way to read `ctx` from Astro middleware depends on your adapter version, so check the [Cloudflare adapter docs](https://docs.astro.build/en/guides/integrations-guide/cloudflare/).

</callout>

## Quick Start

### 1. Install

<code-group>

```bash [pnpm]
pnpm add evlog
```

```bash [bun]
bun add evlog
```

```bash [yarn]
yarn add evlog
```

```bash [npm]
npm install evlog
```

</code-group>

### 2. Create evlog.config.ts

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'

export default defineEvlog({
  service: 'my-astro-app',
})
```

Every option lives in this file: sampling, drain, `enrich`, route filters. `npx evlog init` writes it, and the wiring below, for you. See [Configuration](https://www.evlog.dev/reference/configuration).

### 3. Create a middleware

```typescript [src/middleware.ts]
import { defineMiddleware } from 'astro:middleware'
import { initLogger, createRequestLogger, toLoggerConfig } from 'evlog'
import config from '../evlog.config'

initLogger(toLoggerConfig(config))

export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
  const url = new URL(request.url)

  const log = createRequestLogger({
    method: request.method,
    path: url.pathname,
  })

  locals.log = log

  try {
    const response = await next()
    log.emit()
    return response
  } catch (error) {
    log.error(error instanceof Error ? error : new Error(String(error)))
    log.emit()
    throw error
  }
})
```

### 4. Type your locals

```typescript [src/env.d.ts]
/// <reference types="astro/client" />

import type { RequestLogger } from 'evlog'

declare namespace App {
  interface Locals {
    log: RequestLogger
  }
}
```

## Wide Events

Access the logger from `Astro.locals` in your pages and API routes:

```typescript [src/pages/api/users/[id].ts]
import type { APIRoute } from 'astro'

export const GET: APIRoute = async ({ params, locals }) => {
  locals.log.set({ user: { id: params.id } })

  const user = await db.findUser(params.id)
  locals.log.set({ user: { name: user.name, plan: user.plan } })

  return new Response(JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' },
  })
}
```

```bash [Terminal output]
14:58:15 INFO [my-astro-app] GET /api/users/usr_123
  ├─ user: id=usr_123 name=Alice plan=pro
  └─ requestId: 4a8ff3a8-...
```

## Error Handling

Use `createError` for structured errors:

```typescript [src/pages/api/checkout.ts]
import type { APIRoute } from 'astro'
import { createError, parseError } from 'evlog'

export const POST: APIRoute = async ({ request, locals }) => {
  const body = await request.json()
  locals.log.set({ cart: { items: body.items } })

  if (!body.paymentMethod) {
    const error = createError({
      status: 400,
      message: 'Missing payment method',
      why: 'No payment method was provided',
      fix: 'Include a paymentMethod field in the request body',
    })
    locals.log.error(error)
    const parsed = parseError(error)
    return new Response(JSON.stringify(parsed), { status: parsed.status })
  }

  return new Response(JSON.stringify({ success: true }))
}
```

## Configuration

Every option goes in `evlog.config.ts`. See the [Configuration reference](https://www.evlog.dev/reference/configuration) for the full list. This guide builds the request logger with `createRequestLogger`, not an evlog middleware, so the file's `enrich`, `keep`, `include`, `exclude` and `routes` do not apply.

## Drain

Set the drain in the file; `toLoggerConfig(config)` passes it to `initLogger`:

```typescript [evlog.config.ts]
import { defineEvlog, type DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 50, intervalMs: 5000 },
})

export default defineEvlog({
  service: 'my-astro-app',
  drain: pipeline(createAxiomDrain()),
})
```

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

See the [Adapters](https://www.evlog.dev/integrate/adapters/overview) docs for all available drain adapters.

</callout>

## Next Steps

- [Wide Events](https://www.evlog.dev/learn/wide-events): Design comprehensive events with context layering
- [Adapters](https://www.evlog.dev/integrate/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more
- [Sampling](https://www.evlog.dev/learn/sampling): Control log volume with head and tail sampling
- [Structured Errors](https://www.evlog.dev/learn/structured-errors): Throw errors with `why`, `fix`, and `link` fields


## Sitemap

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