Frameworks
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.
Set up evlog in my Astro app
This is a guide-level integration. It uses the generic
createRequestLogger API rather than a framework-specific module.On Cloudflare Workers (including Astro with
@astrojs/cloudflare), set waitUntil on createRequestLogger to your ExecutionContext#waitUntil (properly bound), or use defineWorkerFetch / createWorkersLogger 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 — check the Cloudflare adapter docs.Quick Start
1. Install
pnpm add evlog
bun add evlog
yarn add evlog
npm install evlog
2. Create a middleware
src/middleware.ts
import { defineMiddleware } from 'astro:middleware'
import { initLogger, createRequestLogger } from 'evlog'
initLogger({
env: { service: 'my-astro-app' },
})
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
}
})
3. Type your locals
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:
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' },
})
}
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:
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
See the Configuration reference for all available options (initLogger, middleware options, sampling, silent mode, etc.).
Drain
Configure drain in initLogger inside your middleware:
src/middleware.ts
import { initLogger, createRequestLogger } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'
import type { DrainContext } from 'evlog'
const pipeline = createDrainPipeline<DrainContext>({
batch: { size: 50, intervalMs: 5000 },
})
const drain = pipeline(createAxiomDrain())
initLogger({
env: { service: 'my-astro-app' },
drain,
})
See the Adapters docs for all available drain adapters.
Next Steps
- Wide Events: Design comprehensive events with context layering
- Adapters: Send logs to Axiom, Sentry, PostHog, and more
- Sampling: Control log volume with head and tail sampling
- Structured Errors: Throw errors with
why,fix, andlinkfields