Getting Started
Get up and running with evlog in minutes. Learn the log API, createLogger for wide events, useLogger for requests, 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.

log (Simple Logging)

The simplest way to use evlog. Fire-and-forget structured logs, anywhere in your code:

import { log } from 'evlog'

log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined' })
log.warn('cache', 'Cache miss')

Two call styles:

  • Taggedlog.info('tag', 'message') for quick, readable console output
  • Structuredlog.info({ key: value }) for rich events that flow through the drain pipeline
See the full Simple Logging guide for all patterns and drain integration.

createLogger (Wide Events)

When you need to accumulate context across multiple steps of an operation — a script, background job, queue worker, or workflow — use createLogger:

import { initLogger, createLogger } from 'evlog'

initLogger({ env: { service: 'sync-worker' } })

const log = createLogger({ jobId: job.id, queue: 'emails' })

log.set({ batch: { size: 50 } })
log.set({ batch: { processed: 50 } })
log.emit()

createLogger() accepts any initial context as a plain object. It returns a logger with set, error, info, warn, emit, and getContext.

For HTTP request contexts specifically, use createRequestLogger() which pre-populates method, path, and requestId:

src/worker.ts
import { createRequestLogger } from 'evlog'

const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
With createLogger and createRequestLogger, you must call log.emit() manually. In framework integrations, this happens automatically.

useLogger (Retrieve the Request Logger)

When using a framework integration (Nuxt, Hono, Express, etc.), the middleware automatically creates a wide event logger on request start and emits it on response end. useLogger(event) retrieves that logger from the request context:

import { useLogger } from 'evlog'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return { success: true, orderId: order.id }
})
useLogger doesn't create a logger — the framework middleware already did that. It just retrieves it from the event context so you can add data with set().

When to use what

Use logUse createLogger() / createRequestLogger()Use useLogger(event)
Quick one-off eventsScripts, jobs, workers, queues, HTTP without a frameworkAPI routes with a framework integration
No context accumulation neededAccumulate context over an operationRetrieve the request-scoped logger
Client-side loggingWide events (one log per operation)Access the auto-managed wide event

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',
    },
    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
import { useLogger } from 'evlog'

export default defineEventHandler((event) => {
  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:

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)

    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 (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>
See Client Logging for transport configuration, identity context, and browser drain setup.

Next Steps