From d86b83176e4533cd793bcfd3d7e9b181193079a1 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Fri, 3 Apr 2026 19:43:32 +0100 Subject: [PATCH 1/4] docs: improve documentation --- apps/docs/app/app.config.ts | 3 + apps/docs/app/components/app/AppHeader.vue | 11 + .../app/components/app/AppHeaderCenter.vue | 58 ++--- .../features/FeatureStructuredErrors.vue | 2 +- apps/docs/content/0.landing.md | 30 +-- .../1.getting-started/1.introduction.md | 97 ++++---- .../1.getting-started/2.installation.md | 2 +- .../1.getting-started/3.quick-start.md | 223 ++++++++---------- .../1.getting-started/4.agent-skills.md | 4 +- apps/docs/content/2.logging/.navigation.yml | 1 + apps/docs/content/2.logging/0.overview.md | 140 +++++++++++ .../content/2.logging/1.simple-logging.md | 176 ++++++++++++++ .../2.wide-events.md} | 155 +++++++++--- .../3.structured-errors.md} | 23 +- .../4.client-logging.md} | 54 ++--- .../11.ai-sdk.md => 2.logging/5.ai-sdk.md} | 25 +- .../content/3.core-concepts/0.lifecycle.md | 78 +++++- ...{7.configuration.md => 1.configuration.md} | 10 +- .../{5.sampling.md => 2.sampling.md} | 24 +- .../{4.typed-fields.md => 3.typed-fields.md} | 14 +- ....best-practices.md => 4.best-practices.md} | 8 +- .../{8.performance.md => 5.performance.md} | 0 .../{10.vite-plugin.md => 6.vite-plugin.md} | 10 +- .../.navigation.yml | 0 .../00.overview.md | 2 +- .../{2.frameworks => 4.frameworks}/01.nuxt.md | 9 + .../02.nextjs.md | 21 +- .../03.sveltekit.md | 19 +- .../04.nitro.md | 9 + .../05.tanstack-start.md | 23 +- .../06.nestjs.md | 19 +- .../07.express.md | 20 +- .../{2.frameworks => 4.frameworks}/08.hono.md | 19 +- .../09.fastify.md | 17 +- .../10.elysia.md | 17 +- .../11.react-router.md | 17 +- .../12.cloudflare-workers.md | 11 +- .../13.standalone.md | 17 +- .../14.astro.md | 9 +- .../15.custom-integration.md | 19 +- .../.navigation.yml | 0 .../{4.adapters => 5.adapters}/1.overview.md | 4 +- .../{4.adapters => 5.adapters}/10.pipeline.md | 2 +- .../{4.adapters => 5.adapters}/11.browser.md | 8 +- .../{4.adapters => 5.adapters}/2.axiom.md | 8 +- .../{4.adapters => 5.adapters}/3.otlp.md | 8 +- .../{4.adapters => 5.adapters}/4.posthog.md | 10 +- .../{4.adapters => 5.adapters}/5.sentry.md | 8 +- .../6.better-stack.md | 6 +- .../{4.adapters => 5.adapters}/7.fs.md | 18 +- .../{4.adapters => 5.adapters}/8.hyperdx.md | 8 +- .../{4.adapters => 5.adapters}/9.custom.md | 2 +- .../.navigation.yml | 0 .../1.overview.md | 2 +- .../2.built-in.md | 24 +- .../{5.enrichers => 6.enrichers}/3.custom.md | 6 +- .../{6.nuxthub => 7.nuxthub}/.navigation.yml | 0 .../{6.nuxthub => 7.nuxthub}/1.overview.md | 2 +- .../{6.nuxthub => 7.nuxthub}/2.retention.md | 4 +- apps/docs/nuxt.config.ts | 14 +- apps/docs/package.json | 1 + 61 files changed, 1068 insertions(+), 463 deletions(-) create mode 100644 apps/docs/content/2.logging/.navigation.yml create mode 100644 apps/docs/content/2.logging/0.overview.md create mode 100644 apps/docs/content/2.logging/1.simple-logging.md rename apps/docs/content/{3.core-concepts/1.wide-events.md => 2.logging/2.wide-events.md} (50%) rename apps/docs/content/{3.core-concepts/2.structured-errors.md => 2.logging/3.structured-errors.md} (94%) rename apps/docs/content/{3.core-concepts/6.client-logging.md => 2.logging/4.client-logging.md} (84%) rename apps/docs/content/{3.core-concepts/11.ai-sdk.md => 2.logging/5.ai-sdk.md} (95%) rename apps/docs/content/3.core-concepts/{7.configuration.md => 1.configuration.md} (96%) rename apps/docs/content/3.core-concepts/{5.sampling.md => 2.sampling.md} (94%) rename apps/docs/content/3.core-concepts/{4.typed-fields.md => 3.typed-fields.md} (90%) rename apps/docs/content/3.core-concepts/{3.best-practices.md => 4.best-practices.md} (96%) rename apps/docs/content/3.core-concepts/{8.performance.md => 5.performance.md} (100%) rename apps/docs/content/3.core-concepts/{10.vite-plugin.md => 6.vite-plugin.md} (96%) rename apps/docs/content/{2.frameworks => 4.frameworks}/.navigation.yml (100%) rename apps/docs/content/{2.frameworks => 4.frameworks}/00.overview.md (94%) rename apps/docs/content/{2.frameworks => 4.frameworks}/01.nuxt.md (96%) rename apps/docs/content/{2.frameworks => 4.frameworks}/02.nextjs.md (96%) rename apps/docs/content/{2.frameworks => 4.frameworks}/03.sveltekit.md (91%) rename apps/docs/content/{2.frameworks => 4.frameworks}/04.nitro.md (95%) rename apps/docs/content/{2.frameworks => 4.frameworks}/05.tanstack-start.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/06.nestjs.md (93%) rename apps/docs/content/{2.frameworks => 4.frameworks}/07.express.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/08.hono.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/09.fastify.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/10.elysia.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/11.react-router.md (93%) rename apps/docs/content/{2.frameworks => 4.frameworks}/12.cloudflare-workers.md (92%) rename apps/docs/content/{2.frameworks => 4.frameworks}/13.standalone.md (89%) rename apps/docs/content/{2.frameworks => 4.frameworks}/14.astro.md (91%) rename apps/docs/content/{2.frameworks => 4.frameworks}/15.custom-integration.md (90%) rename apps/docs/content/{4.adapters => 5.adapters}/.navigation.yml (100%) rename apps/docs/content/{4.adapters => 5.adapters}/1.overview.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/10.pipeline.md (99%) rename apps/docs/content/{4.adapters => 5.adapters}/11.browser.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/2.axiom.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/3.otlp.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/4.posthog.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/5.sentry.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/6.better-stack.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/7.fs.md (95%) rename apps/docs/content/{4.adapters => 5.adapters}/8.hyperdx.md (98%) rename apps/docs/content/{4.adapters => 5.adapters}/9.custom.md (99%) rename apps/docs/content/{5.enrichers => 6.enrichers}/.navigation.yml (100%) rename apps/docs/content/{5.enrichers => 6.enrichers}/1.overview.md (98%) rename apps/docs/content/{5.enrichers => 6.enrichers}/2.built-in.md (92%) rename apps/docs/content/{5.enrichers => 6.enrichers}/3.custom.md (96%) rename apps/docs/content/{6.nuxthub => 7.nuxthub}/.navigation.yml (100%) rename apps/docs/content/{6.nuxthub => 7.nuxthub}/1.overview.md (99%) rename apps/docs/content/{6.nuxthub => 7.nuxthub}/2.retention.md (98%) diff --git a/apps/docs/app/app.config.ts b/apps/docs/app/app.config.ts index 52b93615..a5c2a597 100644 --- a/apps/docs/app/app.config.ts +++ b/apps/docs/app/app.config.ts @@ -1,4 +1,7 @@ export default defineAppConfig({ + navigation: { + sub: 'header', + }, github: { rootDir: 'apps/docs', }, diff --git a/apps/docs/app/components/app/AppHeader.vue b/apps/docs/app/components/app/AppHeader.vue index 5d52179e..298ce5eb 100644 --- a/apps/docs/app/components/app/AppHeader.vue +++ b/apps/docs/app/components/app/AppHeader.vue @@ -1,8 +1,11 @@ diff --git a/apps/docs/app/components/features/FeatureStructuredErrors.vue b/apps/docs/app/components/features/FeatureStructuredErrors.vue index 1c0f5243..f50c3e68 100644 --- a/apps/docs/app/components/features/FeatureStructuredErrors.vue +++ b/apps/docs/app/components/features/FeatureStructuredErrors.vue @@ -45,7 +45,7 @@ const pills = [ {{ pill.label }} - + Learn about structured errors diff --git a/apps/docs/content/0.landing.md b/apps/docs/content/0.landing.md index 3bef80ce..2ea51b16 100644 --- a/apps/docs/content/0.landing.md +++ b/apps/docs/content/0.landing.md @@ -61,7 +61,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont :::features-feature-client-drain --- - link: /core-concepts/client-logging + link: /logging/client-logging link-label: Client logging guide --- #headline @@ -91,7 +91,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont :::features-feature-ai-sdk --- - link: /core-concepts/ai-sdk + link: /logging/ai-sdk link-label: AI SDK integration --- #headline @@ -107,7 +107,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont :::features-feature-performance --- link: /core-concepts/performance - link-label: Full benchmark results + link-label: Benchmark results --- #headline Performance @@ -161,6 +161,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont #nextjs ```ts [app/api/checkout/route.ts] import { withEvlog, useLogger } from '@/lib/evlog' + import { createError } from 'evlog' export const POST = withEvlog(async (req) => { const log = useLogger() @@ -217,7 +218,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont #nitro ```ts [routes/api/checkout.post.ts] - import { defineHandler } from 'nitro/h3' + import { defineHandler, readBody } from 'nitro/h3' import { useLogger, createError } from 'evlog/nitro/v3' export default defineHandler(async (event) => { @@ -312,6 +313,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont ```ts [app.module.ts] import { Module } from '@nestjs/common' import { EvlogModule } from 'evlog/nestjs' + import { createAxiomDrain } from 'evlog/axiom' @Module({ imports: [ @@ -321,15 +323,12 @@ Wide events and structured errors for TypeScript. One log per request, full cont ], }) export class AppModule {} - - // In any service or controller: - const log = useLogger() - log.set({ cart: { items: cart.items.length, total: cart.total } }) ``` #express ```ts [src/index.ts] import { evlog, useLogger } from 'evlog/express' + import { createAxiomDrain } from 'evlog/axiom' const app = express() app.use(evlog({ drain: createAxiomDrain() })) @@ -351,6 +350,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont #hono ```ts [src/index.ts] import { evlog, type EvlogVariables } from 'evlog/hono' + import { createAxiomDrain } from 'evlog/axiom' const app = new Hono() app.use(evlog({ drain: createAxiomDrain() })) @@ -371,7 +371,8 @@ Wide events and structured errors for TypeScript. One log per request, full cont #fastify ```ts [src/index.ts] - import { evlog, useLogger } from 'evlog/fastify' + import { evlog } from 'evlog/fastify' + import { createAxiomDrain } from 'evlog/axiom' const app = Fastify({ logger: false }) await app.register(evlog, { drain: createAxiomDrain() }) @@ -392,11 +393,12 @@ Wide events and structured errors for TypeScript. One log per request, full cont #elysia ```ts [src/index.ts] import { evlog, useLogger } from 'evlog/elysia' + import { createAxiomDrain } from 'evlog/axiom' const app = new Elysia() .use(evlog({ drain: createAxiomDrain() })) - .post('/checkout', async ({ log }) => { - const { cartId } = await req.body + .post('/checkout', async ({ log, body }) => { + const { cartId } = body const cart = await db.findCart(cartId) log.set({ cart: { items: cart.items.length, total: cart.total } }) @@ -433,11 +435,11 @@ Wide events and structured errors for TypeScript. One log per request, full cont #bun ```ts [scripts/migrate-users.ts] - import { initLogger, createRequestLogger } from 'evlog' + import { initLogger, createLogger } from 'evlog' - initLogger({ service: 'migrate' }) + initLogger({ env: { service: 'migrate' } }) - const log = createRequestLogger({ task: 'user-migration' }) + const log = createLogger({ task: 'user-migration' }) const users = await db.query('SELECT * FROM legacy_users') log.set({ found: users.length }) diff --git a/apps/docs/content/1.getting-started/1.introduction.md b/apps/docs/content/1.getting-started/1.introduction.md index 87c2092a..a55ad092 100644 --- a/apps/docs/content/1.getting-started/1.introduction.md +++ b/apps/docs/content/1.getting-started/1.introduction.md @@ -1,6 +1,6 @@ --- title: Introduction -description: A TypeScript logging library focused on wide events and structured error handling. Replace scattered logs with one comprehensive event per request. +description: A structured logging library for TypeScript. Simple logging, wide events, and structured errors — from quick one-liners to comprehensive request-scoped events. navigation: icon: i-lucide-info links: @@ -17,7 +17,7 @@ links: variant: subtle --- -**evlog** is a TypeScript logging library that replaces scattered log lines with comprehensive wide events and structured errors. +**evlog** is a structured logging library for TypeScript. It gives you simple one-liner logs, wide events that accumulate context over any operation, and structured errors that explain *why* something failed and *how* to fix it. Inspired by [Logging Sucks](https://loggingsucks.com/) by [Boris Tane](https://x.com/boristane). @@ -30,26 +30,26 @@ Traditional logging is broken. Your logs are scattered across dozens of files. E ::card-group :::card --- - icon: i-lucide-layers - title: Wide Events + icon: i-lucide-terminal + title: Structured Logging --- - One comprehensive log event per request, containing all the context you need. + Replace `console.log` with typed, structured events that flow through a drain pipeline. ::: :::card --- - icon: i-lucide-shield-alert - title: Structured Errors + icon: i-lucide-layers + title: Wide Events --- - Errors that explain why they occurred and how to fix them. + Accumulate context over any unit of work — a request, script, or job — and emit once. ::: :::card --- - icon: i-lucide-git-branch - title: Request Scoping + icon: i-lucide-shield-alert + title: Structured Errors --- - Accumulate context throughout the request lifecycle, emit once at the end. + Errors that explain why they occurred and how to fix them. ::: :::card @@ -61,56 +61,60 @@ Traditional logging is broken. Your logs are scattered across dozens of files. E ::: :: -## What are Wide Events? +## Three Ways to Log + +evlog provides three APIs for different contexts. You can use all three in the same project. -Instead of scattering logs throughout your code: +### Simple Logging -```typescript [Traditional logging] -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') +Fire-and-forget structured logs. Replace `console.log`, consola, or pino: + +```typescript [src/index.ts] +import { log } from 'evlog' + +log.info('auth', 'User logged in') +log.error({ action: 'payment', error: 'card_declined', userId: 42 }) ``` -You accumulate context and emit once: +### Wide Events + +Accumulate context progressively over any operation, then emit a single comprehensive event: ::code-group -```typescript [Code] -// server/api/checkout.post.ts -const log = useLogger(event) +```typescript [scripts/sync-job.ts] +import { createLogger } from 'evlog' -log.set({ user: { id: 1, plan: 'pro' } }) -log.set({ cart: { id: 42, items: 3, total: 9999 } }) -log.set({ payment: { method: 'card', status: 'success' } }) +const log = createLogger({ jobId: 'sync-001', queue: 'emails' }) +log.set({ batch: { size: 50, processed: 50 } }) +log.emit() +``` +```typescript [src/worker.ts] +import { createRequestLogger } from 'evlog' -return { success: true } +const log = createRequestLogger({ method: 'POST', path: '/api/checkout' }) +log.set({ user: { id: 1, plan: 'pro' } }) +log.emit() ``` -```bash [Output] -[INFO] POST /api/checkout (234ms) - user: { id: 1, plan: 'pro' } - cart: { id: 42, items: 3, total: 9999 } - payment: { method: 'card', status: 'success' } - status: 200 +```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + +export default defineEventHandler(async (event) => { + const log = useLogger(event) + log.set({ user: { id: 1, plan: 'pro' } }) + return { success: true } + // auto-emitted on response end +}) ``` :: -One log, all context. Everything you need to understand what happened during that request. - -## Structured Errors +One log, all context. Everything you need to understand what happened. -Traditional errors are opaque: +### Structured Errors -```typescript -throw new Error('Payment failed') -``` - -Structured errors provide actionable context: +Errors with actionable context — `why` it happened, how to `fix` it, and a `link` to docs: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] import { createError } from 'evlog' throw createError({ @@ -134,8 +138,6 @@ throw createError({ ``` :: -With `why`, `fix`, and `link` fields, anyone debugging (human or AI) can immediately understand the root cause and how to resolve it. - ## Why Context Matters We're entering an era where AI agents build, debug, and maintain applications. These agents need **structured context** to work effectively: @@ -150,3 +152,4 @@ Traditional `console.log` and generic `throw new Error()` provide no actionable - [Installation](/getting-started/installation) - Install evlog in your project - [Quick Start](/getting-started/quick-start) - Get up and running in minutes +- [Logging Overview](/logging/overview) - Understand the three logging modes in depth diff --git a/apps/docs/content/1.getting-started/2.installation.md b/apps/docs/content/1.getting-started/2.installation.md index 0bd6c30e..9f93debd 100644 --- a/apps/docs/content/1.getting-started/2.installation.md +++ b/apps/docs/content/1.getting-started/2.installation.md @@ -171,5 +171,5 @@ evlog requires TypeScript 5.0 or higher for optimal type inference. ## Next Steps - [Quick Start](/getting-started/quick-start) - Learn the core concepts and start using evlog -- [Wide Events](/core-concepts/wide-events) - Understand the wide event pattern +- [Wide Events](/logging/wide-events) - Understand the wide event pattern - [Adapters](/adapters/overview) - Send logs to Axiom, PostHog, Sentry, and more diff --git a/apps/docs/content/1.getting-started/3.quick-start.md b/apps/docs/content/1.getting-started/3.quick-start.md index 91d41d8c..b7fb5599 100644 --- a/apps/docs/content/1.getting-started/3.quick-start.md +++ b/apps/docs/content/1.getting-started/3.quick-start.md @@ -1,17 +1,17 @@ --- title: Quick Start -description: Get up and running with evlog in minutes. Learn useLogger, createError, parseError, and the log API for wide events and structured errors. +description: Get up and running with evlog in minutes. Learn the log API, createLogger for wide events, useLogger for requests, and structured errors. navigation: icon: i-lucide-zap links: - - label: Wide Events - icon: i-lucide-layers - to: /core-concepts/wide-events + - label: Logging Overview + icon: i-lucide-list + to: /logging/overview color: neutral variant: subtle - label: Structured Errors icon: i-lucide-shield-alert - to: /core-concepts/structured-errors + to: /logging/structured-errors color: neutral variant: subtle --- @@ -22,25 +22,88 @@ 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) +## log (Simple Logging) + +The simplest way to use evlog. Fire-and-forget structured logs, anywhere in your code: + +::code-group +```typescript [Server] +import { log } from 'evlog' + +log.info('auth', 'User logged in') +log.error({ action: 'payment', error: 'card_declined' }) +log.warn('cache', 'Cache miss') +``` +```bash [Output] +10:23:45.612 [auth] User logged in +10:23:45.613 ERROR [my-app] action=payment error=card_declined +10:23:45.614 [cache] Cache miss +``` +:: + +Two call styles: +- **Tagged** — `log.info('tag', 'message')` for quick, readable console output +- **Structured** — `log.info({ key: value })` for rich events that flow through the drain pipeline + +::callout{icon="i-lucide-arrow-right" color="neutral"} +See the full [Simple Logging](/logging/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`: + +::code-group +```typescript [scripts/sync-job.ts] +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() +``` +```bash [Output (Pretty)] +10:23:45.612 INFO [sync-worker] in 1204ms + ├─ jobId: job_abc123 + ├─ queue: emails + └─ batch: size=50 processed=50 +``` +:: + +`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`: -Use `useLogger(event)` in any Nuxt/Nitro API route to get a request-scoped logger: +```typescript [src/worker.ts] +import { createRequestLogger } from 'evlog' + +const log = createRequestLogger({ method: 'POST', path: '/api/checkout' }) +``` + +::callout{icon="i-lucide-info" color="info"} +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: ::code-group ```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + 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 } }) ``` @@ -53,16 +116,16 @@ export default defineEventHandler(async (event) => { :: ::callout{icon="i-lucide-check" color="success"} -The logger automatically emits when the request ends. No manual `emit()` call needed. +`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 useLogger vs createLogger vs log +### When to use what -| Use `useLogger(event)` | Use `createLogger()` | Use `log` | -|------------------------|---------------------|-----------| -| API routes, middleware, server plugins | Scripts, jobs, workers, queues, workflows | One-off events outside request context | -| When you need to accumulate context in a request | When you need to accumulate context outside a request | Quick debugging messages | -| For wide events (one log per request) | For wide events (one log per operation) | Client-side logging | +| Use `log` | Use `createLogger()` / `createRequestLogger()` | Use `useLogger(event)` | +|-----------|------------------------------------------------|------------------------| +| Quick one-off events | Scripts, jobs, workers, queues, HTTP without a framework | API routes with a framework integration | +| No context accumulation needed | Accumulate context over an operation | Retrieve the request-scoped logger | +| Client-side logging | Wide events (one log per operation) | Access the auto-managed wide event | ### Service Identification @@ -78,7 +141,7 @@ export default defineNuxtConfig({ evlog: { env: { - service: 'default-service', // Fallback service name + service: 'default-service', }, routes: { '/api/auth/**': { service: 'auth-service' }, @@ -103,8 +166,9 @@ Logs from routes matching these patterns will automatically include the configur Override the service name for specific routes using the second parameter of `useLogger`: ```typescript [server/api/legacy/process.post.ts] +import { useLogger } from 'evlog' + export default defineEventHandler((event) => { - // Explicitly set service name for this handler const log = useLogger(event, 'legacy-service') log.set({ action: 'process_legacy_request' }) @@ -117,51 +181,12 @@ export default defineEventHandler((event) => { **Priority order:** Explicit `useLogger` parameter > Route configuration > `env.service` > Auto-detected from environment :: -## createLogger (Standalone) - -Use `createLogger()` when you need a wide event logger outside of an HTTP request context: scripts, background jobs, queue workers, workflows, etc. - -::code-group -```typescript [scripts/sync-job.ts] -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() // Manual emit required -``` -```bash [Output (Pretty)] -10:23:45.612 INFO [sync-worker] in 1204ms - ├─ jobId: job_abc123 - ├─ queue: emails - └─ batch: size=50 processed=50 -``` -:: - -`createLogger()` accepts any initial context as a plain object. It returns the same `RequestLogger` interface (`set`, `error`, `info`, `warn`, `emit`, `getContext`). - -For HTTP request contexts specifically, use `createRequestLogger()` which pre-populates `method`, `path`, and `requestId`: - -```typescript -import { createRequestLogger } from 'evlog' - -const log = createRequestLogger({ method: 'POST', path: '/api/checkout' }) -``` - -::callout{icon="i-lucide-info" color="info"} -In standalone mode (both `createLogger` and `createRequestLogger`), you must call `log.emit()` manually. In Nuxt/Nitro, this happens automatically at request end. -:: - ## createError (Structured Errors) Use `createError()` to throw errors with actionable context: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] import { createError } from 'evlog' throw createError({ @@ -209,7 +234,6 @@ export async function checkout(cart: Cart) { } catch (err) { const error = parseError(err) - // Direct access to all fields toast.add({ title: error.message, description: error.why, @@ -226,28 +250,6 @@ export async function checkout(cart: Cart) { } ``` -## log (Simple Logging) - -For quick one-off logs anywhere in your code: - -::code-group -```typescript [Server] -// server/utils/auth.ts -log.info('auth', 'User logged in') -log.error({ action: 'payment', error: 'card_declined' }) -log.warn('cache', 'Cache miss') -``` -```bash [Output] -10:23:45.612 [auth] User logged in -10:23:45.613 ERROR [my-app] action=payment error=card_declined -10:23:45.614 [cache] Cache miss -``` -:: - -::callout{icon="i-lucide-lightbulb" color="warning"} -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: @@ -281,55 +283,14 @@ export function useAnalytics() { ``` :: -In pretty mode (development), client logs appear with colored tags in the browser console: - -``` -[my-app] info { action: 'checkout', status: 'success' } -``` - -::callout{icon="i-lucide-info" color="info"} -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: - -::code-group -```typescript [Code] -// 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 }) -``` -```json [JSON Output (Production)] -{ - "level": "info", - "method": "POST", - "path": "/api/checkout", - "userId": 1, - "subscription": "pro", - "cart": { "items": 3, "total": 9999 }, - "coupon": "SAVE10", - "status": 200, - "duration": 234 -} -``` +::callout{icon="i-lucide-arrow-right" color="neutral"} +See [Client Logging](/logging/client-logging) for transport configuration, identity context, and browser drain setup. :: ## Next Steps -- [Wide Events](/core-concepts/wide-events) - Learn how to design effective wide events -- [Typed Fields](/core-concepts/typed-fields) - Add compile-time type safety to your wide events -- [Structured Errors](/core-concepts/structured-errors) - Master error handling with evlog -- [Best Practices](/core-concepts/best-practices) - Security guidelines and production tips +- [Logging Overview](/logging/overview) — Understand all three logging modes +- [Wide Events](/logging/wide-events) — Learn how to design effective wide events +- [Typed Fields](/core-concepts/typed-fields) — Add compile-time type safety to your wide events +- [Structured Errors](/logging/structured-errors) — Master error handling with evlog +- [Best Practices](/core-concepts/best-practices) — Security guidelines and production tips diff --git a/apps/docs/content/1.getting-started/4.agent-skills.md b/apps/docs/content/1.getting-started/4.agent-skills.md index 0187743c..c88a79e2 100644 --- a/apps/docs/content/1.getting-started/4.agent-skills.md +++ b/apps/docs/content/1.getting-started/4.agent-skills.md @@ -159,5 +159,5 @@ The skill includes reference documents that provide: ## Next Steps - [Quick Start](/getting-started/quick-start) - Get started with evlog -- [Wide Events](/core-concepts/wide-events) - Learn wide event design -- [Structured Errors](/core-concepts/structured-errors) - Error handling patterns +- [Wide Events](/logging/wide-events) - Learn wide event design +- [Structured Errors](/logging/structured-errors) - Error handling patterns diff --git a/apps/docs/content/2.logging/.navigation.yml b/apps/docs/content/2.logging/.navigation.yml new file mode 100644 index 00000000..7a89bd39 --- /dev/null +++ b/apps/docs/content/2.logging/.navigation.yml @@ -0,0 +1 @@ +title: Logging diff --git a/apps/docs/content/2.logging/0.overview.md b/apps/docs/content/2.logging/0.overview.md new file mode 100644 index 00000000..9fa2116a --- /dev/null +++ b/apps/docs/content/2.logging/0.overview.md @@ -0,0 +1,140 @@ +--- +title: Logging Overview +description: evlog gives you three ways to log — simple one-liners, wide events that accumulate context, and auto-managed request logging. Choose the right one for your use case. +navigation: + title: Overview + icon: i-lucide-list +links: + - label: Simple Logging + icon: i-lucide-terminal + to: /logging/simple-logging + color: neutral + variant: subtle + - label: Wide Events + icon: i-lucide-layers + to: /logging/wide-events + color: neutral + variant: subtle +--- + +evlog provides three logging APIs, each designed for a different context. You can use all three in the same project. + +## The Three Modes + +::card-group + :::card + --- + icon: i-lucide-terminal + title: Simple Logging + to: /logging/simple-logging + color: neutral + --- + Fire-and-forget structured logs. Replace `console.log`, consola, or pino with `log.info`, `log.error`, `log.warn`, `log.debug`. + ::: + + :::card + --- + icon: i-lucide-layers + title: Wide Events + to: /logging/wide-events + color: neutral + --- + Accumulate context over a unit of work — a script, job, queue task, or request — then emit a single comprehensive event. + ::: + + :::card + --- + icon: i-lucide-git-branch + title: Request Logging + to: /frameworks/overview + color: neutral + --- + Auto-managed wide events scoped to HTTP requests. Framework middleware creates the logger and emits it for you. + ::: +:: + +## Quick Comparison + +### Simple Logging (`log`) + +One event per call. No accumulation, no lifecycle management. + +```typescript [src/index.ts] +import { log } from 'evlog' + +log.info('auth', 'User logged in') +log.error({ action: 'payment', error: 'card_declined', userId: 42 }) +``` + +### Wide Events (`createLogger` / `createRequestLogger`) + +One event per unit of work. Accumulate context progressively, emit when done. + +::code-group +```typescript [scripts/sync-job.ts] +import { createLogger } from 'evlog' + +const log = createLogger({ jobId: 'sync-001', queue: 'emails' }) +log.set({ batch: { size: 50, processed: 50 } }) +log.emit() +``` +```typescript [src/worker.ts] +import { createRequestLogger } from 'evlog' + +const log = createRequestLogger({ method: 'POST', path: '/api/checkout' }) +log.set({ user: { id: 1, plan: 'pro' } }) +log.emit() +``` +:: + +`createRequestLogger` is a thin wrapper around `createLogger` that pre-populates `method`, `path`, and `requestId`. + +### Request Logging (framework middleware) + +Framework integrations create a wide event logger automatically on each request. `useLogger(event)` retrieves the logger that's already attached to the request context: + +```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + +export default defineEventHandler(async (event) => { + const log = useLogger(event) + log.set({ user: { id: 1, plan: 'pro' } }) + return { success: true } + // auto-emitted on response end +}) +``` + +::callout{icon="i-lucide-info" color="info"} +`useLogger(event)` doesn't create a logger — it retrieves the one the framework middleware already attached to the event. Each framework has its own way to access it (`useLogger`, `req.log`, `c.get('log')`, etc.). In Nuxt, `useLogger` is auto-imported. +:: + +## When to Use What + +| | `log` | `createLogger` / `createRequestLogger` | Framework middleware | +|--|-------|----------------------------------------|---------------------| +| **Use case** | Quick one-off events | Scripts, jobs, workers, queues, HTTP without a framework | API routes with a framework integration | +| **Context** | Single call | Accumulate with `set()` | Accumulate with `set()` | +| **Emit** | Immediate | Manual `emit()` | Automatic on response end | +| **Lifecycle** | None | You manage it | Framework manages it | +| **Output** | Console + drain | Console + drain | Console + drain + enrich | + +::callout{icon="i-lucide-lightbulb" color="info"} +Start with `log` for quick structured logging. When you need to accumulate context across an operation, switch to `createLogger` (or `createRequestLogger` for HTTP contexts). When using a framework integration, the middleware handles everything — just call `useLogger(event)` to retrieve the logger. +:: + +## Shared Features + +All three modes share the same foundation: + +- **Pretty output** in development, **JSON** in production (default, no configuration needed) +- **Drain pipeline** to send events to Axiom, Sentry, PostHog, and more +- **Structured errors** with `why`, `fix`, and `link` fields +- **Sampling** to control log volume in production +- **Zero dependencies**, ~5 kB gzip + +## Next Steps + +- [Simple Logging](/logging/simple-logging) — The `log` API in detail +- [Wide Events](/logging/wide-events) — Accumulating context and emitting events +- [Structured Errors](/logging/structured-errors) — Errors with actionable context +- [Frameworks](/frameworks/overview) — Auto-managed request logging per framework diff --git a/apps/docs/content/2.logging/1.simple-logging.md b/apps/docs/content/2.logging/1.simple-logging.md new file mode 100644 index 00000000..cfa337b4 --- /dev/null +++ b/apps/docs/content/2.logging/1.simple-logging.md @@ -0,0 +1,176 @@ +--- +title: Simple Logging +description: Structured logging for everyday use. Replace console.log with log.info, log.error, log.warn, and log.debug. Fire-and-forget events with pretty output in dev and JSON in production. +navigation: + icon: i-lucide-terminal +links: + - label: Wide Events + icon: i-lucide-layers + to: /logging/wide-events + color: neutral + variant: subtle + - label: Configuration + icon: i-lucide-settings + to: /core-concepts/configuration + color: neutral + variant: subtle +--- + +The `log` API is the simplest way to use evlog. Each call emits a single structured event — no accumulation, no lifecycle management, no manual `emit()`. + +::callout{icon="i-lucide-sparkles" color="info"} +In Nuxt, `log` is **auto-imported**. No import statement needed. +:: + +## Setup + +For standalone projects (non-Nuxt), initialize once at startup: + +```typescript [src/index.ts] +import { initLogger, log } from 'evlog' + +initLogger({ + env: { service: 'my-app' }, +}) + +log.info('app', 'Server started') +``` + +::callout{icon="i-lucide-info" color="info"} +`env.service` defaults to `'app'` if not specified. Only set it if you want a custom service name. +:: + +## Two Call Styles + +### Tagged Logs + +Pass a tag and a message for quick, readable output: + +```typescript [src/index.ts] +import { log } from 'evlog' + +log.info('auth', 'User logged in') +log.warn('cache', 'Cache miss for key user:42') +log.error('payment', 'Stripe webhook failed') +log.debug('router', 'Matched route /api/checkout') +``` + +```bash [Output (Pretty)] +10:23:45.612 [auth] User logged in +10:23:45.613 [cache] Cache miss for key user:42 +10:23:45.614 ERROR [payment] Stripe webhook failed +10:23:45.615 [router] Matched route /api/checkout +``` + +### Structured Events + +Pass an object for rich, queryable events that flow through the drain pipeline: + +```typescript [src/index.ts] +import { log } from 'evlog' + +log.info({ action: 'user_login', userId: 42, method: 'oauth', provider: 'github' }) +log.error({ action: 'sync_failed', source: 'postgres', target: 's3', error: 'connection_timeout' }) +``` + +```bash [Output (Pretty)] +10:23:45.612 INFO [my-app] + ├─ action: user_login + ├─ userId: 42 + ├─ method: oauth + └─ provider: github +``` + +::callout{icon="i-lucide-info" color="info"} +**Tagged logs** are optimized for console readability. **Structured events** (object form) produce full wide events that flow through the drain pipeline to external services. +:: + +## Log Levels + +| Level | Method | When to use | +|-------|--------|-------------| +| `info` | `log.info()` | Normal operations: startup, shutdown, successful actions | +| `warn` | `log.warn()` | Unexpected but recoverable situations: cache miss, retry, deprecation | +| `error` | `log.error()` | Failures that need attention: API errors, timeouts, invalid state | +| `debug` | `log.debug()` | Development-only details: SQL queries, intermediate state, routing | + +::callout{icon="i-lucide-lightbulb" color="warning"} +`log.debug()` calls can be stripped from production builds using the [Vite Plugin](/core-concepts/vite-plugin) or the Nuxt module's `strip` option. +:: + +## Common Patterns + +### Application Lifecycle + +```typescript [src/index.ts] +import { log } from 'evlog' + +log.info('app', 'Starting server on port 3000') +log.info({ action: 'db_connected', host: 'localhost', database: 'mydb', pool: 10 }) +log.info('app', 'Ready to accept connections') +``` + +### Background Tasks + +```typescript [src/jobs/cleanup.ts] +import { log } from 'evlog' + +log.info({ action: 'cron_started', job: 'cleanup', schedule: '0 */6 * * *' }) +log.info({ action: 'cron_completed', job: 'cleanup', deleted: 42, duration: 1200 }) +``` + +### Utility Functions + +```typescript [src/utils/webhook.ts] +import { log } from 'evlog' + +function processWebhook(payload: WebhookPayload) { + log.info({ action: 'webhook_received', type: payload.type, source: payload.source }) + + if (!isValid(payload)) { + log.warn({ action: 'webhook_invalid', type: payload.type, reason: 'missing_signature' }) + return + } +} +``` + +## Drain Integration + +When using the object form, events are sent through the [drain pipeline](/adapters/overview) just like wide events: + +```typescript [src/index.ts] +import { initLogger, log } from 'evlog' +import { createAxiomDrain } from 'evlog/axiom' + +initLogger({ + env: { service: 'my-app' }, + drain: createAxiomDrain(), +}) + +log.info({ action: 'deploy', version: '1.2.3', region: 'us-east-1' }) +``` + +## When to Upgrade to createLogger + +Use `log` when each event is self-contained. When you need to **accumulate context** across multiple steps of an operation, switch to [`createLogger`](/logging/wide-events): + +```typescript [scripts/sync-data.ts] +import { log, createLogger } from 'evlog' + +// log: each call is independent +log.info({ action: 'sync_started', source: 'postgres' }) +log.info({ action: 'sync_completed', records: 150 }) + +// createLogger: accumulate context, emit once +const syncLog = createLogger({ source: 'postgres' }) +syncLog.set({ records: 150 }) +syncLog.set({ status: 'complete' }) +syncLog.emit() +``` + +## Next Steps + +- [Wide Events](/logging/wide-events) — Accumulate context and emit comprehensive events +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` +- [Configuration](/core-concepts/configuration) — All `initLogger` options +- [Adapters](/adapters/overview) — Send events to Axiom, Sentry, PostHog, and more diff --git a/apps/docs/content/3.core-concepts/1.wide-events.md b/apps/docs/content/2.logging/2.wide-events.md similarity index 50% rename from apps/docs/content/3.core-concepts/1.wide-events.md rename to apps/docs/content/2.logging/2.wide-events.md index 5e097b4c..16fb32cf 100644 --- a/apps/docs/content/3.core-concepts/1.wide-events.md +++ b/apps/docs/content/2.logging/2.wide-events.md @@ -1,12 +1,12 @@ --- title: Wide Events -description: Learn how to design effective wide events that capture everything you need in a single log. One comprehensive event per request with full context. +description: Accumulate context over any unit of work and emit a single comprehensive event. Works for HTTP requests, scripts, background jobs, queue workers, and workflows. navigation: icon: i-lucide-layers links: - label: Structured Errors icon: i-lucide-shield-alert - to: /core-concepts/structured-errors + to: /logging/structured-errors color: neutral variant: subtle - label: Best Practices @@ -16,41 +16,48 @@ links: variant: subtle --- -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. +Wide events are the core concept behind evlog. Instead of scattering logs throughout your codebase, you accumulate context over any unit of work — a request, script, job, or workflow — and emit a single, comprehensive log event. ## Why Wide Events? Traditional logging creates noise: -```typescript [server/api/checkout.post.ts] -// Traditional approach - 6 separate log lines -logger.info('Request started') +```typescript [src/service.ts] +logger.info('Job 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 }) +logger.info('Fetching data', { source: 'postgres' }) +logger.info('Processing records') +logger.info('Processing complete') +logger.info('Job finished', { 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 +- **Hard to correlate**: Matching logs to operations requires IDs everywhere +- **Noise**: 10+ log lines per operation makes finding issues harder - **Incomplete**: Some logs might be missing if errors occur Wide events solve this: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + 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' } }) +``` +```typescript [scripts/sync-data.ts] +import { createLogger } from 'evlog' -// One log, all context - emitted automatically +const log = createLogger({ jobId: 'sync-001', queue: 'emails' }) + +log.set({ source: 'postgres', target: 's3' }) +log.set({ records: { found: 1250, synced: 1250 } }) +log.emit() ``` ```bash [Output] [INFO] POST /api/checkout (234ms) @@ -61,30 +68,114 @@ log.set({ payment: { method: 'card', status: 'success' } }) ``` :: +One log, all context. Everything you need to understand what happened. + +## Creating Wide Events + +### `createLogger` — General Purpose + +Use `createLogger()` for scripts, background jobs, queue workers, cron jobs, or any operation where you manage the lifecycle: + +```typescript [scripts/migrate-users.ts] +import { initLogger, createLogger } from 'evlog' + +initLogger({ env: { service: 'migrate' } }) + +const log = createLogger({ task: 'user-migration' }) + +const users = await db.query('SELECT * FROM legacy_users') +log.set({ found: users.length }) + +let migrated = 0 +for (const user of users) { + await newDb.upsert({ id: user.id, email: user.email, plan: user.plan }) + migrated++ +} + +log.set({ migrated, status: 'complete' }) +log.emit() +``` + +### `createRequestLogger` — HTTP Contexts + +Use `createRequestLogger()` when working with HTTP requests outside of a framework integration. It's a thin wrapper around `createLogger` that pre-populates `method`, `path`, and `requestId`: + +```typescript [src/worker.ts] +import { initLogger, createRequestLogger } from 'evlog' + +initLogger({ env: { service: 'my-worker' } }) + +const log = createRequestLogger({ method: 'POST', path: '/api/checkout' }) + +log.set({ user: { id: 1, plan: 'pro' } }) +log.set({ cart: { items: 3, total: 9999 } }) + +log.emit() +``` + +::callout{icon="i-lucide-info" color="info"} +Both `createLogger` and `createRequestLogger` require a manual `log.emit()` call. The event won't be emitted until you call it. +:: + +### `useLogger` — Retrieving the Request Logger + +When using a framework integration (Nuxt, Hono, Express, etc.), the middleware creates a wide event logger automatically on each request. `useLogger(event)` retrieves that logger from the request context: + +```typescript [server/api/checkout.post.ts] +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 } }) + + return { success: true } + // auto-emitted on response end +}) +``` + +::callout{icon="i-lucide-info" color="info"} +`useLogger` doesn't create a logger — it retrieves the one the framework middleware already attached to the event. The middleware handles creation and emission automatically. In Nuxt, `useLogger` is auto-imported. +:: + ## Anatomy of a Wide Event -A well-designed wide event contains context from multiple layers: +A well-designed wide event contains context from multiple layers. The examples below show what to add inside your handler or script — they assume `log` is already created via `createLogger`, `createRequestLogger`, or `useLogger`. -### Request Context +### Operation Context -Basic information about the request itself: +Basic information about the operation: +::code-group ```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + +const log = useLogger(event) log.set({ method: 'POST', path: '/api/checkout', requestId: 'abc-123-def', - traceId: 'trace-xyz-789', }) ``` +```typescript [scripts/sync-data.ts] +import { createLogger } from 'evlog' + +const log = createLogger({ + jobId: 'sync-001', + queue: 'emails', + source: 'postgres', +}) +``` +:: ::callout{icon="i-lucide-info" color="info"} -In Nuxt/Nitro, most request context is auto-populated by evlog. +In framework integrations, request context (`method`, `path`, `requestId`) is auto-populated by the middleware. You don't need to set these fields manually. :: -### User Context +### User / Actor Context -Who is making the request: +Who triggered the operation: ```typescript [server/api/checkout.post.ts] log.set({ @@ -143,7 +234,7 @@ log.set({ ### Use Meaningful Keys -```typescript +```typescript [server/api/orders.post.ts] // Avoid generic keys log.set({ data: { id: 123 } }) @@ -153,7 +244,7 @@ log.set({ order: { id: 123, status: 'pending' } }) ### Group Related Data -```typescript +```typescript [server/api/checkout.post.ts] // Flat structure is hard to read log.set({ userId: 1, @@ -174,8 +265,9 @@ log.set({ Call `log.set()` as you gather information: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + export default defineEventHandler(async (event) => { const log = useLogger(event) @@ -205,8 +297,9 @@ export default defineEventHandler(async (event) => { When errors occur, the wide event still emits with error context: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + export default defineEventHandler(async (event) => { const log = useLogger(event) @@ -240,7 +333,7 @@ export default defineEventHandler(async (event) => { ## Output Formats -evlog automatically switches between formats based on environment: +evlog automatically switches between formats based on environment — pretty in development, JSON in production. This is the default behavior, no configuration needed. ::code-group ```bash [Development (Pretty)] @@ -264,5 +357,7 @@ evlog automatically switches between formats based on environment: ## Next Steps +- [Simple Logging](/logging/simple-logging) - Fire-and-forget logs when you don't need context accumulation - [Typed Fields](/core-concepts/typed-fields) - Add compile-time type safety to your wide events -- [Structured Errors](/core-concepts/structured-errors) - Learn how to create errors with actionable context +- [Structured Errors](/logging/structured-errors) - Errors with actionable context +- [Frameworks](/frameworks/overview) - Auto-managed request logging per framework diff --git a/apps/docs/content/3.core-concepts/2.structured-errors.md b/apps/docs/content/2.logging/3.structured-errors.md similarity index 94% rename from apps/docs/content/3.core-concepts/2.structured-errors.md rename to apps/docs/content/2.logging/3.structured-errors.md index 130d5a5a..2a149524 100644 --- a/apps/docs/content/3.core-concepts/2.structured-errors.md +++ b/apps/docs/content/2.logging/3.structured-errors.md @@ -6,7 +6,7 @@ navigation: links: - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle - label: Best Practices @@ -32,8 +32,9 @@ This tells you *what* happened, but not *why* or *how to fix it*. Structured errors provide context: ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] +import { createError } from 'evlog' + throw createError({ message: 'Payment failed', status: 402, @@ -71,8 +72,7 @@ throw createError({ ### Simple Error ::code-group -```typescript [Code] -// server/api/users/[id].get.ts +```typescript [server/api/users/[id].get.ts] import { createError } from 'evlog' throw createError({ @@ -91,8 +91,9 @@ throw createError({ ### Error with Full Context ::code-group -```typescript [Code] -// server/api/checkout.post.ts +```typescript [server/api/checkout.post.ts] +import { createError } from 'evlog' + throw createError({ message: 'Payment failed', status: 402, @@ -119,6 +120,8 @@ throw createError({ Wrap underlying errors while preserving the original: ```typescript [server/api/checkout.post.ts] +import { createError } from 'evlog' + try { await stripe.charges.create(charge) } catch (err) { @@ -136,8 +139,7 @@ try { Use `parseError()` to extract all fields from caught errors: ::code-group -```typescript [Code] -// composables/useCheckout.ts +```typescript [composables/useCheckout.ts] import { parseError } from 'evlog' try { @@ -151,8 +153,7 @@ try { console.log(error.fix) // "Try another card" } ``` -```typescript [With Nuxt UI Toast] -// composables/useCheckout.ts +```typescript [composables/useCheckout.ts (Nuxt UI)] import { parseError } from 'evlog' const toast = useToast() diff --git a/apps/docs/content/3.core-concepts/6.client-logging.md b/apps/docs/content/2.logging/4.client-logging.md similarity index 84% rename from apps/docs/content/3.core-concepts/6.client-logging.md rename to apps/docs/content/2.logging/4.client-logging.md index e0644b79..e7dd46b5 100644 --- a/apps/docs/content/3.core-concepts/6.client-logging.md +++ b/apps/docs/content/2.logging/4.client-logging.md @@ -11,7 +11,7 @@ links: variant: subtle - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle --- @@ -56,44 +56,43 @@ log.info({ action: 'app_init', path: window.location.pathname }) The `log` object works anywhere in your client code: components, composables, event handlers. -## Logging API +## Two Call Signatures -### Wide Events +The `log` API accepts two forms depending on the context. -Pass an object to capture structured context, just like server-side `log.set()`: +### Object Form (structured context) -```typescript +Pass an object to capture structured data, just like server-side `log.set()`: + +```typescript [pages/products.vue] log.info({ action: 'page_view', path: '/products', referrer: document.referrer }) -log.warn({ action: 'slow_load', component: 'ProductList', duration: 3200 }) -log.error({ action: 'fetch_failed', endpoint: '/api/cart', status: 500 }) ``` -### Tagged Logs +```bash [Browser console] +[web] info { action: 'page_view', path: '/products', referrer: 'https://google.com' } +``` + +### Tag + Message Form (quick logs) -Pass a tag and message for quick, readable logs: +Pass a tag and a message for quick, readable logs: -```typescript +```typescript [composables/useAuth.ts] log.info('auth', 'User logged in') -log.warn('perf', 'Image lazy-load took 4s') -log.error('payment', 'Stripe checkout failed') -log.debug('router', 'Navigated to /checkout') ``` -### Console Output - -In the browser console, logs render with colors and grouping: - -```bash -[web] info { action: 'page_view', path: '/products' } +```bash [Browser console] [auth] User logged in -[perf] Image lazy-load took 4s ``` +### 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()`: -```typescript +```typescript [composables/useAuth.ts] import { setIdentity, clearIdentity, log } from 'evlog/client' // After login @@ -120,19 +119,20 @@ Identity fields are automatically merged into every log event until cleared. Thi | `service` | `'client'` | Service name included in every log event | | `transport` | - | Send logs to a server endpoint (see below) | -```typescript +```typescript [app/plugins/logger.client.ts] initLog({ - enabled: true, - console: true, - pretty: true, service: 'web', transport: { enabled: true, - endpoint: '/api/_evlog/ingest', + endpoint: '/api/_evlog/ingest', // default endpoint }, }) ``` +::callout{icon="i-lucide-info" color="info"} +`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: @@ -224,4 +224,4 @@ See the [Browser Drain](/adapters/browser) adapter docs for full configuration r - [Browser Drain](/adapters/browser) - Batching, retry, and sendBeacon fallback - [Pipeline](/adapters/pipeline) - Advanced pipeline configuration -- [Structured Errors](/core-concepts/structured-errors) - Surface client errors with actionable context +- [Structured Errors](/logging/structured-errors) - Surface client errors with actionable context diff --git a/apps/docs/content/3.core-concepts/11.ai-sdk.md b/apps/docs/content/2.logging/5.ai-sdk.md similarity index 95% rename from apps/docs/content/3.core-concepts/11.ai-sdk.md rename to apps/docs/content/2.logging/5.ai-sdk.md index beb30c73..431d29f0 100644 --- a/apps/docs/content/3.core-concepts/11.ai-sdk.md +++ b/apps/docs/content/2.logging/5.ai-sdk.md @@ -6,7 +6,7 @@ navigation: links: - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle - label: Adapters @@ -31,7 +31,7 @@ Add AI observability to my app with evlog. - For embedding calls, use ai.captureEmbed({ usage }) after embed() or embedMany() - Works with all frameworks: Nuxt, Express, Hono, Fastify, NestJS, Elysia, standalone -Docs: https://www.evlog.dev/core-concepts/ai-sdk +Docs: https://www.evlog.dev/logging/ai-sdk Adapters: https://www.evlog.dev/adapters ``` @@ -210,10 +210,12 @@ The middleware fires for each step automatically. Steps, tool calls, and tokens ```typescript [server/api/agent.post.ts] import { ToolLoopAgent, createAgentUIStreamResponse, stepCountIs } from 'ai' +import { useLogger } from 'evlog' import { createAILogger } from 'evlog/ai' export default defineEventHandler(async (event) => { const log = useLogger(event) + const { messages } = await readBody(event) const ai = createAILogger(log, { toolInputs: { maxLength: 500 }, }) @@ -359,18 +361,20 @@ const model = ai.wrap(anthropic('claude-sonnet-4.6')) `ai.wrap()` works with models that are already wrapped by other tools. If you use supermemory, guardrails middleware, or any other model wrapper, pass the wrapped model to `ai.wrap()`: -```typescript +```typescript [server/api/chat.post.ts] import { createAILogger } from 'evlog/ai' import { withSupermemory } from '@supermemory/tools/ai-sdk' +import { createGateway } from 'ai' +const gateway = createGateway({ ... }) const ai = createAILogger(log) const base = gateway('anthropic/claude-sonnet-4.6') -const model = ai.wrap(withSupermemory(base, orgId, { mode: 'full' })) +const model = ai.wrap(withSupermemory(base, 'your-org-id', { mode: 'full' })) ``` For explicit middleware composition, use `createAIMiddleware` to get the raw middleware and compose it yourself via `wrapLanguageModel`: -```typescript +```typescript [server/api/chat.post.ts] import { createAIMiddleware } from 'evlog/ai' import { wrapLanguageModel } from 'ai' @@ -406,11 +410,16 @@ Stream errors (e.g. content filter) are also captured from the stream's error ch ::code-group ```typescript [Nuxt] +import { useLogger } from 'evlog' +import { createAILogger } from 'evlog/ai' + const log = useLogger(event) const ai = createAILogger(log) ``` ```typescript [Express] +import { createAILogger } from 'evlog/ai' + app.post('/api/chat', (req, res) => { const ai = createAILogger(req.log) // ... @@ -418,6 +427,8 @@ app.post('/api/chat', (req, res) => { ``` ```typescript [Hono] +import { createAILogger } from 'evlog/ai' + app.post('/api/chat', (c) => { const ai = createAILogger(c.get('log')) // ... @@ -425,6 +436,8 @@ app.post('/api/chat', (c) => { ``` ```typescript [Fastify] +import { createAILogger } from 'evlog/ai' + app.post('/api/chat', async (request) => { const ai = createAILogger(request.log) // ... @@ -433,6 +446,7 @@ app.post('/api/chat', async (request) => { ```typescript [NestJS] import { useLogger } from 'evlog/nestjs' +import { createAILogger } from 'evlog/ai' const log = useLogger() const ai = createAILogger(log) @@ -440,6 +454,7 @@ const ai = createAILogger(log) ```typescript [Standalone] import { createLogger } from 'evlog' +import { createAILogger } from 'evlog/ai' const log = createLogger() const ai = createAILogger(log) diff --git a/apps/docs/content/3.core-concepts/0.lifecycle.md b/apps/docs/content/3.core-concepts/0.lifecycle.md index 85c8e217..6599ef76 100644 --- a/apps/docs/content/3.core-concepts/0.lifecycle.md +++ b/apps/docs/content/3.core-concepts/0.lifecycle.md @@ -1,12 +1,12 @@ --- -title: Request Lifecycle -description: Understand the full lifecycle of a request in evlog, from creation to drain. Every step from logger creation, context accumulation, sampling, enrichment, to external delivery. +title: Lifecycle +description: Understand the full lifecycle of an evlog event — from creation to drain. Covers all three modes (simple logging, wide events, request logging), sampling, enrichment, and delivery. navigation: icon: i-lucide-arrow-right-left links: - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle - label: Sampling @@ -16,7 +16,22 @@ links: variant: subtle --- -Every request that passes through evlog follows the same pipeline. Understanding this pipeline helps you know exactly when your hooks fire, where context is added, and how events reach your observability platform. +evlog events follow a pipeline from creation to delivery. The pipeline differs slightly depending on which logging mode you use, but the core stages — emit, sample, enrich, drain — are shared. + +## Overview by Mode + +| Stage | `log` (simple) | `createLogger` / `createRequestLogger` | Framework middleware | +|-------|----------------|----------------------------------------|---------------------| +| **Create** | Implicit per call | `createLogger({...})` or `createRequestLogger({...})` | Auto on request start | +| **Accumulate** | N/A (single call) | `log.set()` multiple times | `log.set()` via `useLogger(event)` | +| **Emit** | Immediate | Manual `log.emit()` | Auto on response end | +| **Sample** | Head sampling only | Head + tail sampling | Head + tail sampling | +| **Enrich** | Via global drain | Via global drain | Via hooks or callbacks | +| **Drain** | Via global drain | Via global drain | Via hooks or callbacks | + +## Request Logging Pipeline + +For framework-managed request logging, every request follows this pipeline. The middleware creates the logger and `useLogger(event)` retrieves it: ```mdc Request In @@ -82,6 +97,8 @@ Every request that passes through evlog follows the same pipeline. Understanding When a request arrives, evlog checks whether the path matches the configured `include` / `exclude` patterns. If the route is excluded, no logger is created and the request proceeds without any logging overhead. +By default, all routes are logged. Use `include` to restrict logging to specific patterns: + ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['evlog/nuxt'], @@ -102,13 +119,15 @@ For matched routes, evlog creates a `RequestLogger` and attaches it to the reque | `requestId` | Auto-generated UUID (or `cf-ray` on Cloudflare) | | `startTime` | `Date.now()` for duration calculation | -The logger is stored on the event context so it's accessible via `useLogger(event)`. +The logger is stored on the event context. `useLogger(event)` is a shortcut to retrieve it — it doesn't create a new logger. ### 3. Context Accumulation During the handler, you call `log.set()` to attach context. Each call deep-merges into the existing context, so you can call it as many times as needed: ```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + const log = useLogger(event) const user = await getUser(event) @@ -162,7 +181,9 @@ If any rule or hook sets `shouldKeep = true`, the event **bypasses head sampling ### 6. Head Sampling -If the event wasn't force-kept by tail sampling, head sampling applies. This is a random coin flip per log level: +If the event wasn't force-kept by tail sampling, head sampling applies. This is a random coin flip per log level. + +By default, all levels are kept at 100% (no sampling). Configure `sampling.rates` to reduce volume in production: ```typescript [nuxt.config.ts] evlog: { @@ -174,7 +195,7 @@ evlog: { - `info: 10` - keep 10% of info-level events - `warn: 50` - keep 50% of warnings -- `error` defaults to **100%** (never sampled out) +- `error` defaults to **100%** (never sampled out, even if you set a rate) If the event is sampled out, processing stops entirely: no console output, no enrichment, no drain. @@ -182,9 +203,9 @@ If the event is sampled out, processing stops entirely: no console output, no en The `WideEvent` object is built from the accumulated context: -```json +```json [WideEvent] { - "timestamp": "2025-01-15T10:30:00.000Z", + "timestamp": "2026-01-15T10:30:00.000Z", "level": "info", "service": "my-app", "method": "POST", @@ -197,7 +218,7 @@ The `WideEvent` object is built from the accumulated context: } ``` -The event is printed to the console, pretty-formatted in development and as JSON in production. +The event is printed to the console — pretty-formatted in development and as JSON in production. This is the default behavior, no configuration needed. ### 8. Enrich (`evlog:enrich`) @@ -258,9 +279,44 @@ Both paths converge at the same emit/enrich/drain pipeline. The only difference | **Error context** | None | `error` field with message, stack, `why`, `fix` | | **Double-emit guard** | Checks `_evlogEmitted` flag | Sets `_evlogEmitted = true` | +## Simple Logging Pipeline + +When using the `log` singleton, the pipeline is shorter: + +1. **Call** — `log.info({ action: 'deploy' })` or `log.info('tag', 'message')` +2. **Emit** — The event is built and printed immediately +3. **Drain** — If a global `drain` was configured via `initLogger()`, the event is sent to external services + +Tagged logs (`log.info('tag', 'message')`) are console-only in pretty mode. Object-form logs (`log.info({ ... })`) always flow through the drain pipeline. + +## Standalone Wide Event Pipeline + +When using `createLogger()` outside a framework: + +1. **Create** — `createLogger({ jobId: 'sync-001' })` +2. **Accumulate** — `log.set()`, `log.info()`, `log.warn()`, `log.error()` over the operation +3. **Emit** — Manual `log.emit()` call +4. **Sample** — Head sampling applies based on computed level. Tail sampling via `initLogger({ sampling: { keep: [...] } })` +5. **Drain** — If a global `drain` was configured, the event is sent + +```typescript [scripts/migrate.ts] +import { initLogger, createLogger } from 'evlog' +import { createAxiomDrain } from 'evlog/axiom' + +initLogger({ + env: { service: 'worker' }, + drain: createAxiomDrain(), + sampling: { rates: { info: 10 } }, +}) + +const log = createLogger({ task: 'migrate' }) +log.set({ records: 500, status: 'complete' }) +log.emit() +``` + ## Next Steps -- [Wide Events](/core-concepts/wide-events) - Design effective wide events +- [Wide Events](/logging/wide-events) - Design effective wide events - [Sampling](/core-concepts/sampling) - Configure head and tail sampling - [Adapters](/adapters/overview) - Send events to external platforms - [Enrichers](/enrichers/overview) - Add derived context automatically diff --git a/apps/docs/content/3.core-concepts/7.configuration.md b/apps/docs/content/3.core-concepts/1.configuration.md similarity index 96% rename from apps/docs/content/3.core-concepts/7.configuration.md rename to apps/docs/content/3.core-concepts/1.configuration.md index b58942d0..6b906972 100644 --- a/apps/docs/content/3.core-concepts/7.configuration.md +++ b/apps/docs/content/3.core-concepts/1.configuration.md @@ -22,8 +22,9 @@ evlog has two configuration surfaces: **global options** set once at startup, an These options apply to all frameworks. Call `initLogger()` once at application startup for standalone frameworks (Hono, Express, Fastify, Elysia, NestJS, SvelteKit, Cloudflare Workers). For Nuxt and Nitro, these are set via module config and passed through automatically. -```typescript +```typescript [src/index.ts] import { initLogger } from 'evlog' +import { createAxiomDrain } from 'evlog/axiom' initLogger({ enabled: true, @@ -62,10 +63,13 @@ The `env` option controls the fields included in every log event. Most values ar Use `silent` when your deployment platform captures stdout as its primary log ingestion (GCP Cloud Run, AWS Lambda, Fly.io, Railway, etc.) and you want a drain adapter to control the output format. -```typescript +```typescript [src/index.ts] +import { initLogger } from 'evlog' +import { createAxiomDrain } from 'evlog/axiom' + initLogger({ silent: process.env.NODE_ENV === 'production', - drain: createCloudLoggingDrain(), + drain: createAxiomDrain(), }) ``` diff --git a/apps/docs/content/3.core-concepts/5.sampling.md b/apps/docs/content/3.core-concepts/2.sampling.md similarity index 94% rename from apps/docs/content/3.core-concepts/5.sampling.md rename to apps/docs/content/3.core-concepts/2.sampling.md index 66dfa25a..2dcf11d7 100644 --- a/apps/docs/content/3.core-concepts/5.sampling.md +++ b/apps/docs/content/3.core-concepts/2.sampling.md @@ -11,7 +11,7 @@ links: variant: subtle - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle --- @@ -80,15 +80,17 @@ Head sampling is random. A `10%` rate means roughly 1 in 10 info logs are kept, Head sampling is blind: it doesn't know if a request was slow, failed, or hit a critical path. Tail sampling fixes this by evaluating **after** the request completes and force-keeping logs that match specific conditions. -```typescript -// Works the same across all frameworks -sampling: { - rates: { info: 10 }, - keep: [ - { status: 400 }, // HTTP status >= 400 - { duration: 1000 }, // Request took >= 1s - { path: '/api/payments/**' }, // Critical path (glob) - ], +```typescript [nuxt.config.ts] +// Sampling config — works the same across all frameworks +evlog: { + sampling: { + rates: { info: 10 }, + keep: [ + { status: 400 }, // HTTP status >= 400 + { duration: 1000 }, // Request took >= 1s + { path: '/api/payments/**' }, // Critical path (glob) + ], + }, } ``` @@ -267,4 +269,4 @@ In Nuxt, use the `$production` override to keep full logging in development whil ## Next Steps - [Best Practices](/core-concepts/best-practices) - Security and production checklist -- [Wide Events](/core-concepts/wide-events) - Design effective wide events +- [Wide Events](/logging/wide-events) - Design effective wide events diff --git a/apps/docs/content/3.core-concepts/4.typed-fields.md b/apps/docs/content/3.core-concepts/3.typed-fields.md similarity index 90% rename from apps/docs/content/3.core-concepts/4.typed-fields.md rename to apps/docs/content/3.core-concepts/3.typed-fields.md index 0813fdf4..03a597c0 100644 --- a/apps/docs/content/3.core-concepts/4.typed-fields.md +++ b/apps/docs/content/3.core-concepts/3.typed-fields.md @@ -6,7 +6,7 @@ navigation: links: - label: Wide Events icon: i-lucide-layers - to: /core-concepts/wide-events + to: /logging/wide-events color: neutral variant: subtle - label: Best Practices @@ -51,7 +51,7 @@ TypeScript catches typos and unknown fields at compile time, before they reach p evlog sets some fields internally (`status`, `service`). These are always accepted regardless of your type, through the `InternalFields` type: -```typescript +```typescript [server/api/checkout.post.ts] log.set({ status: 200 }) // OK - internal field log.set({ service: 'api' }) // OK - internal field ``` @@ -62,7 +62,7 @@ You don't need to include `status` or `service` in your interface. Without a generic, `useLogger` accepts any fields as usual: -```typescript +```typescript [server/api/example.ts] const log = useLogger(event) log.set({ anything: true, nested: { deep: 'value' } }) // OK ``` @@ -155,7 +155,7 @@ export default defineEventHandler(async (event) => { Include only the fields your routes actually set. The interface doesn't need to mirror your entire data model: -```typescript +```typescript [server/types/evlog.ts] // Too broad - most routes won't set all these interface EverythingFields { user: FullUserProfile @@ -170,3 +170,9 @@ interface CheckoutFields { cart: { items: number; total: number } } ``` + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design effective wide events with context layering +- [Best Practices](/core-concepts/best-practices) — Security guidelines for preventing sensitive data leakage +- [Configuration](/core-concepts/configuration) — All `initLogger` and middleware options diff --git a/apps/docs/content/3.core-concepts/3.best-practices.md b/apps/docs/content/3.core-concepts/4.best-practices.md similarity index 96% rename from apps/docs/content/3.core-concepts/3.best-practices.md rename to apps/docs/content/3.core-concepts/4.best-practices.md index b7abe2e8..be34bb38 100644 --- a/apps/docs/content/3.core-concepts/3.best-practices.md +++ b/apps/docs/content/3.core-concepts/4.best-practices.md @@ -36,6 +36,8 @@ Logs are often accessible to your entire team and may be stored in third-party s The safest approach is to explicitly select which fields to log: ```typescript [server/api/user/update.post.ts] +import { useLogger } from 'evlog' + export default defineEventHandler(async (event) => { const log = useLogger(event) const body = await readBody(event) @@ -96,6 +98,8 @@ export function sanitize>( Usage: ```typescript [server/api/checkout.post.ts] +import { useLogger } from 'evlog' + export default defineEventHandler(async (event) => { const log = useLogger(event) const { user, card } = await readBody(event) @@ -239,5 +243,5 @@ Use `$production` override to keep full logging in development while sampling in ## Next Steps -- [Wide Events](/core-concepts/wide-events) - Design effective wide events -- [Structured Errors](/core-concepts/structured-errors) - Error handling patterns +- [Wide Events](/logging/wide-events) - Design effective wide events +- [Structured Errors](/logging/structured-errors) - Error handling patterns diff --git a/apps/docs/content/3.core-concepts/8.performance.md b/apps/docs/content/3.core-concepts/5.performance.md similarity index 100% rename from apps/docs/content/3.core-concepts/8.performance.md rename to apps/docs/content/3.core-concepts/5.performance.md diff --git a/apps/docs/content/3.core-concepts/10.vite-plugin.md b/apps/docs/content/3.core-concepts/6.vite-plugin.md similarity index 96% rename from apps/docs/content/3.core-concepts/10.vite-plugin.md rename to apps/docs/content/3.core-concepts/6.vite-plugin.md index 7d125ece..f954ff04 100644 --- a/apps/docs/content/3.core-concepts/10.vite-plugin.md +++ b/apps/docs/content/3.core-concepts/6.vite-plugin.md @@ -6,7 +6,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/packages/evlog/src/vite + to: https://github.com/hugorcd/evlog/tree/main/packages/evlog/src/vite color: neutral variant: subtle --- @@ -21,7 +21,7 @@ The `evlog/vite` plugin adds build-time DX features to any Vite-based project. I ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -51,7 +51,7 @@ That's it. The plugin automatically: The plugin injects logger configuration at compile time via Vite's `define` hook. Your code can use `log`, `createLogger()`, and `createRequestLogger()` immediately — no `initLogger()` call required. -```typescript +```typescript [logger-setup.ts] // Before (manual setup) import { initLogger, createLogger } from 'evlog' initLogger({ env: { service: 'my-api' } }) @@ -97,12 +97,12 @@ evlog({ ``` Before transform: -```typescript +```typescript [src/checkout.ts] log.info({ action: 'checkout', total: 99 }) ``` After transform: -```typescript +```typescript [src/checkout.ts] log.info({ action: 'checkout', total: 99, __source: 'src/checkout.ts:42' }) ``` diff --git a/apps/docs/content/2.frameworks/.navigation.yml b/apps/docs/content/4.frameworks/.navigation.yml similarity index 100% rename from apps/docs/content/2.frameworks/.navigation.yml rename to apps/docs/content/4.frameworks/.navigation.yml diff --git a/apps/docs/content/2.frameworks/00.overview.md b/apps/docs/content/4.frameworks/00.overview.md similarity index 94% rename from apps/docs/content/2.frameworks/00.overview.md rename to apps/docs/content/4.frameworks/00.overview.md index b1b4c078..c38d0c42 100644 --- a/apps/docs/content/2.frameworks/00.overview.md +++ b/apps/docs/content/4.frameworks/00.overview.md @@ -166,7 +166,7 @@ evlog provides native integrations for every major TypeScript framework. The sam :: ::callout{icon="i-lucide-info" color="info"} -All frameworks support the same features: [wide events](/core-concepts/wide-events), [structured errors](/core-concepts/structured-errors), [drain adapters](/adapters/overview), [enrichers](/enrichers/overview), [sampling](/core-concepts/best-practices), and [AI SDK integration](/core-concepts/ai-sdk). +All frameworks support the same features: [wide events](/logging/wide-events), [structured errors](/logging/structured-errors), [drain adapters](/adapters/overview), [enrichers](/enrichers/overview), [sampling](/core-concepts/sampling), and [AI SDK integration](/logging/ai-sdk). :: ## Vite Plugin diff --git a/apps/docs/content/2.frameworks/01.nuxt.md b/apps/docs/content/4.frameworks/01.nuxt.md similarity index 96% rename from apps/docs/content/2.frameworks/01.nuxt.md rename to apps/docs/content/4.frameworks/01.nuxt.md index e661b98a..0f4cb7cf 100644 --- a/apps/docs/content/2.frameworks/01.nuxt.md +++ b/apps/docs/content/4.frameworks/01.nuxt.md @@ -374,3 +374,12 @@ export default defineNuxtConfig({ }, }) ``` + +## Next Steps + +Deepen your **Nuxt** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/02.nextjs.md b/apps/docs/content/4.frameworks/02.nextjs.md similarity index 96% rename from apps/docs/content/2.frameworks/02.nextjs.md rename to apps/docs/content/4.frameworks/02.nextjs.md index e4815941..7ac01c25 100644 --- a/apps/docs/content/2.frameworks/02.nextjs.md +++ b/apps/docs/content/4.frameworks/02.nextjs.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/nextjs + to: https://github.com/hugorcd/evlog/tree/main/examples/nextjs color: neutral variant: subtle --- @@ -37,7 +37,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -485,7 +485,7 @@ export function Dashboard({ user }: { user: { id: string } }) { For advanced use cases, send structured `DrainContext` events directly from the browser to a custom endpoint: -```typescript +```typescript [lib/browser-drain.ts] import { createBrowserLogDrain } from 'evlog/browser' const drain = createBrowserLogDrain({ @@ -509,8 +509,8 @@ export async function POST(request: Request) { ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog/examples/nextjs bun install bun run dev @@ -523,8 +523,17 @@ Open [http://localhost:3000](http://localhost:3000) to explore the example. --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/nextjs + to: https://github.com/hugorcd/evlog/tree/main/examples/nextjs --- Browse the complete Next.js example source on GitHub. ::: :: + +## Next Steps + +Deepen your **Next.js** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/03.sveltekit.md b/apps/docs/content/4.frameworks/03.sveltekit.md similarity index 91% rename from apps/docs/content/2.frameworks/03.sveltekit.md rename to apps/docs/content/4.frameworks/03.sveltekit.md index b9a44a98..b3a6482f 100644 --- a/apps/docs/content/2.frameworks/03.sveltekit.md +++ b/apps/docs/content/4.frameworks/03.sveltekit.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/sveltekit + to: https://github.com/hugorcd/evlog/tree/main/examples/sveltekit color: neutral variant: subtle --- @@ -36,7 +36,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -253,8 +253,8 @@ export const { handle, handleError } = createEvlogHooks({ ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:sveltekit @@ -267,8 +267,17 @@ Open [http://localhost:5173](http://localhost:5173) to explore the interactive t --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/sveltekit + to: https://github.com/hugorcd/evlog/tree/main/examples/sveltekit --- Browse the complete SvelteKit example source on GitHub. ::: :: + +## Next Steps + +Deepen your **SvelteKit** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/04.nitro.md b/apps/docs/content/4.frameworks/04.nitro.md similarity index 95% rename from apps/docs/content/2.frameworks/04.nitro.md rename to apps/docs/content/4.frameworks/04.nitro.md index 9ca4f4a4..6b9ab23c 100644 --- a/apps/docs/content/2.frameworks/04.nitro.md +++ b/apps/docs/content/4.frameworks/04.nitro.md @@ -317,3 +317,12 @@ export default defineNitroPlugin((nitroApp) => { ::callout{icon="i-lucide-info" color="info"} Errors are always kept by default. You have to explicitly set `error: 0` to drop them. :: + +## Next Steps + +Deepen your **Nitro** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/05.tanstack-start.md b/apps/docs/content/4.frameworks/05.tanstack-start.md similarity index 92% rename from apps/docs/content/2.frameworks/05.tanstack-start.md rename to apps/docs/content/4.frameworks/05.tanstack-start.md index 054b29d7..475e9dd7 100644 --- a/apps/docs/content/2.frameworks/05.tanstack-start.md +++ b/apps/docs/content/4.frameworks/05.tanstack-start.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/tanstack-start + to: https://github.com/hugorcd/evlog/tree/main/examples/tanstack-start color: neutral variant: subtle --- @@ -15,7 +15,7 @@ links: TanStack Start uses [Nitro v3](/frameworks/nitro) as its server layer, so evlog integrates via the `evlog/nitro/v3` module. The same plugin-based hooks system applies. ::callout{icon="i-lucide-info" color="info"} -**TanStack Router vs TanStack Start** — TanStack Router is a client-side router and doesn't need server-side logging. This page covers **TanStack Start**, the full-stack framework. If you're using TanStack Router in SPA mode, see [Client Logging](/core-concepts/client-logging) instead. +**TanStack Router vs TanStack Start** — TanStack Router is a client-side router and doesn't need server-side logging. This page covers **TanStack Start**, the full-stack framework. If you're using TanStack Router in SPA mode, see [Client Logging](/logging/client-logging) instead. :: ::code-collapse @@ -42,7 +42,7 @@ Starting from a TanStack Start project created with `npm create @tanstack/start@ ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -186,7 +186,7 @@ The error is captured and logged with both the custom context and structured err Use `parseError` to extract the structured fields from any error response: -```tsx +```tsx [src/routes/checkout.tsx] import { parseError } from 'evlog' try { @@ -302,8 +302,8 @@ export default definePlugin((nitroApp) => { ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog/examples/tanstack-start bun install bun run dev @@ -316,8 +316,17 @@ Open [http://localhost:3000](http://localhost:3000) and navigate to the evlog De --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/tanstack-start + to: https://github.com/hugorcd/evlog/tree/main/examples/tanstack-start --- Browse the complete TanStack Start example source on GitHub. ::: :: + +## Next Steps + +Deepen your **TanStack Start** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/06.nestjs.md b/apps/docs/content/4.frameworks/06.nestjs.md similarity index 93% rename from apps/docs/content/2.frameworks/06.nestjs.md rename to apps/docs/content/4.frameworks/06.nestjs.md index ad83b07b..280d5f93 100644 --- a/apps/docs/content/2.frameworks/06.nestjs.md +++ b/apps/docs/content/4.frameworks/06.nestjs.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/nestjs + to: https://github.com/hugorcd/evlog/tree/main/examples/nestjs color: neutral variant: subtle --- @@ -36,7 +36,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog @nestjs/common @nestjs/core @nestjs/platform-express ``` @@ -307,8 +307,8 @@ EvlogModule.forRoot({ ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:nestjs @@ -321,8 +321,17 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/nestjs + to: https://github.com/hugorcd/evlog/tree/main/examples/nestjs --- Browse the complete NestJS example source on GitHub. ::: :: + +## Next Steps + +Deepen your **NestJS** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/07.express.md b/apps/docs/content/4.frameworks/07.express.md similarity index 92% rename from apps/docs/content/2.frameworks/07.express.md rename to apps/docs/content/4.frameworks/07.express.md index 91a447ce..75640587 100644 --- a/apps/docs/content/2.frameworks/07.express.md +++ b/apps/docs/content/4.frameworks/07.express.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/express + to: https://github.com/hugorcd/evlog/tree/main/examples/express color: neutral variant: subtle --- @@ -37,7 +37,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog express ``` @@ -163,7 +163,6 @@ The error is captured and logged with both the custom context and structured err ```bash [Terminal output] 14:58:20 ERROR [my-api] GET /checkout 402 in 3ms ├─ error: name=EvlogError message=Payment failed status=402 - ├─ cart: items=3 total=9999 └─ requestId: 880a50ac-... ``` @@ -280,8 +279,8 @@ See the full [Browser Drain](/adapters/browser) adapter docs for batching, retry ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:express @@ -294,8 +293,17 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/express + to: https://github.com/hugorcd/evlog/tree/main/examples/express --- Browse the complete Express example source on GitHub. ::: :: + +## Next Steps + +Deepen your **Express** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/08.hono.md b/apps/docs/content/4.frameworks/08.hono.md similarity index 92% rename from apps/docs/content/2.frameworks/08.hono.md rename to apps/docs/content/4.frameworks/08.hono.md index 441841dc..0c747974 100644 --- a/apps/docs/content/2.frameworks/08.hono.md +++ b/apps/docs/content/4.frameworks/08.hono.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/hono + to: https://github.com/hugorcd/evlog/tree/main/examples/hono color: neutral variant: subtle --- @@ -38,7 +38,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog hono @hono/node-server ``` @@ -268,8 +268,8 @@ See the full [Browser Drain](/adapters/browser) adapter docs for batching, retry ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:hono @@ -282,8 +282,17 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/hono + to: https://github.com/hugorcd/evlog/tree/main/examples/hono --- Browse the complete Hono example source on GitHub. ::: :: + +## Next Steps + +Deepen your **Hono** integration: + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/09.fastify.md b/apps/docs/content/4.frameworks/09.fastify.md similarity index 92% rename from apps/docs/content/2.frameworks/09.fastify.md rename to apps/docs/content/4.frameworks/09.fastify.md index 98e398e1..795acb80 100644 --- a/apps/docs/content/2.frameworks/09.fastify.md +++ b/apps/docs/content/4.frameworks/09.fastify.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/fastify + to: https://github.com/hugorcd/evlog/tree/main/examples/fastify color: neutral variant: subtle --- @@ -37,7 +37,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog fastify ``` @@ -240,8 +240,8 @@ await app.register(evlog, { ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:fastify @@ -254,8 +254,15 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/fastify + to: https://github.com/hugorcd/evlog/tree/main/examples/fastify --- Browse the complete Fastify example source on GitHub. ::: :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/10.elysia.md b/apps/docs/content/4.frameworks/10.elysia.md similarity index 92% rename from apps/docs/content/2.frameworks/10.elysia.md rename to apps/docs/content/4.frameworks/10.elysia.md index 18486227..93a519ec 100644 --- a/apps/docs/content/2.frameworks/10.elysia.md +++ b/apps/docs/content/4.frameworks/10.elysia.md @@ -7,7 +7,7 @@ navigation: links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/elysia + to: https://github.com/hugorcd/evlog/tree/main/examples/elysia color: neutral variant: subtle --- @@ -37,7 +37,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog elysia ``` @@ -279,8 +279,8 @@ See the full [Browser Drain](/adapters/browser) adapter docs for batching, retry ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:elysia @@ -293,8 +293,15 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t --- icon: i-custom-elysia title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/elysia + to: https://github.com/hugorcd/evlog/tree/main/examples/elysia --- Browse the complete Elysia example source on GitHub. ::: :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/11.react-router.md b/apps/docs/content/4.frameworks/11.react-router.md similarity index 93% rename from apps/docs/content/2.frameworks/11.react-router.md rename to apps/docs/content/4.frameworks/11.react-router.md index dfcd2069..c285ce05 100644 --- a/apps/docs/content/2.frameworks/11.react-router.md +++ b/apps/docs/content/4.frameworks/11.react-router.md @@ -4,7 +4,7 @@ description: Using evlog with React Router — automatic wide events, structured links: - label: Source Code icon: i-simple-icons-github - to: https://github.com/HugoRCD/evlog/tree/main/examples/react-router + to: https://github.com/hugorcd/evlog/tree/main/examples/react-router color: neutral variant: subtle navigation: @@ -41,7 +41,7 @@ Adapters: https://www.evlog.dev/adapters/overview ### 1. Install -```bash +```bash [Terminal] bun add evlog react-router @react-router/node @react-router/serve ``` @@ -282,8 +282,8 @@ export const middleware: Route.MiddlewareFunction[] = [ ## Run Locally -```bash -git clone https://github.com/HugoRCD/evlog.git +```bash [Terminal] +git clone https://github.com/hugorcd/evlog.git cd evlog bun install bun run example:react-router @@ -296,8 +296,15 @@ Open to explore the interactive test UI. --- icon: i-simple-icons-github title: Source Code - to: https://github.com/HugoRCD/evlog/tree/main/examples/react-router + to: https://github.com/hugorcd/evlog/tree/main/examples/react-router --- Browse the complete React Router example source on GitHub. ::: :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/12.cloudflare-workers.md b/apps/docs/content/4.frameworks/12.cloudflare-workers.md similarity index 92% rename from apps/docs/content/2.frameworks/12.cloudflare-workers.md rename to apps/docs/content/4.frameworks/12.cloudflare-workers.md index d1d4d1c3..56cf1adf 100644 --- a/apps/docs/content/2.frameworks/12.cloudflare-workers.md +++ b/apps/docs/content/4.frameworks/12.cloudflare-workers.md @@ -30,7 +30,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -181,6 +181,13 @@ enabled = false ## Run Locally -```bash +```bash [Terminal] wrangler dev ``` + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/13.standalone.md b/apps/docs/content/4.frameworks/13.standalone.md similarity index 89% rename from apps/docs/content/2.frameworks/13.standalone.md rename to apps/docs/content/4.frameworks/13.standalone.md index 6a8392d4..5b202fe7 100644 --- a/apps/docs/content/2.frameworks/13.standalone.md +++ b/apps/docs/content/4.frameworks/13.standalone.md @@ -30,7 +30,7 @@ Adapters: https://www.evlog.dev/adapters ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -75,7 +75,7 @@ evlog provides two manual logger constructors: **`createLogger(context)`** - For non-HTTP contexts (scripts, CLI, queues): -```typescript +```typescript [scripts/job.ts] import { createLogger } from 'evlog' const log = createLogger({ jobId: 'migrate-001', source: 'postgres' }) @@ -85,7 +85,7 @@ log.emit() **`createRequestLogger(requestMeta)`** - For HTTP-like contexts where you want method/path/status tracking: -```typescript +```typescript [scripts/webhook-handler.ts] import { createRequestLogger } from 'evlog' const log = createRequestLogger({ @@ -166,7 +166,7 @@ See the [Configuration reference](/core-concepts/configuration) for all availabl Configure drain in `initLogger`: -```typescript +```typescript [scripts/init-logger.ts] import type { DrainContext } from 'evlog' import { initLogger } from 'evlog' import { createAxiomDrain } from 'evlog/axiom' @@ -189,5 +189,12 @@ See the [Adapters](/adapters/overview) docs for all available drain adapters (Ax :: ::callout{icon="i-lucide-arrow-right" color="neutral"} -See the full [bun-script example](https://github.com/HugoRCD/evlog/tree/main/examples/bun-script) for a complete working script. +See the full [bun-script example](https://github.com/hugorcd/evlog/tree/main/examples/bun-script) for a complete working script. :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/14.astro.md b/apps/docs/content/4.frameworks/14.astro.md similarity index 91% rename from apps/docs/content/2.frameworks/14.astro.md rename to apps/docs/content/4.frameworks/14.astro.md index e8ef7e64..cfc50135 100644 --- a/apps/docs/content/2.frameworks/14.astro.md +++ b/apps/docs/content/4.frameworks/14.astro.md @@ -34,7 +34,7 @@ This is a guide-level integration. It uses the generic `createRequestLogger` API ### 1. Install -```bash +```bash [Terminal] bun add evlog ``` @@ -165,3 +165,10 @@ initLogger({ ::callout{icon="i-lucide-info" color="info"} See the [Adapters](/adapters/overview) docs for all available drain adapters. :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/2.frameworks/15.custom-integration.md b/apps/docs/content/4.frameworks/15.custom-integration.md similarity index 90% rename from apps/docs/content/2.frameworks/15.custom-integration.md rename to apps/docs/content/4.frameworks/15.custom-integration.md index 30c607ba..d9f27421 100644 --- a/apps/docs/content/2.frameworks/15.custom-integration.md +++ b/apps/docs/content/4.frameworks/15.custom-integration.md @@ -125,7 +125,7 @@ That's it. This middleware gets **every feature** for free: route filtering, dra Once built, your integration is used like any other: -```typescript +```typescript [src/index.ts] import { initLogger } from 'evlog' import { evlog, useLogger } from './my-framework-evlog' import { createAxiomDrain } from 'evlog/axiom' @@ -161,11 +161,18 @@ Study these built-in integrations for framework-specific patterns: | Framework | Lines | Pattern | Source | |-----------|-------|---------|--------| -| Hono | ~40 | Web API Headers, `c.set()`, try/catch | [hono/index.ts](https://github.com/HugoRCD/evlog/blob/main/packages/evlog/src/hono/index.ts) | -| Express | ~60 | Node.js headers, `req.log`, `res.on('finish')` | [express/index.ts](https://github.com/HugoRCD/evlog/blob/main/packages/evlog/src/express/index.ts) | -| Elysia | ~70 | Plugin API, `derive()`, `onAfterHandle`/`onError` | [elysia/index.ts](https://github.com/HugoRCD/evlog/blob/main/packages/evlog/src/elysia/index.ts) | -| Fastify | ~70 | Plugin, `decorateRequest`, `onRequest`/`onResponse` hooks | [fastify/index.ts](https://github.com/HugoRCD/evlog/blob/main/packages/evlog/src/fastify/index.ts) | +| Hono | ~40 | Web API Headers, `c.set()`, try/catch | [hono/index.ts](https://github.com/hugorcd/evlog/blob/main/packages/evlog/src/hono/index.ts) | +| Express | ~60 | Node.js headers, `req.log`, `res.on('finish')` | [express/index.ts](https://github.com/hugorcd/evlog/blob/main/packages/evlog/src/express/index.ts) | +| Elysia | ~70 | Plugin API, `derive()`, `onAfterHandle`/`onError` | [elysia/index.ts](https://github.com/hugorcd/evlog/blob/main/packages/evlog/src/elysia/index.ts) | +| Fastify | ~70 | Plugin, `decorateRequest`, `onRequest`/`onResponse` hooks | [fastify/index.ts](https://github.com/hugorcd/evlog/blob/main/packages/evlog/src/fastify/index.ts) | ::callout{icon="i-lucide-heart" color="neutral"} -Built an integration for a framework we don't support? [Open a PR](https://github.com/HugoRCD/evlog/pulls) - the community will thank you. +Built an integration for a framework we don't support? [Open a PR](https://github.com/hugorcd/evlog/pulls) - the community will thank you. :: + +## Next Steps + +- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering +- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.adapters/.navigation.yml b/apps/docs/content/5.adapters/.navigation.yml similarity index 100% rename from apps/docs/content/4.adapters/.navigation.yml rename to apps/docs/content/5.adapters/.navigation.yml diff --git a/apps/docs/content/4.adapters/1.overview.md b/apps/docs/content/5.adapters/1.overview.md similarity index 98% rename from apps/docs/content/4.adapters/1.overview.md rename to apps/docs/content/5.adapters/1.overview.md index 1e084037..a1ece2a5 100644 --- a/apps/docs/content/4.adapters/1.overview.md +++ b/apps/docs/content/5.adapters/1.overview.md @@ -217,14 +217,14 @@ await drain.flush() ``` ::callout{icon="i-lucide-arrow-right" color="neutral"} -See the full [bun-script example](https://github.com/HugoRCD/evlog/tree/main/examples/bun-script) for a realistic batch processing script. +See the full [bun-script example](https://github.com/hugorcd/evlog/tree/main/examples/bun-script) for a realistic batch processing script. :: ## Multiple Destinations Send logs to multiple services simultaneously by composing drains: -```typescript +```typescript [src/index.ts] import { createAxiomDrain } from 'evlog/axiom' import { createOTLPDrain } from 'evlog/otlp' diff --git a/apps/docs/content/4.adapters/10.pipeline.md b/apps/docs/content/5.adapters/10.pipeline.md similarity index 99% rename from apps/docs/content/4.adapters/10.pipeline.md rename to apps/docs/content/5.adapters/10.pipeline.md index 4b9c9099..68ec8641 100644 --- a/apps/docs/content/4.adapters/10.pipeline.md +++ b/apps/docs/content/5.adapters/10.pipeline.md @@ -182,7 +182,7 @@ await drain.flush() ``` ::callout{icon="i-lucide-arrow-right" color="neutral"} -See the full [bun-script example](https://github.com/HugoRCD/evlog/tree/main/examples/bun-script) for a complete working script. +See the full [bun-script example](https://github.com/hugorcd/evlog/tree/main/examples/bun-script) for a complete working script. :: ::callout{icon="i-lucide-code" color="neutral"} diff --git a/apps/docs/content/4.adapters/11.browser.md b/apps/docs/content/5.adapters/11.browser.md similarity index 98% rename from apps/docs/content/4.adapters/11.browser.md rename to apps/docs/content/5.adapters/11.browser.md index ecce318b..9fc81e53 100644 --- a/apps/docs/content/4.adapters/11.browser.md +++ b/apps/docs/content/5.adapters/11.browser.md @@ -47,7 +47,7 @@ log.info({ action: 'page_view', path: location.pathname }) High-level, pre-composed: creates a pipeline with batching, retry, and auto-flush on `visibilitychange`. Returns a `PipelineDrainFn` directly usable with `initLogger({ drain })`. -```typescript +```typescript [app.ts] import { initLogger, log } from 'evlog' import { createBrowserLogDrain } from 'evlog/browser' @@ -64,7 +64,7 @@ log.info({ action: 'click', target: 'buy-button' }) Low-level transport function. Use this when you want full control over the pipeline configuration: -```typescript +```typescript [app.ts] import { createBrowserDrain } from 'evlog/browser' import { createDrainPipeline } from 'evlog/pipeline' import type { DrainContext } from 'evlog' @@ -112,7 +112,7 @@ When `useBeacon` is enabled (the default) and the page becomes hidden, the drain Pass custom headers to protect your ingest endpoint: -```typescript +```typescript [app.ts] const drain = createBrowserLogDrain({ drain: { endpoint: 'https://logs.example.com/v1/ingest', @@ -187,7 +187,7 @@ window.addEventListener('beforeunload', () => drain.flush()) ``` ::callout{icon="i-lucide-arrow-right" color="neutral"} -See the full [browser example](https://github.com/HugoRCD/evlog/tree/main/examples/browser) for a working Hono server + browser page that demonstrates the complete flow end to end. +See the full [browser example](https://github.com/hugorcd/evlog/tree/main/examples/browser) for a working Hono server + browser page that demonstrates the complete flow end to end. :: ::callout{icon="i-lucide-code" color="neutral"} diff --git a/apps/docs/content/4.adapters/2.axiom.md b/apps/docs/content/5.adapters/2.axiom.md similarity index 98% rename from apps/docs/content/4.adapters/2.axiom.md rename to apps/docs/content/5.adapters/2.axiom.md index cadf713c..c778ece7 100644 --- a/apps/docs/content/4.adapters/2.axiom.md +++ b/apps/docs/content/5.adapters/2.axiom.md @@ -42,7 +42,7 @@ Framework setup: https://www.evlog.dev/frameworks The Axiom adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createAxiomDrain } from 'evlog/axiom' ``` @@ -147,7 +147,7 @@ export default defineNuxtConfig({ Pass options directly to override any configuration: -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createAxiomDrain({ dataset: 'production-logs', timeout: 10000, @@ -169,7 +169,7 @@ const drain = createAxiomDrain({ evlog sends structured wide events that are perfect for Axiom's APL query language: -```apl +```apl [Axiom APL queries] // Find slow requests ['your-dataset'] | where duration > 1000 @@ -191,7 +191,7 @@ evlog sends structured wide events that are perfect for Axiom's APL query langua ### Missing dataset or token error -``` +```text [Console] [evlog/axiom] Missing dataset or token. Set AXIOM_DATASET and AXIOM_TOKEN ``` diff --git a/apps/docs/content/4.adapters/3.otlp.md b/apps/docs/content/5.adapters/3.otlp.md similarity index 98% rename from apps/docs/content/4.adapters/3.otlp.md rename to apps/docs/content/5.adapters/3.otlp.md index 84fdc4d5..b1d5c19d 100644 --- a/apps/docs/content/4.adapters/3.otlp.md +++ b/apps/docs/content/5.adapters/3.otlp.md @@ -52,7 +52,7 @@ Framework setup: https://www.evlog.dev/frameworks The OTLP adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createOTLPDrain } from 'evlog/otlp' ``` @@ -146,7 +146,7 @@ export default defineNuxtConfig({ ### Override Options -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createOTLPDrain({ endpoint: 'http://localhost:4318', serviceName: 'my-api', @@ -215,7 +215,7 @@ service: exporters: [debug] ``` -```bash +```bash [Terminal] docker run --rm -p 4318:4318 \ -v $(pwd)/otel-collector.yaml:/etc/otelcol/config.yaml \ otel/opentelemetry-collector:latest @@ -254,7 +254,7 @@ evlog maps wide events to the OTLP log format: ### Missing endpoint error -``` +```text [Console] [evlog/otlp] Missing endpoint. Set OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT ``` diff --git a/apps/docs/content/4.adapters/4.posthog.md b/apps/docs/content/5.adapters/4.posthog.md similarity index 98% rename from apps/docs/content/4.adapters/4.posthog.md rename to apps/docs/content/5.adapters/4.posthog.md index 1f4fafe5..515fa45f 100644 --- a/apps/docs/content/4.adapters/4.posthog.md +++ b/apps/docs/content/5.adapters/4.posthog.md @@ -43,7 +43,7 @@ Framework setup: https://www.evlog.dev/frameworks The PostHog adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createPostHogDrain } from 'evlog/posthog' ``` @@ -140,7 +140,7 @@ export default defineNuxtConfig({ Pass options directly to override any configuration: -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createPostHogDrain({ host: 'https://eu.i.posthog.com', timeout: 10000, @@ -191,7 +191,7 @@ Once your logs are flowing, use the **Logs** tab in PostHog to query them: If you prefer sending logs as PostHog custom events (e.g., for product analytics, cohorts, or funnels), use `createPostHogEventsDrain()`: -```typescript +```typescript [server/plugins/evlog-drain.ts] import { createPostHogEventsDrain } from 'evlog/posthog' const drain = createPostHogEventsDrain({ @@ -248,7 +248,7 @@ The `distinct_id` follows a fallback chain: You can use both drains simultaneously to get the best of both worlds: -```typescript +```typescript [server/plugins/evlog-drain.ts] import { createPostHogDrain, createPostHogEventsDrain } from 'evlog/posthog' const logs = createPostHogDrain() @@ -263,7 +263,7 @@ const drain = async (ctx) => { ### Missing apiKey error -``` +```text [Console] [evlog/posthog] Missing apiKey. Set POSTHOG_API_KEY env var or pass to createPostHogDrain() ``` diff --git a/apps/docs/content/4.adapters/5.sentry.md b/apps/docs/content/5.adapters/5.sentry.md similarity index 98% rename from apps/docs/content/4.adapters/5.sentry.md rename to apps/docs/content/5.adapters/5.sentry.md index f8add261..b1657693 100644 --- a/apps/docs/content/4.adapters/5.sentry.md +++ b/apps/docs/content/5.adapters/5.sentry.md @@ -42,7 +42,7 @@ Framework setup: https://www.evlog.dev/frameworks The Sentry adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createSentryDrain } from 'evlog/sentry' ``` @@ -142,7 +142,7 @@ export default defineNuxtConfig({ Pass options directly to override any configuration: -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createSentryDrain({ dsn: 'https://key@o0.ingest.sentry.io/123', tags: { team: 'backend' }, @@ -187,7 +187,7 @@ Sentry Structured Logs support high-cardinality attributes, making them a great ### Missing DSN error -``` +```text [Console] [evlog/sentry] Missing DSN. Set SENTRY_DSN env var or pass to createSentryDrain() ``` @@ -197,7 +197,7 @@ Make sure your environment variable is set and the server was restarted after ad If the DSN is malformed (missing public key or project ID), the adapter will throw an error. Verify your DSN format: -``` +```text [Sentry DSN format] https://@/ ``` diff --git a/apps/docs/content/4.adapters/6.better-stack.md b/apps/docs/content/5.adapters/6.better-stack.md similarity index 98% rename from apps/docs/content/4.adapters/6.better-stack.md rename to apps/docs/content/5.adapters/6.better-stack.md index dd68d59a..3a275dda 100644 --- a/apps/docs/content/4.adapters/6.better-stack.md +++ b/apps/docs/content/5.adapters/6.better-stack.md @@ -42,7 +42,7 @@ Framework setup: https://www.evlog.dev/frameworks The Better Stack adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createBetterStackDrain } from 'evlog/better-stack' ``` @@ -138,7 +138,7 @@ export default defineNuxtConfig({ Pass options directly to override any configuration: -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createBetterStackDrain({ sourceToken: 'my-token', timeout: 10000, @@ -176,7 +176,7 @@ Better Stack provides a powerful log search interface: ### Missing source token error -``` +```text [Console] [evlog/better-stack] Missing source token. Set BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain() ``` diff --git a/apps/docs/content/4.adapters/7.fs.md b/apps/docs/content/5.adapters/7.fs.md similarity index 95% rename from apps/docs/content/4.adapters/7.fs.md rename to apps/docs/content/5.adapters/7.fs.md index 48429559..43ac0d84 100644 --- a/apps/docs/content/4.adapters/7.fs.md +++ b/apps/docs/content/5.adapters/7.fs.md @@ -47,7 +47,7 @@ Framework setup: https://www.evlog.dev/frameworks The File System adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createFsDrain } from 'evlog/fs' ``` @@ -100,7 +100,7 @@ Logs start appearing in `.evlog/logs/` immediately. ## File Structure -``` +```text [.evlog/logs directory layout] .evlog/ logs/ 2026-03-14.jsonl ← one file per day @@ -127,7 +127,7 @@ A `.gitignore` is automatically created on first write, inside the `.evlog/` anc ### Examples -```typescript +```typescript [server/plugins/evlog-drain.ts] // Keep only the last 7 days of logs createFsDrain({ maxFiles: 7 }) @@ -148,7 +148,7 @@ createFsDrain({ dir: '/var/log/myapp' }) By default, a new file is created each day (`2026-03-14.jsonl`). When `maxSizePerFile` is set, the adapter creates suffixed files when the current file exceeds the limit: -``` +```text [Rotated log files] .evlog/logs/ 2026-03-14.jsonl ← base file (full) 2026-03-14.1.jsonl ← first rotation @@ -163,7 +163,7 @@ When `maxFiles` is set, the adapter automatically deletes the oldest `.jsonl` fi Use the FS adapter alongside a network drain for local backup: -```typescript +```typescript [server/plugins/evlog-drain.ts] import { createFsDrain } from 'evlog/fs' import { createAxiomDrain } from 'evlog/axiom' @@ -179,13 +179,13 @@ const drain = async (ctx) => { ### Stream in real-time -```bash +```bash [Terminal] tail -f .evlog/logs/2026-03-14.jsonl ``` ### Search with jq -```bash +```bash [Terminal] # Find errors cat .evlog/logs/2026-03-14.jsonl | jq 'select(.level == "error")' @@ -198,7 +198,7 @@ cat .evlog/logs/2026-03-14.jsonl | jq 'select(.path == "/api/checkout")' ### Search with grep -```bash +```bash [Terminal] # Find all errors grep '"level":"error"' .evlog/logs/2026-03-14.jsonl @@ -210,7 +210,7 @@ grep 'req_abc123' .evlog/logs/*.jsonl For advanced use cases, use the lower-level write functions: -```typescript +```typescript [src/index.ts] import { writeToFs, writeBatchToFs } from 'evlog/fs' await writeToFs(event, { diff --git a/apps/docs/content/4.adapters/8.hyperdx.md b/apps/docs/content/5.adapters/8.hyperdx.md similarity index 98% rename from apps/docs/content/4.adapters/8.hyperdx.md rename to apps/docs/content/5.adapters/8.hyperdx.md index e480dd6b..750d9847 100644 --- a/apps/docs/content/4.adapters/8.hyperdx.md +++ b/apps/docs/content/5.adapters/8.hyperdx.md @@ -42,7 +42,7 @@ Framework setup: https://www.evlog.dev/frameworks The HyperDX adapter comes bundled with evlog: -```typescript +```typescript [src/index.ts] import { createHyperDXDrain } from 'evlog/hyperdx' ``` @@ -151,7 +151,7 @@ You can also nest keys under `runtimeConfig.evlog.hyperdx`; both match how the a Pass options directly to override any configuration: -```typescript +```typescript [server/plugins/evlog-drain.ts] const drain = createHyperDXDrain({ apiKey: process.env.HYPERDX_API_KEY!, endpoint: 'https://in-otel.hyperdx.io', @@ -188,7 +188,7 @@ From [HyperDX — OpenTelemetry](https://hyperdx.io/docs/install/opentelemetry): HyperDX documents this collector configuration (HTTP and gRPC exporters): -```yaml +```yaml [OpenTelemetry HyperDX exporters] exporters: # HTTP setup otlphttp/hdx: @@ -219,7 +219,7 @@ Use the HyperDX UI to search and explore wide events: ### Missing apiKey error -``` +```text [Console] [evlog/hyperdx] Missing apiKey. Set HYPERDX_API_KEY or NUXT_HYPERDX_API_KEY, or pass to createHyperDXDrain() ``` diff --git a/apps/docs/content/4.adapters/9.custom.md b/apps/docs/content/5.adapters/9.custom.md similarity index 99% rename from apps/docs/content/4.adapters/9.custom.md rename to apps/docs/content/5.adapters/9.custom.md index 2e7b9ea6..a2ad1881 100644 --- a/apps/docs/content/4.adapters/9.custom.md +++ b/apps/docs/content/5.adapters/9.custom.md @@ -145,7 +145,7 @@ export function createMyAdapter(config: MyAdapterConfig) { Then pass the adapter to your framework like any other drain: -```typescript +```typescript [lib/my-adapter.ts] const drain = createMyAdapter({ apiKey: process.env.MY_SERVICE_API_KEY!, }) diff --git a/apps/docs/content/5.enrichers/.navigation.yml b/apps/docs/content/6.enrichers/.navigation.yml similarity index 100% rename from apps/docs/content/5.enrichers/.navigation.yml rename to apps/docs/content/6.enrichers/.navigation.yml diff --git a/apps/docs/content/5.enrichers/1.overview.md b/apps/docs/content/6.enrichers/1.overview.md similarity index 98% rename from apps/docs/content/5.enrichers/1.overview.md rename to apps/docs/content/6.enrichers/1.overview.md index 717f2908..3bb2a583 100644 --- a/apps/docs/content/5.enrichers/1.overview.md +++ b/apps/docs/content/6.enrichers/1.overview.md @@ -112,7 +112,7 @@ Every enricher receives an `EnrichContext` with: By default, enrichers preserve existing fields. If your application code already sets `event.userAgent`, the enricher won't overwrite it. Pass `{ overwrite: true }` to change this: -```typescript +```typescript [enricher-factory-options.ts] createUserAgentEnricher({ overwrite: true }) ``` diff --git a/apps/docs/content/5.enrichers/2.built-in.md b/apps/docs/content/6.enrichers/2.built-in.md similarity index 92% rename from apps/docs/content/5.enrichers/2.built-in.md rename to apps/docs/content/6.enrichers/2.built-in.md index f850d924..9c610b9b 100644 --- a/apps/docs/content/5.enrichers/2.built-in.md +++ b/apps/docs/content/6.enrichers/2.built-in.md @@ -36,7 +36,7 @@ Framework setup: https://www.evlog.dev/frameworks :: -```typescript +```typescript [server/plugins/evlog-enrich.ts] import { createUserAgentEnricher, createGeoEnricher, @@ -51,13 +51,13 @@ Parse browser, OS, and device type from the `User-Agent` header. **Sets:** `event.userAgent` -```typescript +```typescript [user-agent-enricher.ts] const enrich = createUserAgentEnricher() ``` **Output shape:** -```typescript +```typescript [user-agent-types.ts] interface UserAgentInfo { raw: string // Original User-Agent string browser?: { name: string; version?: string } // Chrome, Firefox, Safari, Edge @@ -68,7 +68,7 @@ interface UserAgentInfo { **Example output:** -```json +```json [Example wide event: userAgent] { "userAgent": { "raw": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0", @@ -89,13 +89,13 @@ Extract geographic data from platform-injected headers. **Sets:** `event.geo` -```typescript +```typescript [geo-enricher.ts] const enrich = createGeoEnricher() ``` **Output shape:** -```typescript +```typescript [geo-types.ts] interface GeoInfo { country?: string // ISO country code (e.g., "US", "FR") region?: string // Region/state name @@ -123,13 +123,13 @@ Capture request and response payload sizes from `Content-Length` headers. **Sets:** `event.requestSize` -```typescript +```typescript [request-size-enricher.ts] const enrich = createRequestSizeEnricher() ``` **Output shape:** -```typescript +```typescript [request-size-types.ts] interface RequestSizeInfo { requestBytes?: number // Request Content-Length responseBytes?: number // Response Content-Length @@ -138,7 +138,7 @@ interface RequestSizeInfo { **Example output:** -```json +```json [Example wide event: requestSize] { "requestSize": { "requestBytes": 1234, @@ -157,13 +157,13 @@ Extract W3C trace context from the `traceparent` and `tracestate` headers. **Sets:** `event.traceContext`, `event.traceId`, `event.spanId` -```typescript +```typescript [trace-context-enricher.ts] const enrich = createTraceContextEnricher() ``` **Output shape:** -```typescript +```typescript [trace-context-types.ts] interface TraceContextInfo { traceparent?: string // Full traceparent header value tracestate?: string // Full tracestate header value @@ -174,7 +174,7 @@ interface TraceContextInfo { **Example output:** -```json +```json [Example wide event: traceContext] { "traceContext": { "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", diff --git a/apps/docs/content/5.enrichers/3.custom.md b/apps/docs/content/6.enrichers/3.custom.md similarity index 96% rename from apps/docs/content/5.enrichers/3.custom.md rename to apps/docs/content/6.enrichers/3.custom.md index 3d971c10..787bca73 100644 --- a/apps/docs/content/5.enrichers/3.custom.md +++ b/apps/docs/content/6.enrichers/3.custom.md @@ -36,7 +36,7 @@ export default defineNitroPlugin((nitroApp) => { The `evlog:enrich` hook receives an `EnrichContext`: -```typescript +```typescript [enrich-context.ts] interface EnrichContext { /** The emitted wide event (mutable) */ event: WideEvent @@ -122,7 +122,7 @@ export default defineNitroPlugin((nitroApp) => { ### Feature Flags -```typescript +```typescript [server/plugins/evlog-enrich.ts] nitroApp.hooks.hook('evlog:enrich', (ctx) => { ctx.event.featureFlags = { newCheckout: isEnabled('new-checkout'), @@ -133,7 +133,7 @@ nitroApp.hooks.hook('evlog:enrich', (ctx) => { ### Response Time Classification -```typescript +```typescript [server/plugins/evlog-enrich.ts] nitroApp.hooks.hook('evlog:enrich', (ctx) => { const duration = ctx.event.duration as number | undefined if (duration === undefined) return diff --git a/apps/docs/content/6.nuxthub/.navigation.yml b/apps/docs/content/7.nuxthub/.navigation.yml similarity index 100% rename from apps/docs/content/6.nuxthub/.navigation.yml rename to apps/docs/content/7.nuxthub/.navigation.yml diff --git a/apps/docs/content/6.nuxthub/1.overview.md b/apps/docs/content/7.nuxthub/1.overview.md similarity index 99% rename from apps/docs/content/6.nuxthub/1.overview.md rename to apps/docs/content/7.nuxthub/1.overview.md index 29560bab..9f4a6aca 100644 --- a/apps/docs/content/6.nuxthub/1.overview.md +++ b/apps/docs/content/7.nuxthub/1.overview.md @@ -50,7 +50,7 @@ bun add @nuxthub/core @evlog/nuxthub Or with `nuxi`: -```bash +```bash [Terminal] npx nuxi module add @nuxthub/core @evlog/nuxthub ``` diff --git a/apps/docs/content/6.nuxthub/2.retention.md b/apps/docs/content/7.nuxthub/2.retention.md similarity index 98% rename from apps/docs/content/6.nuxthub/2.retention.md rename to apps/docs/content/7.nuxthub/2.retention.md index 1db7ff4f..8f430566 100644 --- a/apps/docs/content/6.nuxthub/2.retention.md +++ b/apps/docs/content/7.nuxthub/2.retention.md @@ -55,7 +55,7 @@ The cleanup task deletes all rows in `evlog_events` where `created_at` is older You can trigger cleanup manually via the API endpoint: -```bash +```bash [Terminal] curl https://your-app.com/api/_cron/evlog-cleanup ``` @@ -63,7 +63,7 @@ curl https://your-app.com/api/_cron/evlog-cleanup If the `CRON_SECRET` environment variable is set, the endpoint requires a Bearer token: -```bash +```bash [Terminal] curl -H "Authorization: Bearer your-secret" \ https://your-app.com/api/_cron/evlog-cleanup ``` diff --git a/apps/docs/nuxt.config.ts b/apps/docs/nuxt.config.ts index 8986929a..cc1d9ad2 100644 --- a/apps/docs/nuxt.config.ts +++ b/apps/docs/nuxt.config.ts @@ -5,6 +5,7 @@ export default defineNuxtConfig({ '/getting-started': { redirect: { to: '/getting-started/introduction', statusCode: 301 } }, '/frameworks': { redirect: { to: '/frameworks/overview', statusCode: 301 } }, '/adapters': { redirect: { to: '/adapters/overview', statusCode: 301 } }, + '/core-concepts': { redirect: { to: '/core-concepts/lifecycle', statusCode: 301 } }, '/enrichers': { redirect: { to: '/enrichers/overview', statusCode: 301 } }, '/nuxthub': { redirect: { to: '/nuxthub/overview', statusCode: 301 } }, '/examples/nextjs': { redirect: { to: '/frameworks/nextjs', statusCode: 301 } }, @@ -15,6 +16,12 @@ export default defineNuxtConfig({ '/examples/hono': { redirect: { to: '/frameworks/hono', statusCode: 301 } }, '/examples/fastify': { redirect: { to: '/frameworks/fastify', statusCode: 301 } }, '/examples/elysia': { redirect: { to: '/frameworks/elysia', statusCode: 301 } }, + '/examples/react-router': { redirect: { to: '/frameworks/react-router', statusCode: 301 } }, + '/logging': { redirect: { to: '/logging/overview', statusCode: 301 } }, + '/core-concepts/wide-events': { redirect: { to: '/logging/wide-events', statusCode: 301 } }, + '/core-concepts/structured-errors': { redirect: { to: '/logging/structured-errors', statusCode: 301 } }, + '/core-concepts/client-logging': { redirect: { to: '/logging/client-logging', statusCode: 301 } }, + '/core-concepts/ai-sdk': { redirect: { to: '/logging/ai-sdk', statusCode: 301 } }, }, modules: [ @@ -50,6 +57,9 @@ export default defineNuxtConfig({ }, studio: { + development: { + sync: false, + }, repository: { owner: 'HugoRCD', repo: 'evlog', @@ -63,8 +73,8 @@ export default defineNuxtConfig({ content: { experimental: { - sqliteConnector: 'native' - } + sqliteConnector: 'native', + }, }, mdc: { diff --git a/apps/docs/package.json b/apps/docs/package.json index 16355ae0..b8f4605e 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,5 +1,6 @@ { "name": "evlog-docs", + "type": "module", "private": true, "scripts": { "dev": "nuxt dev", From 32b2b33e87312caa203f44aeeb3b773858db6bf6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:53:05 +0000 Subject: [PATCH 2/4] chore: apply automated lint fixes --- bun.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/bun.lock b/bun.lock index a1d204c1..73d77fad 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "evlog-monorepo", From aa7132aa73c4ffd73599008bbe145b647fbac5fa Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Sat, 4 Apr 2026 08:40:23 +0100 Subject: [PATCH 3/4] up --- apps/docs/content/0.landing.md | 4 +-- .../1.getting-started/1.introduction.md | 6 ++-- .../1.getting-started/3.quick-start.md | 18 ++++++------ apps/docs/content/2.logging/0.overview.md | 16 +++++------ .../content/2.logging/1.simple-logging.md | 10 +++---- apps/docs/content/2.logging/2.wide-events.md | 14 +++++----- .../content/2.logging/3.structured-errors.md | 5 +++- apps/docs/content/2.logging/5.ai-sdk.md | 14 ++++++---- .../content/3.core-concepts/0.lifecycle.md | 24 ++++++++-------- .../3.core-concepts/1.configuration.md | 2 +- .../content/3.core-concepts/2.sampling.md | 2 +- .../content/3.core-concepts/3.typed-fields.md | 8 +++--- .../3.core-concepts/4.best-practices.md | 2 +- .../content/3.core-concepts/5.performance.md | 20 ++++++------- .../content/3.core-concepts/6.vite-plugin.md | 8 +++--- apps/docs/content/4.frameworks/00.overview.md | 6 ++-- apps/docs/content/4.frameworks/01.nuxt.md | 8 +++--- apps/docs/content/4.frameworks/02.nextjs.md | 28 +++++++++---------- .../docs/content/4.frameworks/03.sveltekit.md | 8 +++--- apps/docs/content/4.frameworks/04.nitro.md | 8 +++--- .../content/4.frameworks/05.tanstack-start.md | 12 ++++---- apps/docs/content/4.frameworks/06.nestjs.md | 8 +++--- apps/docs/content/4.frameworks/07.express.md | 8 +++--- apps/docs/content/4.frameworks/08.hono.md | 8 +++--- apps/docs/content/4.frameworks/09.fastify.md | 8 +++--- apps/docs/content/4.frameworks/10.elysia.md | 8 +++--- .../content/4.frameworks/11.react-router.md | 14 +++++----- .../4.frameworks/12.cloudflare-workers.md | 8 +++--- .../content/4.frameworks/13.standalone.md | 8 +++--- apps/docs/content/4.frameworks/14.astro.md | 8 +++--- .../4.frameworks/15.custom-integration.md | 8 +++--- 31 files changed, 157 insertions(+), 152 deletions(-) diff --git a/apps/docs/content/0.landing.md b/apps/docs/content/0.landing.md index 2ea51b16..3132bccc 100644 --- a/apps/docs/content/0.landing.md +++ b/apps/docs/content/0.landing.md @@ -116,7 +116,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont Add logging, :br not overhead #description - Zero dependencies, 5.2 kB gzip, ~3µs per request. Benchmarked against pino, consola, and winston — 8x faster than pino in wide event scenarios while producing richer, more useful output. + Zero dependencies, 5.2 kB gzip, ~3µs per request. Benchmarked against pino, consola, and winston. 8x faster than pino in wide event scenarios while producing richer, more useful output. ::: :::features-feature-frameworks @@ -392,7 +392,7 @@ Wide events and structured errors for TypeScript. One log per request, full cont #elysia ```ts [src/index.ts] - import { evlog, useLogger } from 'evlog/elysia' + import { evlog } from 'evlog/elysia' import { createAxiomDrain } from 'evlog/axiom' const app = new Elysia() diff --git a/apps/docs/content/1.getting-started/1.introduction.md b/apps/docs/content/1.getting-started/1.introduction.md index a55ad092..6caa9e01 100644 --- a/apps/docs/content/1.getting-started/1.introduction.md +++ b/apps/docs/content/1.getting-started/1.introduction.md @@ -1,6 +1,6 @@ --- title: Introduction -description: A structured logging library for TypeScript. Simple logging, wide events, and structured errors — from quick one-liners to comprehensive request-scoped events. +description: A structured logging library for TypeScript. Simple logging, wide events, and structured errors, from quick one-liners to comprehensive request-scoped events. navigation: icon: i-lucide-info links: @@ -41,7 +41,7 @@ Traditional logging is broken. Your logs are scattered across dozens of files. E icon: i-lucide-layers title: Wide Events --- - Accumulate context over any unit of work — a request, script, or job — and emit once. + Accumulate context over any unit of work (a request, script, or job) and emit once. ::: :::card @@ -111,7 +111,7 @@ One log, all context. Everything you need to understand what happened. ### Structured Errors -Errors with actionable context — `why` it happened, how to `fix` it, and a `link` to docs: +Errors with actionable context: `why` it happened, how to `fix` it, and a `link` to docs: ::code-group ```typescript [server/api/checkout.post.ts] diff --git a/apps/docs/content/1.getting-started/3.quick-start.md b/apps/docs/content/1.getting-started/3.quick-start.md index b7fb5599..45c92fb5 100644 --- a/apps/docs/content/1.getting-started/3.quick-start.md +++ b/apps/docs/content/1.getting-started/3.quick-start.md @@ -42,8 +42,8 @@ log.warn('cache', 'Cache miss') :: Two call styles: -- **Tagged** — `log.info('tag', 'message')` for quick, readable console output -- **Structured** — `log.info({ key: value })` for rich events that flow through the drain pipeline +- **Tagged**: `log.info('tag', 'message')` for quick, readable console output +- **Structured**: `log.info({ key: value })` for rich events that flow through the drain pipeline ::callout{icon="i-lucide-arrow-right" color="neutral"} See the full [Simple Logging](/logging/simple-logging) guide for all patterns and drain integration. @@ -51,7 +51,7 @@ See the full [Simple Logging](/logging/simple-logging) guide for all patterns an ## 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`: +When you need to **accumulate context** across multiple steps of an operation, whether a script, background job, queue worker, or workflow, use `createLogger`: ::code-group ```typescript [scripts/sync-job.ts] @@ -116,7 +116,7 @@ export default defineEventHandler(async (event) => { :: ::callout{icon="i-lucide-check" color="success"} -`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()`. +`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 @@ -289,8 +289,8 @@ See [Client Logging](/logging/client-logging) for transport configuration, ident ## Next Steps -- [Logging Overview](/logging/overview) — Understand all three logging modes -- [Wide Events](/logging/wide-events) — Learn how to design effective wide events -- [Typed Fields](/core-concepts/typed-fields) — Add compile-time type safety to your wide events -- [Structured Errors](/logging/structured-errors) — Master error handling with evlog -- [Best Practices](/core-concepts/best-practices) — Security guidelines and production tips +- [Logging Overview](/logging/overview): Understand all three logging modes +- [Wide Events](/logging/wide-events): Learn how to design effective wide events +- [Typed Fields](/core-concepts/typed-fields): Add compile-time type safety to your wide events +- [Structured Errors](/logging/structured-errors): Master error handling with evlog +- [Best Practices](/core-concepts/best-practices): Security guidelines and production tips diff --git a/apps/docs/content/2.logging/0.overview.md b/apps/docs/content/2.logging/0.overview.md index 9fa2116a..2d9591c8 100644 --- a/apps/docs/content/2.logging/0.overview.md +++ b/apps/docs/content/2.logging/0.overview.md @@ -1,6 +1,6 @@ --- title: Logging Overview -description: evlog gives you three ways to log — simple one-liners, wide events that accumulate context, and auto-managed request logging. Choose the right one for your use case. +description: evlog gives you three ways to log. Simple one-liners, wide events that accumulate context, and auto-managed request logging. Choose the right one for your use case. navigation: title: Overview icon: i-lucide-list @@ -39,7 +39,7 @@ evlog provides three logging APIs, each designed for a different context. You ca to: /logging/wide-events color: neutral --- - Accumulate context over a unit of work — a script, job, queue task, or request — then emit a single comprehensive event. + Accumulate context over a unit of work (a script, job, queue task, or request) then emit a single comprehensive event. ::: :::card @@ -105,7 +105,7 @@ export default defineEventHandler(async (event) => { ``` ::callout{icon="i-lucide-info" color="info"} -`useLogger(event)` doesn't create a logger — it retrieves the one the framework middleware already attached to the event. Each framework has its own way to access it (`useLogger`, `req.log`, `c.get('log')`, etc.). In Nuxt, `useLogger` is auto-imported. +`useLogger(event)` doesn't create a logger, it retrieves the one the framework middleware already attached to the event. Each framework has its own way to access it (`useLogger`, `req.log`, `c.get('log')`, etc.). In Nuxt, `useLogger` is auto-imported. :: ## When to Use What @@ -119,7 +119,7 @@ export default defineEventHandler(async (event) => { | **Output** | Console + drain | Console + drain | Console + drain + enrich | ::callout{icon="i-lucide-lightbulb" color="info"} -Start with `log` for quick structured logging. When you need to accumulate context across an operation, switch to `createLogger` (or `createRequestLogger` for HTTP contexts). When using a framework integration, the middleware handles everything — just call `useLogger(event)` to retrieve the logger. +Start with `log` for quick structured logging. When you need to accumulate context across an operation, switch to `createLogger` (or `createRequestLogger` for HTTP contexts). When using a framework integration, the middleware handles everything, just call `useLogger(event)` to retrieve the logger. :: ## Shared Features @@ -134,7 +134,7 @@ All three modes share the same foundation: ## Next Steps -- [Simple Logging](/logging/simple-logging) — The `log` API in detail -- [Wide Events](/logging/wide-events) — Accumulating context and emitting events -- [Structured Errors](/logging/structured-errors) — Errors with actionable context -- [Frameworks](/frameworks/overview) — Auto-managed request logging per framework +- [Simple Logging](/logging/simple-logging): The `log` API in detail +- [Wide Events](/logging/wide-events): Accumulating context and emitting events +- [Structured Errors](/logging/structured-errors): Errors with actionable context +- [Frameworks](/frameworks/overview): Auto-managed request logging per framework diff --git a/apps/docs/content/2.logging/1.simple-logging.md b/apps/docs/content/2.logging/1.simple-logging.md index cfa337b4..cbfede03 100644 --- a/apps/docs/content/2.logging/1.simple-logging.md +++ b/apps/docs/content/2.logging/1.simple-logging.md @@ -16,7 +16,7 @@ links: variant: subtle --- -The `log` API is the simplest way to use evlog. Each call emits a single structured event — no accumulation, no lifecycle management, no manual `emit()`. +The `log` API is the simplest way to use evlog. Each call emits a single structured event, no accumulation, no lifecycle management, no manual `emit()`. ::callout{icon="i-lucide-sparkles" color="info"} In Nuxt, `log` is **auto-imported**. No import statement needed. @@ -170,7 +170,7 @@ syncLog.emit() ## Next Steps -- [Wide Events](/logging/wide-events) — Accumulate context and emit comprehensive events -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` -- [Configuration](/core-concepts/configuration) — All `initLogger` options -- [Adapters](/adapters/overview) — Send events to Axiom, Sentry, PostHog, and more +- [Wide Events](/logging/wide-events): Accumulate context and emit comprehensive events +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` +- [Configuration](/core-concepts/configuration): All `initLogger` options +- [Adapters](/adapters/overview): Send events to Axiom, Sentry, PostHog, and more diff --git a/apps/docs/content/2.logging/2.wide-events.md b/apps/docs/content/2.logging/2.wide-events.md index 16fb32cf..ed605a91 100644 --- a/apps/docs/content/2.logging/2.wide-events.md +++ b/apps/docs/content/2.logging/2.wide-events.md @@ -16,7 +16,7 @@ links: variant: subtle --- -Wide events are the core concept behind evlog. Instead of scattering logs throughout your codebase, you accumulate context over any unit of work — a request, script, job, or workflow — and emit a single, comprehensive log event. +Wide events are the core concept behind evlog. Instead of scattering logs throughout your codebase, you accumulate context over any unit of work, whether a request, script, job, or workflow, and emit a single, comprehensive log event. ## Why Wide Events? @@ -72,7 +72,7 @@ One log, all context. Everything you need to understand what happened. ## Creating Wide Events -### `createLogger` — General Purpose +### `createLogger` (General Purpose) Use `createLogger()` for scripts, background jobs, queue workers, cron jobs, or any operation where you manage the lifecycle: @@ -96,7 +96,7 @@ log.set({ migrated, status: 'complete' }) log.emit() ``` -### `createRequestLogger` — HTTP Contexts +### `createRequestLogger` (HTTP Contexts) Use `createRequestLogger()` when working with HTTP requests outside of a framework integration. It's a thin wrapper around `createLogger` that pre-populates `method`, `path`, and `requestId`: @@ -117,7 +117,7 @@ log.emit() Both `createLogger` and `createRequestLogger` require a manual `log.emit()` call. The event won't be emitted until you call it. :: -### `useLogger` — Retrieving the Request Logger +### `useLogger` (Retrieving the Request Logger) When using a framework integration (Nuxt, Hono, Express, etc.), the middleware creates a wide event logger automatically on each request. `useLogger(event)` retrieves that logger from the request context: @@ -136,12 +136,12 @@ export default defineEventHandler(async (event) => { ``` ::callout{icon="i-lucide-info" color="info"} -`useLogger` doesn't create a logger — it retrieves the one the framework middleware already attached to the event. The middleware handles creation and emission automatically. In Nuxt, `useLogger` is auto-imported. +`useLogger` doesn't create a logger, it retrieves the one the framework middleware already attached to the event. The middleware handles creation and emission automatically. In Nuxt, `useLogger` is auto-imported. :: ## Anatomy of a Wide Event -A well-designed wide event contains context from multiple layers. The examples below show what to add inside your handler or script — they assume `log` is already created via `createLogger`, `createRequestLogger`, or `useLogger`. +A well-designed wide event contains context from multiple layers. The examples below show what to add inside your handler or script. They assume `log` is already created via `createLogger`, `createRequestLogger`, or `useLogger`. ### Operation Context @@ -333,7 +333,7 @@ export default defineEventHandler(async (event) => { ## Output Formats -evlog automatically switches between formats based on environment — pretty in development, JSON in production. This is the default behavior, no configuration needed. +evlog automatically switches between formats based on environment: pretty in development, JSON in production. This is the default behavior, no configuration needed. ::code-group ```bash [Development (Pretty)] diff --git a/apps/docs/content/2.logging/3.structured-errors.md b/apps/docs/content/2.logging/3.structured-errors.md index 2a149524..83743de3 100644 --- a/apps/docs/content/2.logging/3.structured-errors.md +++ b/apps/docs/content/2.logging/3.structured-errors.md @@ -321,4 +321,7 @@ See the [Next.js guide](/frameworks/nextjs) for a working implementation. ## Next Steps -- [Quick Start](/getting-started/quick-start) - See all evlog APIs in action +- [Wide Events](/logging/wide-events): Accumulate context and emit comprehensive events +- [Adapters](/adapters/overview): Send errors and events to Axiom, Sentry, PostHog, and more +- [Frameworks](/frameworks/overview): Auto-managed request logging per framework +- [Quick Start](/getting-started/quick-start): See all evlog APIs in action diff --git a/apps/docs/content/2.logging/5.ai-sdk.md b/apps/docs/content/2.logging/5.ai-sdk.md index 431d29f0..53bd1c08 100644 --- a/apps/docs/content/2.logging/5.ai-sdk.md +++ b/apps/docs/content/2.logging/5.ai-sdk.md @@ -71,6 +71,7 @@ export default defineEventHandler(async (event) => { ``` ```typescript [After] +import { useLogger } from 'evlog' import { createAILogger } from 'evlog/ai' export default defineEventHandler(async (event) => { @@ -88,7 +89,7 @@ export default defineEventHandler(async (event) => { Your wide event now includes: -```json +```json [Wide Event] { "method": "POST", "path": "/api/chat", @@ -134,7 +135,7 @@ Pass `true` to capture all inputs as-is, or an options object for fine-grained c | `maxLength` | `number` | Truncate stringified inputs exceeding this character length (appends `…`) | | `transform` | `(input, toolName) => unknown` | Custom transform applied before `maxLength`. Use to redact fields or reshape data. | -```typescript +```typescript [server/api/chat.post.ts] // Capture everything const ai = createAILogger(log, { toolInputs: true }) @@ -235,7 +236,7 @@ export default defineEventHandler(async (event) => { Wide event after a 3-step agent run: -```json +```json [Wide Event] { "ai": { "calls": 3, @@ -270,6 +271,7 @@ Use `captureEmbed` for embedding calls. They use a different model type that can ```typescript [server/api/rag.post.ts] import { embed, generateText } from 'ai' +import { useLogger } from 'evlog' import { createAILogger } from 'evlog/ai' export default defineEventHandler(async (event) => { @@ -298,7 +300,7 @@ export default defineEventHandler(async (event) => { Wrap each model separately, they share the same accumulator. When multiple models are used, the wide event includes both `model` (last model) and `models` (all unique models): ::code-group -```typescript [Code] +```typescript [server/api/chat.post.ts] const ai = createAILogger(log) const fast = ai.wrap('anthropic/claude-haiku-4.5') @@ -327,7 +329,7 @@ const response = await generateText({ model: smart, prompt: detailedPrompt }) `wrap()` also accepts model objects from provider SDKs if you prefer explicit imports: -```typescript +```typescript [server/api/chat.post.ts] import { anthropic } from '@ai-sdk/anthropic' const model = ai.wrap(anthropic('claude-sonnet-4.6')) @@ -390,7 +392,7 @@ const model = wrapLanguageModel({ If a model call fails, the middleware captures the error into the wide event before re-throwing: -```json +```json [Wide Event] { "ai": { "calls": 1, diff --git a/apps/docs/content/3.core-concepts/0.lifecycle.md b/apps/docs/content/3.core-concepts/0.lifecycle.md index 6599ef76..411a19b0 100644 --- a/apps/docs/content/3.core-concepts/0.lifecycle.md +++ b/apps/docs/content/3.core-concepts/0.lifecycle.md @@ -1,6 +1,6 @@ --- title: Lifecycle -description: Understand the full lifecycle of an evlog event — from creation to drain. Covers all three modes (simple logging, wide events, request logging), sampling, enrichment, and delivery. +description: Understand the full lifecycle of an evlog event, from creation to drain. Covers all three modes (simple logging, wide events, request logging), sampling, enrichment, and delivery. navigation: icon: i-lucide-arrow-right-left links: @@ -16,7 +16,7 @@ links: variant: subtle --- -evlog events follow a pipeline from creation to delivery. The pipeline differs slightly depending on which logging mode you use, but the core stages — emit, sample, enrich, drain — are shared. +evlog events follow a pipeline from creation to delivery. The pipeline differs slightly depending on which logging mode you use, but the core stages (emit, sample, enrich, drain) are shared. ## Overview by Mode @@ -119,7 +119,7 @@ For matched routes, evlog creates a `RequestLogger` and attaches it to the reque | `requestId` | Auto-generated UUID (or `cf-ray` on Cloudflare) | | `startTime` | `Date.now()` for duration calculation | -The logger is stored on the event context. `useLogger(event)` is a shortcut to retrieve it — it doesn't create a new logger. +The logger is stored on the event context. `useLogger(event)` is a shortcut to retrieve it, it doesn't create a new logger. ### 3. Context Accumulation @@ -218,7 +218,7 @@ The `WideEvent` object is built from the accumulated context: } ``` -The event is printed to the console — pretty-formatted in development and as JSON in production. This is the default behavior, no configuration needed. +The event is printed to the console, pretty-formatted in development and as JSON in production. This is the default behavior, no configuration needed. ### 8. Enrich (`evlog:enrich`) @@ -283,9 +283,9 @@ Both paths converge at the same emit/enrich/drain pipeline. The only difference When using the `log` singleton, the pipeline is shorter: -1. **Call** — `log.info({ action: 'deploy' })` or `log.info('tag', 'message')` -2. **Emit** — The event is built and printed immediately -3. **Drain** — If a global `drain` was configured via `initLogger()`, the event is sent to external services +1. **Call**: `log.info({ action: 'deploy' })` or `log.info('tag', 'message')` +2. **Emit**: The event is built and printed immediately +3. **Drain**: If a global `drain` was configured via `initLogger()`, the event is sent to external services Tagged logs (`log.info('tag', 'message')`) are console-only in pretty mode. Object-form logs (`log.info({ ... })`) always flow through the drain pipeline. @@ -293,11 +293,11 @@ Tagged logs (`log.info('tag', 'message')`) are console-only in pretty mode. Obje When using `createLogger()` outside a framework: -1. **Create** — `createLogger({ jobId: 'sync-001' })` -2. **Accumulate** — `log.set()`, `log.info()`, `log.warn()`, `log.error()` over the operation -3. **Emit** — Manual `log.emit()` call -4. **Sample** — Head sampling applies based on computed level. Tail sampling via `initLogger({ sampling: { keep: [...] } })` -5. **Drain** — If a global `drain` was configured, the event is sent +1. **Create**: `createLogger({ jobId: 'sync-001' })` +2. **Accumulate**: `log.set()`, `log.info()`, `log.warn()`, `log.error()` over the operation +3. **Emit**: Manual `log.emit()` call +4. **Sample**: Head sampling applies based on computed level. Tail sampling via `initLogger({ sampling: { keep: [...] } })` +5. **Drain**: If a global `drain` was configured, the event is sent ```typescript [scripts/migrate.ts] import { initLogger, createLogger } from 'evlog' diff --git a/apps/docs/content/3.core-concepts/1.configuration.md b/apps/docs/content/3.core-concepts/1.configuration.md index 6b906972..23e643f4 100644 --- a/apps/docs/content/3.core-concepts/1.configuration.md +++ b/apps/docs/content/3.core-concepts/1.configuration.md @@ -124,7 +124,7 @@ await app.register(evlog, { When a middleware `drain` is set, it takes precedence over the global drain from `initLogger()`. If no middleware drain is set, the global drain is used as fallback, with the benefit of receiving the full enriched event with request context (method, path, headers). -```typescript +```typescript [src/index.ts] import { initLogger } from 'evlog' import { createAxiomDrain } from 'evlog/axiom' diff --git a/apps/docs/content/3.core-concepts/2.sampling.md b/apps/docs/content/3.core-concepts/2.sampling.md index 2dcf11d7..11e805b5 100644 --- a/apps/docs/content/3.core-concepts/2.sampling.md +++ b/apps/docs/content/3.core-concepts/2.sampling.md @@ -81,7 +81,7 @@ Head sampling is random. A `10%` rate means roughly 1 in 10 info logs are kept, Head sampling is blind: it doesn't know if a request was slow, failed, or hit a critical path. Tail sampling fixes this by evaluating **after** the request completes and force-keeping logs that match specific conditions. ```typescript [nuxt.config.ts] -// Sampling config — works the same across all frameworks +// Sampling config, works the same across all frameworks evlog: { sampling: { rates: { info: 10 }, diff --git a/apps/docs/content/3.core-concepts/3.typed-fields.md b/apps/docs/content/3.core-concepts/3.typed-fields.md index 03a597c0..31dec60e 100644 --- a/apps/docs/content/3.core-concepts/3.typed-fields.md +++ b/apps/docs/content/3.core-concepts/3.typed-fields.md @@ -75,7 +75,7 @@ Typed fields are fully opt-in. When using typed fields with `useLogger`, you **must** use an explicit import. The Nuxt auto-import does not support excess property checking for generics due to a TypeScript limitation. :: -```typescript +```typescript [server/api/checkout.post.ts] // Works - explicit import preserves type checking import { useLogger } from 'evlog' const log = useLogger(event) @@ -173,6 +173,6 @@ interface CheckoutFields { ## Next Steps -- [Wide Events](/logging/wide-events) — Design effective wide events with context layering -- [Best Practices](/core-concepts/best-practices) — Security guidelines for preventing sensitive data leakage -- [Configuration](/core-concepts/configuration) — All `initLogger` and middleware options +- [Wide Events](/logging/wide-events): Design effective wide events with context layering +- [Best Practices](/core-concepts/best-practices): Security guidelines for preventing sensitive data leakage +- [Configuration](/core-concepts/configuration): All `initLogger` and middleware options diff --git a/apps/docs/content/3.core-concepts/4.best-practices.md b/apps/docs/content/3.core-concepts/4.best-practices.md index be34bb38..ab4468fb 100644 --- a/apps/docs/content/3.core-concepts/4.best-practices.md +++ b/apps/docs/content/3.core-concepts/4.best-practices.md @@ -184,7 +184,7 @@ Before deploying to production, verify: Use consistent, grouped field names across your codebase: -```typescript +```typescript [server/api/checkout.post.ts] // ✅ Good - grouped and descriptive log.set({ user: { id, plan, accountAge }, diff --git a/apps/docs/content/3.core-concepts/5.performance.md b/apps/docs/content/3.core-concepts/5.performance.md index 177bc67c..03fd0f9e 100644 --- a/apps/docs/content/3.core-concepts/5.performance.md +++ b/apps/docs/content/3.core-concepts/5.performance.md @@ -16,7 +16,7 @@ links: variant: subtle --- -evlog adds **~3µs of overhead per request** — that's 0.003ms, orders of magnitude below any HTTP framework or database call. Performance is tracked on every pull request via [CodSpeed](https://codspeed.io). +evlog adds **~3µs of overhead per request**, that's 0.003ms, orders of magnitude below any HTTP framework or database call. Performance is tracked on every pull request via [CodSpeed](https://codspeed.io). ## evlog vs alternatives @@ -34,10 +34,10 @@ All benchmarks run with JSON output to no-op destinations. pino writes to `/dev/ | Burst (100 logs) | 19.1K ops/s | 10.0K | **40.8K** | 7.6K | | Logger creation | **20.52M** ops/s | 7.36M | 299.3K | 5.43M | -evlog wins **4 out of 7** head-to-head comparisons — and the wins that matter most are decisive: **8x faster** than pino in the wide event lifecycle, **2.8x faster** logger creation, and **3.5x faster** deep nested logging. consola edges ahead on simple strings and burst (it uses a no-op reporter with no serialization), but evlog produces a single correlated event per request where traditional loggers emit N separate lines. +evlog wins **4 out of 7** head-to-head comparisons, and the wins that matter most are decisive: **8x faster** than pino in the wide event lifecycle, **2.8x faster** logger creation, and **3.5x faster** deep nested logging. consola edges ahead on simple strings and burst (it uses a no-op reporter with no serialization), but evlog produces a single correlated event per request where traditional loggers emit N separate lines. ::callout{icon="i-lucide-info" color="info"} -**Why this matters**: in the wide event lifecycle (the real-world pattern), evlog is 8x faster than pino and 14.7x faster than winston — while sending 75% less data to your log drain and giving you one queryable event instead of 4 disconnected lines. +**Why this matters**: in the wide event lifecycle (the real-world pattern), evlog is 8x faster than pino and 14.7x faster than winston while sending 75% less data to your log drain and giving you one queryable event instead of 4 disconnected lines. :: ### What is the "wide event lifecycle"? @@ -45,7 +45,7 @@ evlog wins **4 out of 7** head-to-head comparisons — and the wins that matter This benchmark simulates a real API request: ::code-group -```typescript [evlog — 1 event] +```typescript [evlog (1 event)] const log = createLogger({ method: 'POST', path: '/api/checkout', requestId: 'req_abc' }) log.set({ user: { id: 'usr_123', plan: 'pro' } }) log.set({ cart: { items: 3, total: 9999 } }) @@ -53,7 +53,7 @@ log.set({ payment: { method: 'card', last4: '4242' } }) log.emit({ status: 200 }) ``` -```typescript [pino — 4 log lines] +```typescript [pino (4 log lines)] const child = pinoLogger.child({ method: 'POST', path: '/api/checkout', requestId: 'req_abc' }) child.info({ user: { id: 'usr_123', plan: 'pro' } }, 'user context') child.info({ cart: { items: 3, total: 9999 } }, 'cart context') @@ -66,11 +66,11 @@ Same CPU cost, but evlog gives you everything in one place. ## Why is evlog faster? -The numbers above aren't magic — they come from deliberate architectural choices: +The numbers above aren't magic, they come from deliberate architectural choices: **In-place mutations, not copies.** `log.set()` writes directly into the context object via a recursive `mergeInto` function. Other loggers clone objects on every call (object spread, `Object.assign`). evlog never allocates intermediate objects during context accumulation. -**No serialization until drain.** Context stays as plain JavaScript objects throughout the request lifecycle. `JSON.stringify` runs exactly once, at emit time. Traditional loggers serialize on every `.info()` call — that's 4x serialization for 4 log lines. +**No serialization until drain.** Context stays as plain JavaScript objects throughout the request lifecycle. `JSON.stringify` runs exactly once, at emit time. Traditional loggers serialize on every `.info()` call, that's 4x serialization for 4 log lines. **Lazy allocation.** Timestamps, sampling context, and override objects are only created when actually needed. If tail sampling is disabled (the common case), its context object is never allocated. The `Date` instance used for ISO timestamps is reused across calls. @@ -106,7 +106,7 @@ Every entry point is tree-shakeable. You only pay for what you import. | pipeline | 1.35 kB | | browser | 1.21 kB | -A typical Nuxt setup loads `logger` + `utils` — about **5.2 kB gzip**. Bundle size is tracked on every PR and compared against the `main` baseline. +A typical Nuxt setup loads `logger` + `utils`, about **5.2 kB gzip**. Bundle size is tracked on every PR and compared against the `main` baseline. ## Detailed benchmarks @@ -178,7 +178,7 @@ A typical Nuxt setup loads `logger` + `utils` — about **5.2 kB gzip**. Bundle ### Can you trust these numbers? -Every benchmark in this page is **open source** and **reproducible**. The benchmark files live in [`packages/evlog/bench/`](https://github.com/hugorcd/evlog/tree/main/packages/evlog/bench) — you can read the exact code, run it on your machine, and verify the results. +Every benchmark in this page is **open source** and **reproducible**. The benchmark files live in [`packages/evlog/bench/`](https://github.com/hugorcd/evlog/tree/main/packages/evlog/bench). You can read the exact code, run it on your machine, and verify the results. All libraries are tested under the same conditions: - **Same output mode**: JSON to a no-op destination (no disk or network I/O measured) @@ -195,7 +195,7 @@ Performance regressions are tracked on every pull request via two systems: ### Run it yourself -```bash +```bash [Terminal] cd packages/evlog bun run bench # all benchmarks diff --git a/apps/docs/content/3.core-concepts/6.vite-plugin.md b/apps/docs/content/3.core-concepts/6.vite-plugin.md index f954ff04..839e81db 100644 --- a/apps/docs/content/3.core-concepts/6.vite-plugin.md +++ b/apps/docs/content/3.core-concepts/6.vite-plugin.md @@ -1,6 +1,6 @@ --- title: Vite Plugin -description: Build-time optimizations for any Vite-based framework — auto-init, debug stripping, source location injection, and optional auto-imports. +description: Build-time optimizations for any Vite-based framework. Auto-init, debug stripping, source location injection, and optional auto-imports. navigation: icon: i-custom-vite links: @@ -49,7 +49,7 @@ That's it. The plugin automatically: ### Auto-initialization -The plugin injects logger configuration at compile time via Vite's `define` hook. Your code can use `log`, `createLogger()`, and `createRequestLogger()` immediately — no `initLogger()` call required. +The plugin injects logger configuration at compile time via Vite's `define` hook. Your code can use `log`, `createLogger()`, and `createRequestLogger()` immediately, no `initLogger()` call required. ```typescript [logger-setup.ts] // Before (manual setup) @@ -66,7 +66,7 @@ The `service`, `environment`, `pretty`, `silent`, `enabled`, and `sampling` opti ### Debug stripping -By default, all `log.debug()` calls are removed from production builds. This is a compile-time transformation — the calls are completely eliminated from the output, not just silenced. +By default, all `log.debug()` calls are removed from production builds. This is a compile-time transformation, the calls are completely eliminated from the output, not just silenced. ```typescript [vite.config.ts] evlog({ @@ -175,4 +175,4 @@ export default defineNuxtConfig({ ## Vite Compatibility -The plugin supports **Vite 7+** and is optimized for **Vite 8** (Rolldown). On Vite 8, transform hooks use Rolldown-native `filter` and `moduleType` for maximum performance — non-matching files are skipped entirely on the Rust side without crossing the JS bridge. +The plugin supports **Vite 7+** and is optimized for **Vite 8** (Rolldown). On Vite 8, transform hooks use Rolldown-native `filter` and `moduleType` for maximum performance, non-matching files are skipped entirely on the Rust side without crossing the JS bridge. diff --git a/apps/docs/content/4.frameworks/00.overview.md b/apps/docs/content/4.frameworks/00.overview.md index c38d0c42..ff862e8e 100644 --- a/apps/docs/content/4.frameworks/00.overview.md +++ b/apps/docs/content/4.frameworks/00.overview.md @@ -173,8 +173,8 @@ All frameworks support the same features: [wide events](/logging/wide-events), [ For any Vite-based project, the [`evlog/vite` plugin](/core-concepts/vite-plugin) adds build-time optimizations: -- **Auto-initialization** — no `initLogger()` call needed -- **Debug stripping** — `log.debug()` removed from production builds -- **Source location** — inject `__source: 'file:line'` into log calls +- **Auto-initialization**: no `initLogger()` call needed +- **Debug stripping**: `log.debug()` removed from production builds +- **Source location**: inject `__source: 'file:line'` into log calls Works with SvelteKit, Hono (via vite-node), and any Vite-powered setup. Nuxt users get these features via the `evlog/nuxt` module options. diff --git a/apps/docs/content/4.frameworks/01.nuxt.md b/apps/docs/content/4.frameworks/01.nuxt.md index 0f4cb7cf..49adea7c 100644 --- a/apps/docs/content/4.frameworks/01.nuxt.md +++ b/apps/docs/content/4.frameworks/01.nuxt.md @@ -379,7 +379,7 @@ export default defineNuxtConfig({ Deepen your **Nuxt** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/02.nextjs.md b/apps/docs/content/4.frameworks/02.nextjs.md index 7ac01c25..37e782ac 100644 --- a/apps/docs/content/4.frameworks/02.nextjs.md +++ b/apps/docs/content/4.frameworks/02.nextjs.md @@ -70,8 +70,8 @@ Next.js supports an [`instrumentation.ts`](https://nextjs.org/docs/app/guides/in ::callout{icon="i-lucide-info" color="info"} These two APIs serve different purposes and can be used independently or together: -- **`createEvlog()`** — per-request wide events via `withEvlog()` -- **`createInstrumentation()`** — server startup (`register()`) + unhandled error reporting (`onRequestError()`) across all routes, including SSR and RSC +- **`createEvlog()`**: per-request wide events via `withEvlog()` +- **`createInstrumentation()`**: server startup (`register()`) + unhandled error reporting (`onRequestError()`) across all routes, including SSR and RSC - Both can coexist: `register()` initializes and locks the logger first, so `createEvlog()` respects it. Each can have its own `drain`. :: @@ -92,7 +92,7 @@ export const { register, onRequestError } = createInstrumentation({ Next.js evaluates `instrumentation.ts` in both Node.js and Edge runtimes. Load your real `lib/evlog.ts` only when `NEXT_RUNTIME === 'nodejs'` so Edge bundles never pull Node-only drains (fs, adapters, etc.). -**Recommended** — `defineNodeInstrumentation` gates the Node runtime, dynamic-imports your module once (cached), and forwards `register` / `onRequestError`: +**Recommended**: `defineNodeInstrumentation` gates the Node runtime, dynamic-imports your module once (cached), and forwards `register` / `onRequestError`: ```typescript [instrumentation.ts] import { defineNodeInstrumentation } from 'evlog/next/instrumentation' @@ -100,7 +100,7 @@ import { defineNodeInstrumentation } from 'evlog/next/instrumentation' export const { register, onRequestError } = defineNodeInstrumentation(() => import('./lib/evlog')) ``` -**Manual** — same behavior with explicit handlers; use this if you want full control in the root file (extra branches, per-error logic, or a different import strategy). Without a shared helper, each `onRequestError` typically re-runs `import('./lib/evlog')` unless you add your own cache. +**Manual**: same behavior with explicit handlers; use this if you want full control in the root file (extra branches, per-error logic, or a different import strategy). Without a shared helper, each `onRequestError` typically re-runs `import('./lib/evlog')` unless you add your own cache. ```typescript [instrumentation.ts] export async function register() { @@ -122,12 +122,12 @@ export async function onRequestError( } ``` -Both styles are supported: the helper is optional sugar, not a takeover. `defineNodeInstrumentation` only forwards Next’s two hooks to whatever you export from `lib/evlog` — it does not prevent other work in your app. +Both styles are supported: the helper is optional sugar, not a takeover. `defineNodeInstrumentation` only forwards Next’s two hooks to whatever you export from `lib/evlog`. It does not prevent other work in your app. ### Custom behavior (evlog + your code) -- **Root `instrumentation.ts`** — Next’s stable surface here is `register` and `onRequestError`. The evlog helper exports exactly those; it does not reserve the whole file. If you need **additional** top-level exports later (when Next documents them), use the **manual** wiring and compose by hand, or keep evlog’s hooks minimal and put everything else in `lib/evlog.ts`. -- **`lib/evlog.ts` (recommended for composition)** — wrap evlog’s handlers so you stay free to add startup work, metrics, or extra logging without fighting the helper: +- **Root `instrumentation.ts`**: Next’s stable surface here is `register` and `onRequestError`. The evlog helper exports exactly those; it does not reserve the whole file. If you need **additional** top-level exports later (when Next documents them), use the **manual** wiring and compose by hand, or keep evlog’s hooks minimal and put everything else in `lib/evlog.ts`. +- **`lib/evlog.ts` (recommended for composition)**: wrap evlog’s handlers so you stay free to add startup work, metrics, or extra logging without fighting the helper: ```typescript [lib/evlog.ts] import { createInstrumentation } from 'evlog/next/instrumentation' @@ -152,12 +152,12 @@ export function onRequestError( } ``` -Then keep `instrumentation.ts` as a thin import (`defineNodeInstrumentation` or manual) that only loads `./lib/evlog` on Node — your customization lives next to `createEvlog()` in one place. +Then keep `instrumentation.ts` as a thin import (`defineNodeInstrumentation` or manual) that only loads `./lib/evlog` on Node. Your customization lives next to `createEvlog()` in one place. Next.js automatically calls these exports: -- `register()` — Runs once when the server starts. Initializes the evlog logger with your configured drain, sampling, and options. When `captureOutput` is enabled, `stdout` and `stderr` writes are captured as structured log events. -- `onRequestError()` — Called on every unhandled request error. Emits a structured error log with the error message, digest, stack trace, request path/method, and routing context (`routerKind`, `routePath`, `routeType`, `renderSource`). +- `register()`: Runs once when the server starts. Initializes the evlog logger with your configured drain, sampling, and options. When `captureOutput` is enabled, `stdout` and `stderr` writes are captured as structured log events. +- `onRequestError()`: Called on every unhandled request error. Emits a structured error log with the error message, digest, stack trace, request path/method, and routing context (`routerKind`, `routePath`, `routeType`, `renderSource`). ::callout{icon="i-lucide-info" color="info"} `captureOutput` only activates in the Node.js runtime (`NEXT_RUNTIME === 'nodejs'`). It patches `process.stdout.write` and `process.stderr.write` to emit structured `log.info` / `log.error` events alongside the original output. @@ -533,7 +533,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the example. Deepen your **Next.js** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/03.sveltekit.md b/apps/docs/content/4.frameworks/03.sveltekit.md index b3a6482f..0fd05cb7 100644 --- a/apps/docs/content/4.frameworks/03.sveltekit.md +++ b/apps/docs/content/4.frameworks/03.sveltekit.md @@ -277,7 +277,7 @@ Open [http://localhost:5173](http://localhost:5173) to explore the interactive t Deepen your **SvelteKit** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/04.nitro.md b/apps/docs/content/4.frameworks/04.nitro.md index 6b9ab23c..47777e5f 100644 --- a/apps/docs/content/4.frameworks/04.nitro.md +++ b/apps/docs/content/4.frameworks/04.nitro.md @@ -322,7 +322,7 @@ Errors are always kept by default. You have to explicitly set `error: 0` to drop Deepen your **Nitro** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/05.tanstack-start.md b/apps/docs/content/4.frameworks/05.tanstack-start.md index 475e9dd7..db762d2b 100644 --- a/apps/docs/content/4.frameworks/05.tanstack-start.md +++ b/apps/docs/content/4.frameworks/05.tanstack-start.md @@ -15,7 +15,7 @@ links: TanStack Start uses [Nitro v3](/frameworks/nitro) as its server layer, so evlog integrates via the `evlog/nitro/v3` module. The same plugin-based hooks system applies. ::callout{icon="i-lucide-info" color="info"} -**TanStack Router vs TanStack Start** — TanStack Router is a client-side router and doesn't need server-side logging. This page covers **TanStack Start**, the full-stack framework. If you're using TanStack Router in SPA mode, see [Client Logging](/logging/client-logging) instead. +**TanStack Router vs TanStack Start**: TanStack Router is a client-side router and doesn't need server-side logging. This page covers **TanStack Start**, the full-stack framework. If you're using TanStack Router in SPA mode, see [Client Logging](/logging/client-logging) instead. :: ::code-collapse @@ -88,7 +88,7 @@ export const Route = createRootRoute({ That's it. evlog automatically captures every request as a wide event with method, path, status, and duration. ::callout{color="info" icon="i-custom-vite"} -**Using Vite?** TanStack Start is Vite-based. The [`evlog/vite`](/core-concepts/vite-plugin) plugin strips `log.debug()` from production builds and injects source locations — add it to your `vite.config.ts` alongside the TanStack Start plugin. +**Using Vite?** TanStack Start is Vite-based. The [`evlog/vite`](/core-concepts/vite-plugin) plugin strips `log.debug()` from production builds and injects source locations, add it to your `vite.config.ts` alongside the TanStack Start plugin. :: ## Wide Events @@ -326,7 +326,7 @@ Open [http://localhost:3000](http://localhost:3000) and navigate to the evlog De Deepen your **TanStack Start** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/06.nestjs.md b/apps/docs/content/4.frameworks/06.nestjs.md index 280d5f93..58c30249 100644 --- a/apps/docs/content/4.frameworks/06.nestjs.md +++ b/apps/docs/content/4.frameworks/06.nestjs.md @@ -331,7 +331,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t Deepen your **NestJS** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/07.express.md b/apps/docs/content/4.frameworks/07.express.md index 75640587..70a35714 100644 --- a/apps/docs/content/4.frameworks/07.express.md +++ b/apps/docs/content/4.frameworks/07.express.md @@ -303,7 +303,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t Deepen your **Express** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/08.hono.md b/apps/docs/content/4.frameworks/08.hono.md index 0c747974..3cd14599 100644 --- a/apps/docs/content/4.frameworks/08.hono.md +++ b/apps/docs/content/4.frameworks/08.hono.md @@ -292,7 +292,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t Deepen your **Hono** integration: -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/09.fastify.md b/apps/docs/content/4.frameworks/09.fastify.md index 795acb80..6617577c 100644 --- a/apps/docs/content/4.frameworks/09.fastify.md +++ b/apps/docs/content/4.frameworks/09.fastify.md @@ -262,7 +262,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/10.elysia.md b/apps/docs/content/4.frameworks/10.elysia.md index 93a519ec..37ca96ac 100644 --- a/apps/docs/content/4.frameworks/10.elysia.md +++ b/apps/docs/content/4.frameworks/10.elysia.md @@ -301,7 +301,7 @@ Open [http://localhost:3000](http://localhost:3000) to explore the interactive t ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/11.react-router.md b/apps/docs/content/4.frameworks/11.react-router.md index c285ce05..a0125aad 100644 --- a/apps/docs/content/4.frameworks/11.react-router.md +++ b/apps/docs/content/4.frameworks/11.react-router.md @@ -1,6 +1,6 @@ --- title: React Router -description: Using evlog with React Router — automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in React Router applications. +description: Using evlog with React Router: automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in React Router applications. links: - label: Source Code icon: i-simple-icons-github @@ -15,7 +15,7 @@ navigation: The `evlog/react-router` middleware auto-creates a request-scoped logger accessible via `context.get(loggerContext)` or `useLogger()` and emits a wide event when the response completes. ::callout{color="info" icon="i-lucide-info"} -React Router has three [modes](https://reactrouter.com/start/modes): **Framework**, **Data**, and **Declarative**. The `evlog/react-router` middleware requires the middleware API, which is available in **Framework** and **Data** modes only. Declarative mode does not support middleware — use `evlog/browser` for client-side logging instead. +React Router has three [modes](https://reactrouter.com/start/modes): **Framework**, **Data**, and **Declarative**. The `evlog/react-router` middleware requires the middleware API, which is available in **Framework** and **Data** modes only. Declarative mode does not support middleware, use `evlog/browser` for client-side logging instead. :: ::code-collapse @@ -153,7 +153,7 @@ export async function findUser(userId: string) { } ``` -Then call the service from your loader — `useLogger()` returns the same logger instance: +Then call the service from your loader: `useLogger()` returns the same logger instance: ```typescript [app/routes/users.$id.tsx] import { loggerContext } from 'evlog/react-router' @@ -304,7 +304,7 @@ Open to explore the interactive test UI. ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/12.cloudflare-workers.md b/apps/docs/content/4.frameworks/12.cloudflare-workers.md index 56cf1adf..7f5bd9f3 100644 --- a/apps/docs/content/4.frameworks/12.cloudflare-workers.md +++ b/apps/docs/content/4.frameworks/12.cloudflare-workers.md @@ -187,7 +187,7 @@ wrangler dev ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/13.standalone.md b/apps/docs/content/4.frameworks/13.standalone.md index 5b202fe7..8e2bb9b4 100644 --- a/apps/docs/content/4.frameworks/13.standalone.md +++ b/apps/docs/content/4.frameworks/13.standalone.md @@ -194,7 +194,7 @@ See the full [bun-script example](https://github.com/hugorcd/evlog/tree/main/exa ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/14.astro.md b/apps/docs/content/4.frameworks/14.astro.md index cfc50135..9b34010e 100644 --- a/apps/docs/content/4.frameworks/14.astro.md +++ b/apps/docs/content/4.frameworks/14.astro.md @@ -168,7 +168,7 @@ See the [Adapters](/adapters/overview) docs for all available drain adapters. ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields diff --git a/apps/docs/content/4.frameworks/15.custom-integration.md b/apps/docs/content/4.frameworks/15.custom-integration.md index d9f27421..92bb8596 100644 --- a/apps/docs/content/4.frameworks/15.custom-integration.md +++ b/apps/docs/content/4.frameworks/15.custom-integration.md @@ -172,7 +172,7 @@ Built an integration for a framework we don't support? [Open a PR](https://githu ## Next Steps -- [Wide Events](/logging/wide-events) — Design comprehensive events with context layering -- [Adapters](/adapters/overview) — Send logs to Axiom, Sentry, PostHog, and more -- [Sampling](/core-concepts/sampling) — Control log volume with head and tail sampling -- [Structured Errors](/logging/structured-errors) — Throw errors with `why`, `fix`, and `link` fields +- [Wide Events](/logging/wide-events): Design comprehensive events with context layering +- [Adapters](/adapters/overview): Send logs to Axiom, Sentry, PostHog, and more +- [Sampling](/core-concepts/sampling): Control log volume with head and tail sampling +- [Structured Errors](/logging/structured-errors): Throw errors with `why`, `fix`, and `link` fields From 0171ebf58f551b3a068a1ab8b66a9099abce98dc Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Sat, 4 Apr 2026 08:53:17 +0100 Subject: [PATCH 4/4] up --- apps/docs/app/assets/icons/reactrouter.svg | 1 + apps/docs/app/components/features/FeatureFrameworks.vue | 2 +- apps/docs/content/1.getting-started/2.installation.md | 4 ++-- apps/docs/content/4.frameworks/00.overview.md | 2 +- apps/docs/content/4.frameworks/11.react-router.md | 8 ++++---- 5 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 apps/docs/app/assets/icons/reactrouter.svg diff --git a/apps/docs/app/assets/icons/reactrouter.svg b/apps/docs/app/assets/icons/reactrouter.svg new file mode 100644 index 00000000..247cf13e --- /dev/null +++ b/apps/docs/app/assets/icons/reactrouter.svg @@ -0,0 +1 @@ + diff --git a/apps/docs/app/components/features/FeatureFrameworks.vue b/apps/docs/app/components/features/FeatureFrameworks.vue index 3711b4ae..5519e74c 100644 --- a/apps/docs/app/components/features/FeatureFrameworks.vue +++ b/apps/docs/app/components/features/FeatureFrameworks.vue @@ -23,7 +23,7 @@ const frameworkRows = [ { name: 'SvelteKit', icon: 'i-simple-icons-svelte', tab: 2 }, { name: 'Nitro', icon: 'i-custom-nitro', tab: 3 }, { name: 'TanStack Start', icon: 'i-custom-tanstack', tab: 4 }, - { name: 'React Router', icon: 'i-simple-icons-reactrouter', tab: 5 }, + { name: 'React Router', icon: 'i-custom-reactrouter', tab: 5 }, { name: 'NestJS', icon: 'i-simple-icons-nestjs', tab: 6 }, ], [ diff --git a/apps/docs/content/1.getting-started/2.installation.md b/apps/docs/content/1.getting-started/2.installation.md index 9f93debd..8877774b 100644 --- a/apps/docs/content/1.getting-started/2.installation.md +++ b/apps/docs/content/1.getting-started/2.installation.md @@ -1,6 +1,6 @@ --- title: Install evlog -description: Install evlog in your TypeScript project. +description: Install evlog in your TypeScript project. Supports Nuxt, Next.js, SvelteKit, Hono, Express, Fastify, Elysia, NestJS, and standalone scripts. navigation: title: Installation icon: i-lucide-download @@ -93,7 +93,7 @@ After installing the package, follow the setup guide for your framework: ::: :::card --- - icon: i-simple-icons-reactrouter + icon: i-custom-reactrouter title: React Router to: /frameworks/react-router color: neutral diff --git a/apps/docs/content/4.frameworks/00.overview.md b/apps/docs/content/4.frameworks/00.overview.md index ff862e8e..39004a90 100644 --- a/apps/docs/content/4.frameworks/00.overview.md +++ b/apps/docs/content/4.frameworks/00.overview.md @@ -79,7 +79,7 @@ evlog provides native integrations for every major TypeScript framework. The sam ::: :::card --- - icon: i-simple-icons-reactrouter + icon: i-custom-reactrouter title: React Router to: /frameworks/react-router color: neutral diff --git a/apps/docs/content/4.frameworks/11.react-router.md b/apps/docs/content/4.frameworks/11.react-router.md index a0125aad..f488ad36 100644 --- a/apps/docs/content/4.frameworks/11.react-router.md +++ b/apps/docs/content/4.frameworks/11.react-router.md @@ -1,15 +1,15 @@ --- title: React Router -description: Using evlog with React Router: automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in React Router applications. +description: "Automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in React Router applications." +navigation: + title: React Router + icon: i-custom-reactrouter links: - label: Source Code icon: i-simple-icons-github to: https://github.com/hugorcd/evlog/tree/main/examples/react-router color: neutral variant: subtle -navigation: - title: React Router - icon: i-simple-icons-reactrouter --- The `evlog/react-router` middleware auto-creates a request-scoped logger accessible via `context.get(loggerContext)` or `useLogger()` and emits a wide event when the response completes.