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 }
})
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 pluginsOne-off events outside request context
When you need to accumulate contextQuick 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',
})

Error Fields

FieldRequiredDescription
messageYesWhat happened (user-facing)
statusNoHTTP status code (default: 500)
whyNoTechnical reason (for debugging)
fixNoActionable solution
linkNoDocumentation URL for more info
causeNoOriginal 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')
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>

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

Next Steps

Copyright © 2026