Getting Started

Quick Start

Get up and running with evlog in minutes. Learn useLogger, createError, parseError, and the log API for wide events and structured errors.

This guide covers the core APIs you'll use most often with evlog.

In Nuxt, evlog auto-imports all functions (useLogger, log, createError, parseError). 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

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:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],

  evlog: {
    env: {
      service: 'default-service', // Fallback service name
    },
    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:

Output
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:

server/api/legacy/process.post.ts
export default defineEventHandler((event) => {
  // Explicitly set service name for this handler
  const log = useLogger(event, 'legacy-service')

  log.set({ action: 'process_legacy_request' })

  return { success: true }
})
Priority order: Explicit useLogger parameter > Route configuration > env.service > Auto-detected from environment

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