Getting Started
Quick Start
Get up and running with evlog in minutes.
This guide covers the core APIs you'll use most often with evlog.
In Nuxt, all evlog functions (
useLogger, log, createError, parseError) are auto-imported. No import statements needed.useLogger (Server-Side)
Use useLogger(event) in any Nuxt/Nitro API route to get a request-scoped logger:
export default defineEventHandler(async (event) => {
// Get the request-scoped logger (auto-imported in Nuxt)
const log = useLogger(event)
// Accumulate context throughout the request
log.set({ user: { id: 1, plan: 'pro' } })
log.set({ cart: { items: 3, total: 9999 } })
// Process checkout...
const order = await processCheckout()
log.set({ orderId: order.id })
// Logger auto-emits when request ends - nothing else to do!
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
The logger automatically emits when the request ends. No manual
emit() call needed.When to use useLogger vs log
Use useLogger(event) | Use log |
|---|---|
| API routes, middleware, server plugins | One-off events outside request context |
| When you need to accumulate context | Quick debugging messages |
| For wide events (one log per request) | Client-side logging |
createError (Structured Errors)
Use createError() to throw errors with actionable context:
// server/api/checkout.post.ts
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:
composables/useCheckout.ts
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)
// Direct access to all fields
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 (Simple Logging)
For quick one-off logs anywhere in your code:
// server/utils/auth.ts
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
Prefer wide events (
useLogger) over simple logs when possible. Use log for truly one-off events that don't belong to a request.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 }
}
In pretty mode (development), client logs appear with colored tags in the browser console:
[my-app] info { action: 'checkout', status: 'success' }
Client-side
log is designed for debugging and development. For production analytics, use dedicated services like Plausible, PostHog, or Mixpanel.Wide Event Fields
Every wide event should include context from different layers:
// server/api/checkout.post.ts
const log = useLogger(event)
// Request context (often auto-populated)
log.set({ method: 'POST', path: '/api/checkout' })
// User context
log.set({ userId: 1, subscription: 'pro' })
// Business context
log.set({ cart: { items: 3, total: 9999 }, coupon: 'SAVE10' })
// Outcome
log.set({ status: 200, duration: 234 })
{
"level": "info",
"method": "POST",
"path": "/api/checkout",
"userId": 1,
"subscription": "pro",
"cart": { "items": 3, "total": 9999 },
"coupon": "SAVE10",
"status": 200,
"duration": 234
}
Next Steps
- Wide Events - Learn how to design effective wide events
- Structured Errors - Master error handling with evlog
- Best Practices - Security guidelines and production tips