Core Concepts
Wide Events
Learn how to design effective wide events that capture everything you need in a single log.
Wide events are the core concept behind evlog. Instead of scattering logs throughout your codebase, you accumulate context and emit a single, comprehensive log event.
Why Wide Events?
Traditional logging creates noise:
server/api/checkout.post.ts
// Traditional approach - 6 separate log lines
logger.info('Request started')
logger.info('User authenticated', { userId: user.id })
logger.info('Fetching cart', { cartId: cart.id })
logger.info('Processing payment')
logger.info('Payment successful')
logger.info('Request completed', { duration: 234 })
This approach has problems:
- Scattered context: Information is spread across multiple log lines
- Hard to correlate: Matching logs to requests requires request IDs everywhere
- Noise: 10+ log lines per request makes finding issues harder
- Incomplete: Some logs might be missing if errors occur
Wide events solve this:
// server/api/checkout.post.ts
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
log.set({ cart: { id: 42, items: 3, total: 9999 } })
log.set({ payment: { method: 'card', status: 'success' } })
// One log, all context - emitted automatically
[INFO] POST /api/checkout (234ms)
user: { id: 1, plan: 'pro' }
cart: { id: 42, items: 3, total: 9999 }
payment: { method: 'card', status: 'success' }
status: 200
Anatomy of a Wide Event
A well-designed wide event contains context from multiple layers:
Request Context
Basic information about the request itself:
server/api/checkout.post.ts
log.set({
method: 'POST',
path: '/api/checkout',
requestId: 'abc-123-def',
traceId: 'trace-xyz-789',
})
In Nuxt/Nitro, most request context is auto-populated by evlog.
User Context
Who is making the request:
server/api/checkout.post.ts
log.set({
userId: user.id,
email: user.email,
subscription: user.plan,
accountAge: daysSince(user.createdAt),
})
Business Context
Domain-specific data relevant to the operation:
server/api/checkout.post.ts
log.set({
cart: {
id: cart.id,
items: cart.items.length,
total: cart.total,
currency: 'USD',
},
shipping: {
method: 'express',
country: address.country,
},
coupon: appliedCoupon?.code,
})
Outcome
The result of the operation:
log.set({
status: 200,
duration: Date.now() - startTime,
success: true,
})
log.set({
status: 500,
error: {
message: err.message,
code: err.code,
type: err.constructor.name,
},
})
Best Practices
Use Meaningful Keys
// Avoid generic keys
log.set({ data: { id: 123 } })
// Use specific, descriptive keys
log.set({ order: { id: 123, status: 'pending' } })
Group Related Data
// Flat structure is hard to read
log.set({
userId: 1,
userEmail: 'a@b.com',
cartId: 2,
cartTotal: 100,
})
// Grouped structure is clearer
log.set({
user: { id: 1, email: 'a@b.com' },
cart: { id: 2, total: 100 },
})
Add Context Incrementally
Call log.set() as you gather information:
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const user = await getUser(event)
log.set({ user: { id: user.id, plan: user.plan } })
const cart = await getCart(user.id)
log.set({ cart: { items: cart.items.length, total: cart.total } })
const payment = await processPayment(cart)
log.set({ payment: { method: payment.method, status: payment.status } })
return { success: true }
})
[INFO] POST /api/checkout (456ms)
user: { id: 1, plan: 'pro' }
cart: { items: 3, total: 9999 }
payment: { method: 'card', status: 'success' }
status: 200
Handle Errors Gracefully
When errors occur, the wide event still emits with error context:
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
const log = useLogger(event)
try {
const result = await processPayment(cart)
return result
} catch (err) {
log.set({
error: {
message: err.message,
code: err.code,
type: err.constructor.name,
},
})
throw err
}
})
[ERROR] POST /api/checkout (123ms)
user: { id: 1, plan: 'pro' }
cart: { items: 3, total: 9999 }
error: {
message: 'Card declined',
code: 'CARD_DECLINED',
type: 'PaymentError'
}
status: 500
Output Formats
evlog automatically switches between formats based on environment:
[INFO] POST /api/checkout (234ms)
user: { id: 1, plan: 'pro' }
cart: { items: 3, total: 9999 }
payment: { method: 'card', status: 'success' }
{
"level": "info",
"method": "POST",
"path": "/api/checkout",
"duration": 234,
"user": { "id": 1, "plan": "pro" },
"cart": { "items": 3, "total": 9999 },
"payment": { "method": "card", "status": "success" }
}
Next Steps
- Structured Errors - Learn how to create errors with actionable context