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

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,
})

Best Practices

Use Meaningful Keys

// Avoid generic keys
log.set({ data: { id: 123 } })

// Use specific, descriptive keys
log.set({ order: { id: 123, status: 'pending' } })
// 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 }
})

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
  }
})

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' }

Next Steps