Client Logging
Server logs tell you what happened on the backend. Client logs complete the picture: user interactions, page views, frontend errors, and performance signals that never reach the server unless you capture them.
Quick Start
evlog provides a client-side logging API that works in any browser environment:
import { initLog, log } from 'evlog/client'
export default defineNuxtPlugin(() => {
initLog({ service: 'web' })
log.info({ action: 'app_init', path: window.location.pathname })
})
'use client'
import { useEffect } from 'react'
import { initLog, log } from 'evlog/client'
export function LogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
initLog({ service: 'web' })
log.info({ action: 'app_init', path: window.location.pathname })
}, [])
return <>{children}</>
}
import { initLog, log } from 'evlog/client'
initLog({ service: 'web' })
log.info({ action: 'app_init', path: window.location.pathname })
The log object works anywhere in your client code: components, composables, event handlers.
Two Call Signatures
The log API accepts two forms depending on the context.
Object Form (structured context)
Pass an object to capture structured data, just like server-side log.set():
log.info({ action: 'page_view', path: '/products', referrer: document.referrer })
[web] info { action: 'page_view', path: '/products', referrer: 'https://google.com' }
Tag + Message Form (quick logs)
Pass a tag and a message for quick, readable logs:
log.info('auth', 'User logged in')
[auth] User logged in
Available Levels
Both forms support four levels: log.info(), log.warn(), log.error(), and log.debug().
Identity Context
Track which user generated a log with setIdentity():
import { setIdentity, clearIdentity, log } from 'evlog/client'
// After login
setIdentity({ userId: 'usr_123', plan: 'pro' })
log.info({ action: 'dashboard_view' })
// → { userId: 'usr_123', plan: 'pro', action: 'dashboard_view', ... }
// After logout
clearIdentity()
Identity fields are automatically merged into every log event until cleared. This lets you correlate browser events to specific users in your observability tools.
Configuration
initLog() accepts the following options:
| Option | Default | Description |
|---|---|---|
enabled | true | Enable or disable all client logging |
console | true | Output logs to the browser console |
pretty | true | Use colored, formatted console output |
service | 'client' | Service name included in every log event |
transport | - | Send logs to a server endpoint (see below) |
initLog({
service: 'web',
transport: {
enabled: true,
endpoint: '/api/_evlog/ingest', // default endpoint
},
})
enabled, console, and pretty all default to true. You only need to set them if you want to change the defaults.Sending Logs to the Server
By default, client logs only appear in the browser console. To persist them, you have two options:
Built-in Transport
The simplest approach is to enable the built-in transport in initLog(). Each log is sent individually via fetch with keepalive: true. Good for low-volume apps.
import { initLog } from 'evlog/client'
export default defineNuxtPlugin(() => {
initLog({
service: 'web',
transport: {
enabled: true,
endpoint: '/api/_evlog/ingest',
},
})
})
import { initLog } from 'evlog/client'
initLog({
service: 'web',
transport: {
enabled: true,
endpoint: '/api/_evlog/ingest',
},
})
Browser Drain Pipeline
For higher volume or when you need batching, retries, and page-exit flushing, use the browser drain. This works with any frontend and has no framework dependency.
import { initLogger, log } from 'evlog'
import { createBrowserLogDrain } from 'evlog/browser'
export default defineNuxtPlugin(() => {
const drain = createBrowserLogDrain({
drain: { endpoint: '/api/_evlog/ingest' },
pipeline: {
batch: { size: 25, intervalMs: 2000 },
retry: { maxAttempts: 2 },
},
})
initLogger({ drain })
log.info({ action: 'app_init' })
})
import { initLogger, log } from 'evlog'
import { createBrowserLogDrain } from 'evlog/browser'
const drain = createBrowserLogDrain({
drain: { endpoint: 'https://logs.example.com/v1/ingest' },
pipeline: {
batch: { size: 25, intervalMs: 2000 },
retry: { maxAttempts: 2 },
},
})
initLogger({ drain })
log.info({ action: 'app_init' })
The browser drain automatically:
- Batches events by size and time interval
- Retries failed sends with exponential backoff
- Flushes buffered events via
sendBeaconwhen the page becomes hidden (tab switch, navigation, close)
Next Steps
- Browser Drain - Batching, retry, and sendBeacon fallback
- Pipeline - Advanced pipeline configuration
- Structured Errors - Surface client errors with actionable context
Structured Errors
Create errors that explain why they occurred and how to fix them. Add actionable context with why, fix, and link fields for humans and AI agents.
AI SDK Integration
Capture token usage, tool calls, model info, and streaming metrics from the Vercel AI SDK into wide events. Wrap your model and get full AI observability.