Quick Start
This guide covers the core APIs you'll use most often with evlog.
useLogger, log, createError, parseError). No import statements needed.log (Simple Logging)
The simplest way to use evlog. Fire-and-forget structured logs, anywhere in your code:
import { log } from 'evlog'
log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined' })
log.warn('cache', 'Cache miss')
10:23:45.612 [auth] User logged in
10:23:45.613 ERROR [my-app] action=payment error=card_declined
10:23:45.614 [cache] Cache miss
Two call styles:
- Tagged —
log.info('tag', 'message')for quick, readable console output - Structured —
log.info({ key: value })for rich events that flow through the drain pipeline
createLogger (Wide Events)
When you need to accumulate context across multiple steps of an operation — a script, background job, queue worker, or workflow — use createLogger:
import { initLogger, createLogger } from 'evlog'
initLogger({ env: { service: 'sync-worker' } })
const log = createLogger({ jobId: job.id, queue: 'emails' })
log.set({ batch: { size: 50 } })
log.set({ batch: { processed: 50 } })
log.emit()
10:23:45.612 INFO [sync-worker] in 1204ms
├─ jobId: job_abc123
├─ queue: emails
└─ batch: size=50 processed=50
createLogger() accepts any initial context as a plain object. It returns a logger with set, error, info, warn, emit, and getContext.
For HTTP request contexts specifically, use createRequestLogger() which pre-populates method, path, and requestId:
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
createLogger and createRequestLogger, you must call log.emit() manually. In framework integrations, this happens automatically.useLogger (Retrieve the Request Logger)
When using a framework integration (Nuxt, Hono, Express, etc.), the middleware automatically creates a wide event logger on request start and emits it on response end. useLogger(event) retrieves that logger from the request context:
import { useLogger } from 'evlog'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
log.set({ cart: { items: 3, total: 9999 } })
const order = await processCheckout()
log.set({ orderId: order.id })
return { success: true, orderId: order.id }
})
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 234ms
├─ user: id=1 plan=pro
├─ cart: items=3 total=9999
└─ orderId: ord_abc123
useLogger doesn't create a logger — the framework middleware already did that. It just retrieves it from the event context so you can add data with set().When to use what
Use log | Use createLogger() / createRequestLogger() | Use useLogger(event) |
|---|---|---|
| Quick one-off events | Scripts, jobs, workers, queues, HTTP without a framework | API routes with a framework integration |
| No context accumulation needed | Accumulate context over an operation | Retrieve the request-scoped logger |
| Client-side logging | Wide events (one log per operation) | Access the auto-managed wide event |
Service Identification
In multi-service architectures, differentiate which service a log belongs to using either route-based configuration or explicit service names.
Route-Based Configuration
Configure service names per route pattern in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: {
service: 'default-service',
},
routes: {
'/api/auth/**': { service: 'auth-service' },
'/api/payment/**': { service: 'payment-service' },
'/api/booking/**': { service: 'booking-service' },
},
},
})
Logs from routes matching these patterns will automatically include the configured service name:
21:57:10.442 INFO [auth-service] POST /api/auth/login 200 in 1ms
├─ requestId: 88ced16a-bef2-4483-86cb-2b4fb677ea52
├─ user: id=user_123 email=demo@example.com
└─ action: login
Explicit Service Parameter
Override the service name for specific routes using the second parameter of useLogger:
import { useLogger } from 'evlog'
export default defineEventHandler((event) => {
const log = useLogger(event, 'legacy-service')
log.set({ action: 'process_legacy_request' })
return { success: true }
})
useLogger parameter > Route configuration > env.service > Auto-detected from environmentcreateError (Structured Errors)
Use createError() to throw errors with actionable context:
import { createError } from 'evlog'
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different payment method',
link: 'https://docs.example.com/payments/declined',
})
{
"statusCode": 402,
"message": "Payment failed",
"data": {
"why": "Card declined by issuer",
"fix": "Try a different payment method",
"link": "https://docs.example.com/payments/declined"
}
}
Error Fields
| Field | Required | Description |
|---|---|---|
message | Yes | What happened (user-facing) |
status | No | HTTP status code (default: 500) |
why | No | Technical reason (for debugging) |
fix | No | Actionable solution |
link | No | Documentation URL for more info |
cause | No | Original error (if wrapping) |
Frontend Integration
Use parseError() to extract all error fields on the client:
import { parseError } from 'evlog'
export async function checkout(cart: Cart) {
try {
await $fetch('/api/checkout', { method: 'POST', body: cart })
} catch (err) {
const error = parseError(err)
toast.add({
title: error.message,
description: error.why,
color: 'error',
actions: error.link
? [{ label: 'Learn more', onClick: () => window.open(error.link) }]
: undefined,
})
if (error.fix) {
console.info(`Fix: ${error.fix}`)
}
}
}
log (Client-Side)
The same log API works on the client side, outputting to the browser console:
<script setup lang="ts">
async function handleCheckout() {
log.info('checkout', 'User initiated checkout')
try {
await $fetch('/api/checkout', { method: 'POST' })
log.info({ action: 'checkout', status: 'success' })
} catch (err) {
log.error({ action: 'checkout', error: 'failed' })
}
}
</script>
export function useAnalytics() {
function trackEvent(event: string, data?: Record<string, unknown>) {
log.info('analytics', `Event: ${event}`)
if (data) {
log.debug({ event, ...data })
}
}
return { trackEvent }
}
Next Steps
- Logging Overview — Understand all three logging modes
- Wide Events — Learn how to design effective wide events
- Typed Fields — Add compile-time type safety to your wide events
- Structured Errors — Master error handling with evlog
- Best Practices — Security guidelines and production tips