Ghost

struxadotcloud/struxa

public
Fork0
CodeIssues0Pull requests0
1branch
struxa.claudeskillsreview-logging-patternsSKILL.md
feat: global unslop6531421a month ago1042 lines · 34.7 KB
1---
2name: review-logging-patterns
3description: Review code for logging patterns and suggest evlog adoption. Guides setup on Nuxt, Next.js, SvelteKit, Nitro, TanStack Start, React Router, NestJS, Express, Hono, Fastify, Elysia, Cloudflare Workers, and standalone TypeScript. Detects console.log spam, unstructured errors, and missing context. Covers wide events, structured errors, drain adapters (Axiom, OTLP, HyperDX, PostHog, Sentry, Better Stack, Datadog), sampling, enrichers, and AI SDK integration (token usage, tool calls, streaming metrics, telemetry integration, cost estimation, embedding metadata).
4license: MIT
5metadata:
6 author: HugoRCD
7 version: "0.5"
8---
9
10# Review logging patterns
11
12Review and improve logging patterns in TypeScript/JavaScript codebases. Transform scattered console.logs into structured wide events and convert generic errors into self-documenting structured errors.
13
14## When to Use
15
16- Setting up evlog in a new or existing project (any supported framework)
17- Reviewing code for logging best practices
18- Converting console.log statements to structured logging
19- Improving error handling with better context
20- Configuring log draining, sampling, or enrichment
21
22## Quick Reference
23
24| Working on... | Resource |
25| --------------------- | ------------------------------------------------------------------ |
26| Wide events patterns | [references/wide-events.md](
27| Error handling | [references/structured-errors.md](
28| Code review checklist | [references/code-review.md](
29| Drain pipeline | [references/drain-pipeline.md](
30
31## Installation
32
33```bash
34npm install evlog
35```
36
37---
38
39## Framework Setup
40
41### Nuxt
42
43```typescript
44// nuxt.config.ts
45export default defineNuxtConfig({
46 modules: ["evlog/nuxt"],
47 evlog: {
48 env: { service: "
49 include: ["/api/**"],
50 },
51});
52```
53
54All evlog functions (`useLogger`, `createError`, `
55
56```typescript
57// server/api/checkout.post.ts - no imports needed
58export default defineEventHandler(async
59 const log =
60 log.set({
61 return { success:
62});
63```
64
65Drain, enrich, and tail sampling use Nitro hooks in server plugins:
66
67```typescript
68// server/plugins/evlog-drain.ts
69import { createAxiomDrain }
70
71export default defineNitroPlugin((nitroApp
72 nitroApp.hooks.hook
73});
74```
75
76Client transport (auto-configured Vue plugin):
77
78```typescript
79// nuxt.config.ts
80evlog: {
81 transport: { enabled: true
82}
83```
84
85Client-side: `log`, `setIdentity`, `
86
87### Next.js
88
89**Step 1: Create central config** - all exports come from here:
90
91```typescript
92// lib/evlog.ts
93import type { DrainContext
94import { createEvlog }
95import { createUserAgentEnricher, createRequestSizeEnricher
96import { createDrainPipeline }
97
98const enrichers = [
99const pipeline = createDrainPipeline
100const drain = pipeline
101
102export const { withEvlog
103 service: "my-app",
104 sampling: {
105 rates: { info: 10
106 keep: [{ status:
107 },
108 routes: {
109 "/api/auth/**": { service:
110 "/api/checkout/**": { service:
111 },
112 keep: (ctx)
113 const user =
114 if (user?.premium
115 },
116 enrich: (ctx)
117 for (const enricher
118 },
119 drain,
120});
121```
122
123**Step 2: Wrap route handlers** with `withEvlog()
124
125```typescript
126// app/api/checkout/route.ts
127import { withEvlog, useLogger
128
129export const POST =
130 const log =
131 log.set({
132 log.set({
133 return Response.json
134});
135```
136
137**Step 3: Server Actions** - same `withEvlog()
138
139```typescript
140// app/actions.ts
141"use server";
142import { withEvlog, useLogger
143
144export const checkout =
145 const log =
146 log.set({
147 return { success:
148});
149```
150
151**Step 4: Middleware** (optional - sets `x-request-id
152
153```typescript
154// proxy.ts
155import { evlogMiddleware }
156export const proxy =
157export const config =
158```
159
160**Step 5: Client Provider** - wrap root layout:
161
162```tsx
163// app/layout.tsx
164import { EvlogProvider }
165
166export default function Layout
167 return (
168 <html lang="
169 <body>
170 <EvlogProvider
171 service="my-app"
172 transport={{ enabled:
173 >
174 {children}
175 </EvlogProvider>
176 </body>
177 </html>
178 );
179}
180```
181
182**Step 6: Client logging** - in any client component:
183
184```tsx
185"use client";
186import { log, setIdentity
187
188setIdentity({ userId: "
189log.info({ action
190clearIdentity();
191```
192
193**Step 7 (optional): Instrumentation** - startup + global `onRequestError
194
195Export `createInstrumentation()` from `lib/evlog.ts` alongside `
196
197**Step 8: Client ingest endpoint** - receives client logs:
198
199```typescript
200// app/api/evlog/ingest/route.ts
201import { NextRequest }
202
203const VALID_LEVELS = [
204
205export async function POST
206 const origin =
207 const host =
208 if (origin &&
209 return Response.json
210 }
211 const body =
212 if (!body?.timestamp
213 return Response.json
214 }
215 const { service:
216 console.log(
217 "[CLIENT LOG]",
218 JSON.stringify({
219 );
220 return new Response
221}
222```
223
224### SvelteKit
225
226```typescript
227// src/hooks.server.ts
228import { initLogger }
229import { createEvlogHooks }
230
231initLogger({ env: {
232
233export const { handle
234```
235
236Access the logger via `event.locals.log` in route handlers or `useLogger()` from anywhere in the call stack:
237
238```typescript
239// src/routes/api/users/[id]/+server.ts
240import { json }
241
242export const GET =
243 locals.log.set
244 return json({
245};
246```
247
248```typescript
249import { useLogger }
250
251async function findUsers()
252 const log =
253 log.set({
254}
255```
256
257Full pipeline with drain, enrich, and tail sampling:
258
259```typescript
260import { createAxiomDrain }
261
262export const { handle
263 include: ["/api/**"],
264 drain: createAxiomDrain(),
265 enrich: (ctx)
266 ctx.event.region
267 },
268 keep: (ctx)
269 if (ctx.duration
270 },
271});
272```
273
274### Nitro v3
275
276```typescript
277// nitro.config.ts
278import { defineConfig }
279import evlog from "evlog/nitro/v3
280
281export default defineConfig({
282 modules: [evlog({
283});
284```
285
286```typescript
287// routes/api/checkout.post.ts
288import { defineHandler }
289import { useLogger }
290
291export default defineHandler(async
292 const log =
293 log.set({
294 return { ok:
295});
296```
297
298### TanStack Start
299
300TanStack Start uses Nitro v3. Install evlog and add a `nitro.config.ts`:
301
302```typescript
303// nitro.config.ts
304import { defineConfig }
305import evlog from "evlog/nitro/v3
306
307export default defineConfig({
308 experimental: { asyncContext: true
309 modules: [evlog({
310});
311```
312
313Add the error handling middleware to `__root.tsx`:
314
315```typescript
316// src/routes/__root.tsx
317import { createMiddleware }
318import { evlogErrorHandler }
319
320export const Route =
321 server: {
322 middleware: [createMiddleware().
323 },
324});
325```
326
327Use `useRequest()` from `nitro/context` to access the logger:
328
329```typescript
330import { useRequest }
331import type { RequestLogger
332
333const req = useRequest
334const log = req
335log.set({ user
336```
337
338### Nitro v2
339
340```typescript
341// nitro.config.ts
342import { defineNitroConfig }
343import evlog from "evlog/nitro
344
345export default defineNitroConfig({
346 modules: [evlog({
347});
348```
349
350Import `useLogger` from `evlog/nitro` in routes.
351
352### NestJS
353
354```typescript
355// src/app.module.ts
356import { Module }
357import { EvlogModule }
358
359@Module({
360 imports: [EvlogModule.forRoot
361})
362export class AppModule
363```
364
365`EvlogModule.forRoot()` registers a global middleware. Use `useLogger()` to access the request-scoped logger from any controller or service:
366
367```typescript
368import { useLogger }
369
370async function findUsers()
371 const log =
372 log.set({
373}
374```
375
376Full pipeline with drain, enrich, and tail sampling:
377
378```typescript
379import { createAxiomDrain }
380
381EvlogModule.forRoot({
382 include: ["/api/**"],
383 drain: createAxiomDrain(),
384 enrich: (ctx)
385 ctx.event.region
386 },
387 keep: (ctx)
388 if (ctx.duration
389 },
390});
391```
392
393For async configuration with NestJS DI, use `forRootAsync()`:
394
395```typescript
396EvlogModule.forRootAsync({
397 imports: [ConfigModule],
398 inject: [ConfigService],
399 useFactory: (config)
400 drain: createAxiomDrain({
401 }),
402});
403```
404
405### Express
406
407```typescript
408import express from "express
409import { initLogger }
410import { evlog, useLogger
411
412initLogger({ env: {
413
414const app = express
415app.use(evlog())
416
417app.get("/api/users"
418 req.log.set
419 res.json({
420});
421```
422
423Use `useLogger()` to access the logger from anywhere in the call stack without passing `req`:
424
425```typescript
426import { useLogger }
427
428async function findUsers()
429 const log =
430 log.set({
431}
432```
433
434Full pipeline with drain, enrich, and tail sampling:
435
436```typescript
437import { createAxiomDrain }
438
439app.use(
440 evlog({
441 include: ["/api/**"],
442 drain: createAxiomDrain(),
443 enrich: (ctx)
444 ctx.event.region
445 },
446 keep: (ctx)
447 if (ctx.duration
448 },
449 }),
450);
451```
452
453### Hono
454
455```typescript
456import { Hono }
457import { initLogger }
458import { evlog, type
459
460initLogger({ env: {
461
462const app = new
463app.use(evlog())
464
465app.get("/api/users"
466 const log =
467 log.set({
468 return c.json
469});
470```
471
472Access the logger via `c.get('log')` in handlers. No `useLogger()` - use `
473
474Structured errors: throw `createError()`, then in `app.onError` use `
475
476```typescript
477import { createError, parseError
478import type { ContentfulStatusCode
479
480app.onError((error
481 c.get("log
482 const parsed =
483 return c.json
484 { message: parsed.
485 parsed.status as
486 );
487});
488```
489
490Full pipeline with drain, enrich, and tail sampling:
491
492```typescript
493import { createAxiomDrain }
494
495app.use(
496 evlog({
497 include: ["/api/**"],
498 drain: createAxiomDrain(),
499 enrich: (ctx)
500 ctx.event.region
501 },
502 keep: (ctx)
503 if (ctx.duration
504 },
505 }),
506);
507```
508
509### Fastify
510
511```typescript
512import Fastify from "fastify
513import { initLogger }
514import { evlog, useLogger
515
516initLogger({ env: {
517
518const app = Fastify
519await app.register(
520
521app.get("/api/users"
522 request.log.set
523 return { users:
524});
525```
526
527`request.log` is the evlog wide-event logger (shadows Fastify's built-in pino logger on the request). Fastify's pino logger remains accessible via `fastify.log`.
528
529Use `useLogger()` to access the logger from anywhere in the call stack without passing `request`:
530
531```typescript
532import { useLogger }
533
534async function findUsers()
535 const log =
536 log.set({
537}
538```
539
540Full pipeline with drain, enrich, and tail sampling:
541
542```typescript
543import { createAxiomDrain }
544
545await app.register(
546 include: ["/api/**"],
547 drain: createAxiomDrain(),
548 enrich: (ctx)
549 ctx.event.region
550 },
551 keep: (ctx)
552 if (ctx.duration
553 },
554});
555```
556
557### Elysia
558
559```typescript
560import { Elysia }
561import { initLogger }
562import { evlog, useLogger
563
564initLogger({ env: {
565
566const app = new
567 .use(evlog())
568 .get("/api/users",
569 log.set({
570 return { users:
571 })
572 .listen(3000);
573```
574
575Use `useLogger()` to access the logger from anywhere in the call stack:
576
577```typescript
578import { useLogger }
579
580async function findUsers()
581 const log =
582 log.set({
583}
584```
585
586Full pipeline with drain, enrich, and tail sampling:
587
588```typescript
589import { createAxiomDrain }
590
591app.use(
592 evlog({
593 include: ["/api/**"],
594 drain: createAxiomDrain(),
595 enrich: (ctx)
596 ctx.event.region
597 },
598 keep: (ctx)
599 if (ctx.duration
600 },
601 }),
602);
603```
604
605### React Router
606
607```typescript
608// react-router.config.ts
609import type { Config
610
611export default {
612 future: {
613 v8_middleware: true,
614 },
615} satisfies Config;
616```
617
618```typescript
619// app/root.tsx
620import { initLogger }
621import { evlog }
622
623initLogger({ env: {
624
625export const middleware:
626```
627
628Access the logger via `context.get(loggerContext)` in loaders and actions:
629
630```typescript
631// app/routes/api.users.$id.tsx
632import { loggerContext }
633
634export async function loader
635 const log =
636 log.set({
637 return { users:
638}
639```
640
641Use `useLogger()` to access the logger from anywhere in the call stack without passing context:
642
643```typescript
644import { useLogger }
645
646async function findUsers()
647 const log =
648 log.set({
649}
650```
651
652Full pipeline with drain, enrich, and tail sampling:
653
654```typescript
655import { createAxiomDrain }
656
657export const middleware:
658 evlog({
659 include: ["/api/**"],
660 drain: createAxiomDrain(),
661 enrich: (ctx)
662 ctx.event.region
663 },
664 keep: (ctx)
665 if (ctx.duration
666 },
667 }),
668];
669```
670
671### Cloudflare Workers
672
673```typescript
674import { initWorkersLogger, createWorkersLogger
675
676initWorkersLogger({ env: {
677
678export default {
679 async fetch(request
680 const log =
681 try {
682 log.set({
683 const response =
684 log.emit({
685 return response;
686 } catch (error)
687 log.error(error
688 log.emit({
689 throw error;
690 }
691 },
692};
693```
694
695### Vite Plugin (any Vite-based framework)
696
697For any Vite-based project (SvelteKit, Astro, SolidStart, React+Vite, etc.), use the Vite plugin for auto-init, auto-imports, and build-time features:
698
699```typescript
700// vite.config.ts
701import evlog from "evlog/vite
702
703export default defineConfig({
704 plugins: [
705 evlog({
706 service: "my-app",
707 autoImports: true, //
708 strip: ["debug"],
709 sourceLocation: true, //
710 client: {
711 // client-side logging
712 transport: { endpoint: "
713 },
714 }),
715 ],
716});
717```
718
719Server-side middleware (drain, enrich, keep, routes) is still configured in the framework integration (e.g., `evlog()` middleware for Hono/Express/SvelteKit). The Vite plugin handles build-time DX only.
720
721### Standalone TypeScript
722
723```typescript
724import { initLogger, createRequestLogger
725
726initLogger({ env: {
727
728const log = createRequestLogger
729log.set({ source
730log.emit(); //
731```
732
733---
734
735## Configuration Options
736
737All options work in Nuxt (`evlog` key), Nitro (passed to `evlog()`), Next.js (`
738
739| Option | Type | Default | Description |
740| ------------------------- | ---------------------------------------- |
741| `env.service` / `service`
742| `enabled` | `boolean
743| `pretty` | `boolean
744| `silent` | `boolean
745| `include` | `string[]
746| `exclude` | `string[]
747| `routes` | `Record<string, { service }>
748| `minLevel` | `'debug' \| 'info' \| 'warn' \| 'error'
749| `sampling.rates` | `object
750| `sampling.keep` | `array
751| `drain` | `(ctx) => void
752| `enrich` | `(ctx) => void
753| `keep` | `(ctx) => void
754| `redact` | `boolean \| RedactConfig
755
756### Nitro Hooks (Nuxt, Nitro v2/v3)
757
758| Hook | When | Use |
759| ----------------- | ------------------------ |
760| `evlog:drain` | After enrichment | Send events to external services
761| `evlog:enrich` | After emit, before drain | Add derived context
762| `evlog:emit:keep` | During emit | Custom tail sampling logic
763| `close` | Server shutdown | Flush drain pipeline buffers
764
765---
766
767## Drain Adapters
768
769| Adapter | Import | Env Vars |
770| --------------------- | -------------------- |
771| Axiom | `evlog/axiom` |
772| OTLP | `evlog/otlp` |
773| HyperDX | `evlog/hyperdx` |
774| PostHog | `evlog/posthog` |
775| Sentry | `evlog/sentry` |
776| Better Stack | `evlog/better-stack` |
777| Datadog | `evlog/datadog` |
778| File System | `evlog/fs` | None (local file system)
779| HTTP (browser ingest) | `evlog/http` | None (configure
780
781In Nuxt/Nitro, use the `NUXT_` prefix (e.g., `NUXT_AXIOM_TOKEN`) so values are available via
782
783Setup pattern per framework:
784
785```typescript
786// Nuxt/Nitro: server/plugins/evlog-drain.ts
787import { createAxiomDrain }
788export default defineNitroPlugin((nitroApp
789 nitroApp.hooks.hook
790});
791
792// Hono / Express / Elysia: pass drain in middleware options
793import { createAxiomDrain }
794app.use(evlog(
795
796// Fastify: pass drain in plugin options
797import { createAxiomDrain }
798await app.register(
799
800// NestJS: pass drain in module options
801import { createAxiomDrain }
802EvlogModule.forRoot({ drain
803
804// Next.js: pass drain to createEvlog()
805import { createAxiomDrain }
806import { createDrainPipeline }
807const pipeline = createDrainPipeline
808const drain = pipeline
809// then: createEvlog({ ..., drain })
810
811// Standalone: pass drain to initLogger()
812initLogger({ env: {
813```
814
815See [references/drain-pipeline.md](references/drain-pipeline.md
816
817---
818
819## Enrichers
820
821Built-in: `createUserAgentEnricher()`, `createGeoEnricher()`, `
822
823```typescript
824// Nuxt/Nitro: server/plugins/evlog-enrich.ts
825import { createUserAgentEnricher, createGeoEnricher
826export default defineNitroPlugin((nitroApp
827 const enrichers =
828 nitroApp.hooks.hook
829 for (const enricher
830 });
831});
832
833// Next.js: in lib/evlog.ts
834createEvlog({
835 enrich: (ctx)
836 for (const enricher
837 ctx.event.region
838 },
839});
840```
841
842---
843
844## Auto-Redaction (PII Protection)
845
846Built-in redaction scrubs sensitive data from wide events **before** console output and
847
848```typescript
849// Disable in production (opt-out)
850evlog: { redact: false
851
852// Add custom paths on top of built-ins
853evlog: {
854 redact: {
855 paths: ['user.password',
856 }
857}
858
859// Only specific built-ins
860evlog: {
861 redact: {
862 builtins: ['email',
863 }
864}
865
866// No built-ins, only custom (uses flat [REDACTED] replacement)
867evlog: {
868 redact: {
869 builtins: false,
870 paths: ['user.ssn'],
871 patterns: [/SECRET_\w+/
872 }
873}
874```
875
876**Built-in patterns** with smart masking output:
877
878| Pattern | Example Input | Masked Output |
879| ------------ | ----------------------- |
880| `creditCard` | `4111111111111111
881| `email` | `alice@example.com
882| `ipv4` | `192.168.1.100
883| `phone` | `+33 6 12 34 56 78
884| `jwt` | `eyJhbGciOi...
885| `bearer` | `Bearer sk_live_abc...
886| `iban` | `FR76 3000 6000 ...189
887
888Works in all frameworks: Nuxt (`evlog` config), Nitro (`evlog()` module options), Next.js (`
889
890---
891
892## AI SDK Integration
893
894Capture token usage, tool calls, model info, streaming metrics, tool execution timing, cost estimation, and embedding metadata from the Vercel AI SDK into wide events. Import from `evlog/ai`. Requires `ai >= 6.0.0` as a peer dependency.
895
896### Basic setup (middleware)
897
898```typescript
899import { createAILogger }
900
901const log = useLogger
902const ai = createAILogger
903
904const result = streamText
905 model: ai.wrap
906 messages,
907});
908```
909
910`ai.wrap()` uses model middleware to transparently capture all LLM calls. Works with `generateText`, `
911
912### Telemetry integration (deeper observability)
913
914For tool execution timing, success/failure tracking, and total generation wall time, add `createEvlogIntegration()`:
915
916```typescript
917import { createAILogger, createEvlogIntegration
918
919const ai = createAILogger
920
921const agent = new
922 model: ai.wrap
923 tools: { searchWeb,
924 stopWhen: stepCountIs(5)
925 experimental_telemetry: {
926 isEnabled: true,
927 integrations: [createEvlogIntegration(ai
928 },
929});
930```
931
932This adds `ai.tools` (per-tool `{ name, durationMs, success, error? }`) and `
933
934### Embeddings
935
936```typescript
937const { embedding,
938ai.captureEmbed({ usage
939```
940
941For `embedMany`, pass the batch count:
942
943```typescript
944ai.captureEmbed({ usage
945```
946
947### Cost estimation
948
949Pass a pricing map to get `ai.estimatedCost` in the wide event:
950
951```typescript
952const ai = createAILogger
953 cost: {
954 "claude-sonnet-4.6": { input:
955 "gpt-4o": { input:
956 },
957});
958```
959
960### Wide event `
961
962Includes: `calls`, `model`, `
963
964Anti-patterns to detect:
965
966| Anti-Pattern | Fix |
967| -------------------------------------- | ------------------------------------------------------------------------- |
968| Manual token tracking in `onFinish` | `ai.wrap()
969| `console.log('tokens:', result.usage)` | `ai.wrap()
970| No AI observability | Add `createAILogger(log)` + `ai.wrap()
971| No tool execution timing | Add `createEvlogIntegration(ai)` to `experimental_telemetry.integrations
972| Manual cost calculation | Use `cost` option in `createAILogger()
973
974---
975
976## Structured Errors
977
978```typescript
979import { createError }
980
981// Minimal
982throw createError({ message:
983
984// Standard
985throw createError({ message:
986
987// Complete
988throw createError({
989 message: "Payment failed",
990 status: 402,
991 why: "Card declined by issuer - insufficient funds",
992 fix: "Please use a different payment method or contact your bank",
993 link: "https://docs.example.com/payments/declined",
994 cause: originalError,
995});
996
997// Backend-only context (wide events / drains - never HTTP body or parseError())
998throw createError({
999 message: "Not allowed",
1000 status: 403,
1001 why: "Insufficient permissions",
1002 internal: { correlationId: "
1003});
1004```
1005
1006Frontend - extract user-facing fields with `parseError()` (`internal` is never returned to clients):
1007
1008```typescript
1009import { parseError }
1010
1011const error = parseError
1012// error.message, error.status, error.why, error.fix, error.link
1013```
1014
1015See [references/structured-errors.md](references/structured-errors.md
1016
1017---
1018
1019## Anti-Patterns to Detect
1020
1021| Anti-Pattern | Fix |
1022| ------------------------------------------------ | ------------------------------------------------------------------------------------- |
1023| Multiple `console.log` in one function | Single wide event with `log.set()
1024| `throw new Error('...')` | `throw createError({ message, status, why, fix })
1025| `console.error(e); throw e` | `log.error(e); throw createError(...)
1026| No logging in request handlers | Add `useLogger(event)` / `useLogger()
1027| Flat log data `{ uid, n, t }` | Grouped objects: `{ user: {...}, cart: {...} }
1028| Logging sensitive data `log.set({ user: body })` | Explicit fields: `{ user: { id: body.id, plan: body.plan } }
1029| Putting support-only IDs in `why` / `message`
1030
1031See [references/code-review.md](references/code-review.md
1032
1033---
1034
1035## Loading Reference Files
1036
1037Load based on what you're working on - **do not load all at once**:
1038
1039- Designing wide events → [references/wide-events.md](references/wide-events.md
1040- Improving errors → [references/structured-errors.md](references/structured-errors.md
1041- Full code review → [references/code-review.md](references/code-review.md
1042- Drain pipeline setup → [references/drain-pipeline.md](references/drain-pipeline.md
references/wide-events.md
)
|
references/structured-errors.md
)
|
references/code-review.md
)
|
references/drain-pipeline.md
)
|
my-app
"
}
,
parseError
`
,
`
log
`
) are
**
auto-imported
**
- no import statements needed.
(
event
)
=>
{
useLogger
(
event
)
;
user
:
{
id
:
user
.
id
,
plan
:
user
.
plan
}
}
)
;
true
}
;
from
"
evlog/axiom
"
;
)
=>
{
(
"
evlog:drain
"
,
createAxiomDrain
())
;
}
,
//
logs sent to /api/_evlog/ingest
clearIdentity
`
are auto-imported in components.
}
from
"
evlog
"
;
from
"
evlog/next
"
;
}
from
"
evlog/enrichers
"
;
from
"
evlog/pipeline
"
;
createUserAgentEnricher
()
,
createRequestSizeEnricher
()]
;
<
DrainContext
>
(
{
batch
:
{
size
:
50
,
intervalMs
:
5000
}
}
)
;
(
createAxiomDrain
(
{
dataset
:
"
logs
"
,
token
:
process
.
env
.
AXIOM_TOKEN
!
}
))
;
,
useLogger
,
log
,
createError
}
=
createEvlog
(
{
}
,
400
}
,
{
duration
:
1000
}
]
,
"
auth-service
"
}
,
"
checkout-service
"
}
,
=>
{
ctx
.
context
.
user
as
{
premium
?
:
boolean
}
|
undefined
;
)
ctx
.
shouldKeep
=
true
;
=>
{
of
enrichers
)
enricher
(
ctx
)
;
`
:
}
from
"
@/lib/evlog
"
;
withEvlog
(
async
(
request
:
Request
)
=>
{
useLogger
()
;
//
Zero arguments - uses AsyncLocalStorage
user
:
{
id
:
"
user_123
"
,
plan
:
"
enterprise
"
}
}
)
;
cart
:
{
items
:
3
,
total
:
14999
}
}
)
;
(
{
success
:
true
}
)
;
`
wrapper:
}
from
"
@/lib/evlog
"
;
withEvlog
(
async
(
formData
:
FormData
)
=>
{
useLogger
()
;
action
:
"
checkout
"
,
source
:
"
server-action
"
}
)
;
true
}
;
`
+ timing headers):
from
"
evlog/next
"
;
evlogMiddleware
()
;
{
matcher
:
[
"
/api/:path*
"
]
}
;
from
"
evlog/next/client
"
;
(
{
children
}
:
{
children
:
React
.
ReactNode
}
)
{
en
"
>
true
,
endpoint
:
"
/api/evlog/ingest
"
}
}
,
clearIdentity
}
from
"
evlog/next/client
"
;
usr_123
"
}
)
;
:
"
checkout_click
"
}
)
;
`
(SSR/RSC errors outside
`
withEvlog
`
). Use
`
defineNodeInstrumentation(() => import('./lib/evlog'))
`
in root
`
instrumentation.ts
`
to gate Node + cache the import,
**
or
**
write
`
register
`
/
`
onRequestError
`
manually - both are valid. For custom logic, wrap evlog’s
`
register
`
/
`
onRequestError
`
inside
`
lib/evlog.ts
`
(compose with your own init or metrics), then re-export.
createEvlog()
`
. See framework docs for coexistence with
`
lockLogger
`
.
from
"
next/server
"
;
"
info
"
,
"
error
"
,
"
warn
"
,
"
debug
"
]
as
const
;
(
request
:
NextRequest
)
{
request
.
headers
.
get
(
"
origin
"
)
;
request
.
headers
.
get
(
"
host
"
)
;
new
URL
(
origin
)
.
host
!==
host
)
{
(
{
error
:
"
Invalid origin
"
}
,
{
status
:
403
}
)
;
await
request
.
json
()
;
||
!
body
?.
level
||
!
VALID_LEVELS
.
includes
(
body
.
level
))
{
(
{
error
:
"
Invalid payload
"
}
,
{
status
:
400
}
)
;
_
,
...
sanitized
}
=
body
;
...
sanitized
,
service
:
"
my-app
"
,
source
:
"
client
"
}
)
,
(
null
,
{
status
:
204
}
)
;
from
"
evlog
"
;
from
"
evlog/sveltekit
"
;
service
:
"
my-app
"
}
}
)
;
,
handleError
}
=
createEvlogHooks
()
;
from
"
@sveltejs/kit
"
;
(
{
locals
,
params
}
)
=>
{
(
{
user
:
{
id
:
params
.
id
}
}
)
;
id
:
params
.
id
}
)
;
from
"
evlog/sveltekit
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
,
handleError
}
=
createEvlogHooks
(
{
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
from
"
nitro
"
;
"
;
env
:
{
service
:
"
my-api
"
}
}
)]
,
from
"
nitro/h3
"
;
from
"
evlog/nitro/v3
"
;
(
event
)
=>
{
useLogger
(
event
)
;
action
:
"
checkout
"
}
)
;
true
}
;
from
"
nitro
"
;
"
;
}
,
env
:
{
service
:
"
my-app
"
}
}
)]
,
from
"
@tanstack/react-start
"
;
from
"
evlog/nitro/v3
"
;
createRootRoute
(
{
server
(
evlogErrorHandler
)]
,
from
"
nitro/context
"
;
}
from
"
evlog
"
;
()
;
.
context
.
log
as
RequestLogger
;
:
{
id
:
"
user_123
"
}
}
)
;
from
"
nitropack/config
"
;
"
;
env
:
{
service
:
"
my-api
"
}
}
)]
,
from
"
@nestjs/common
"
;
from
"
evlog/nestjs
"
;
()]
,
{}
from
"
evlog/nestjs
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
=>
(
{
token
:
config
.
get
(
"
AXIOM_TOKEN
"
)
}
)
,
"
;
from
"
evlog
"
;
}
from
"
evlog/express
"
;
service
:
"
my-api
"
}
}
)
;
()
;
;
,
(
req
,
res
)
=>
{
(
{
users
:
{
count
:
42
}
}
)
;
users
:
[]
}
)
;
from
"
evlog/express
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
from
"
hono
"
;
from
"
evlog
"
;
EvlogVariables
}
from
"
evlog/hono
"
;
service
:
"
my-api
"
}
}
)
;
Hono
<
EvlogVariables
>
()
;
;
,
(
c
)
=>
{
c
.
get
(
"
log
"
)
;
users
:
{
count
:
42
}
}
)
;
(
{
users
:
[]
}
)
;
c.get('log')
`
and pass it down explicitly, or use Express/Fastify/Elysia if you need
`
useLogger()
`
across async boundaries.
parseError()
`
and pass
`
parsed.status as ContentfulStatusCode
`
to
`
c.json()
`
(Hono types the status argument as
`
ContentfulStatusCode
`
, not
`
number
`
).
}
from
"
evlog
"
;
}
from
"
hono/utils/http-status
"
;
,
c
)
=>
{
"
)
.
error
(
error
)
;
parseError
(
error
)
;
(
message
,
why
:
parsed
.
why
,
fix
:
parsed
.
fix
,
link
:
parsed
.
link
}
,
ContentfulStatusCode
,
from
"
evlog/axiom
"
;
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
"
;
from
"
evlog
"
;
}
from
"
evlog/fastify
"
;
service
:
"
my-api
"
}
}
)
;
(
{
logger
:
false
}
)
;
evlog
)
;
,
async
(
request
)
=>
{
(
{
users
:
{
count
:
42
}
}
)
;
[]
}
;
from
"
evlog/fastify
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
evlog
,
{
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
from
"
elysia
"
;
from
"
evlog
"
;
}
from
"
evlog/elysia
"
;
service
:
"
my-api
"
}
}
)
;
Elysia
()
(
{
log
}
)
=>
{
users
:
{
count
:
42
}
}
)
;
[]
}
;
from
"
evlog/elysia
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
}
from
"
@react-router/dev/config
"
;
from
"
evlog
"
;
from
"
evlog/react-router
"
;
service
:
"
my-api
"
}
}
)
;
Route
.
MiddlewareFunction
[]
=
[
evlog
()]
;
from
"
evlog/react-router
"
;
(
{
params
,
context
}
:
Route
.
LoaderArgs
)
{
context
.
get
(
loggerContext
)
;
user
:
{
id
:
params
.
id
}
}
)
;
[]
}
;
from
"
evlog/react-router
"
;
{
useLogger
()
;
db
:
{
query
:
"
SELECT * FROM users
"
}
}
)
;
from
"
evlog/axiom
"
;
Route
.
MiddlewareFunction
[]
=
[
=>
{
=
process
.
env
.
FLY_REGION
;
=>
{
&&
ctx
.
duration
>
2000
)
ctx
.
shouldKeep
=
true
;
}
from
"
evlog/workers
"
;
service
:
"
edge-api
"
}
}
)
;
:
Request
)
{
createWorkersLogger
(
request
)
;
route
:
"
health
"
}
)
;
new
Response
(
"
ok
"
,
{
status
:
200
}
)
;
status
:
response
.
status
}
)
;
{
as
Error
)
;
status
:
500
}
)
;
"
;
auto-import log, createEvlogError, parseError
//
remove log.debug() in production
inject file:line in dev + prod
/api/logs
"
}
,
}
from
"
evlog
"
;
service
:
"
my-worker
"
,
environment
:
"
production
"
}
}
)
;
(
{
jobId
:
job
.
id
}
)
;
:
job
.
source
,
recordsSynced
:
150
}
)
;
Manual emit required in standalone
createEvlog()
`
), and standalone (
`
initLogger()
`
).
--------------------
|
---------------------------------------------------------------------------------------------------------------------------------------------
|
|
`
string
`
|
`
'app'
`
|
Service name in logs
|
`
|
`
true
`
|
Global toggle (no-ops when false)
|
`
|
`
true
`
in dev
|
Pretty tree format vs JSON
|
`
|
`
false
`
|
Suppress console output. Events still go to drains
|
`
|
All routes
|
Route glob patterns to log
|
`
|
None
|
Route patterns to exclude (takes precedence)
|
`
|
--
|
Route-specific service names
|
`
|
`
'debug'
`
|
Hard threshold for the global
`
log
`
API and client
`
log
`
(not request wide events). Use
`
sampling.rates
`
for probabilistic volume on requests
|
`
|
--
|
Head sampling:
`
{ info: 10, warn: 50 }
`
(0-100%)
|
`
|
--
|
Tail sampling:
`
[{ status: 400 }, { duration: 1000 }]
`
|
`
|
--
|
Drain callback (Next.js, standalone)
|
`
|
--
|
Enrich callback (Next.js)
|
`
|
--
|
Custom tail sampling callback (Next.js)
|
`
|
`
true
`
in production
|
Enabled by default in production.
`
false
`
to disable. Object for fine-grained control
|
--------------------------------
|
|
|
|
|
------------------------------------------------------------------------------------------------
|
`
AXIOM_TOKEN
`
,
`
AXIOM_DATASET
`
|
`
OTLP_ENDPOINT
`
(or
`
OTEL_EXPORTER_OTLP_ENDPOINT
`
)
|
`
HYPERDX_API_KEY
`
(optional
`
HYPERDX_OTLP_ENDPOINT
`
; defaults to
`
https://in-otel.hyperdx.io
`
)
|
`
POSTHOG_API_KEY
`
,
`
POSTHOG_HOST
`
|
`
SENTRY_DSN
`
|
`
BETTER_STACK_SOURCE_TOKEN
`
|
`
DD_API_KEY
`
or
`
DATADOG_API_KEY
`
, optional
`
DD_SITE
`
/
`
DATADOG_LOGS_URL
`
|
|
`
endpoint
`
in code).
`
evlog/browser
`
is deprecated; same API, removed next major
|
`
useRuntimeConfig()
`
. All adapters also read unprefixed variables as fallback.
from
"
evlog/axiom
"
;
)
=>
{
(
"
evlog:drain
"
,
createAxiomDrain
())
;
from
"
evlog/axiom
"
;
{
drain
:
createAxiomDrain
()
}
))
;
from
"
evlog/axiom
"
;
evlog
,
{
drain
:
createAxiomDrain
()
}
)
;
from
"
evlog/axiom
"
;
:
createAxiomDrain
()
}
)
;
from
"
evlog/axiom
"
;
from
"
evlog/pipeline
"
;
<
DrainContext
>
(
{
batch
:
{
size
:
50
}
}
)
;
(
createAxiomDrain
())
;
service
:
"
my-app
"
}
,
drain
:
createAxiomDrain
()
}
)
;
)
for batching, retry, and buffer overflow config.
createRequestSizeEnricher()
`
,
`
createTraceContextEnricher()
`
- all from
`
evlog/enrichers
`
.
}
from
"
evlog/enrichers
"
;
)
=>
{
[
createUserAgentEnricher
()
,
createGeoEnricher
()]
;
(
"
evlog:enrich
"
,
(
ctx
)
=>
{
of
enrichers
)
enricher
(
ctx
)
;
=>
{
of
enrichers
)
enricher
(
ctx
)
;
=
process
.
env
.
VERCEL_REGION
;
**
before
**
any drain sees the data.
**
Enabled by default in production
**
(
`
NODE_ENV === 'production'
`
), disabled in development. Uses
**
smart partial masking
**
- preserving enough context for debugging.
}
'
headers.authorization
'
]
,
'
creditCard
'
]
,
g
]
,
-----------------
|
`
|
`
****1111
`
|
`
|
`
a***@***.com
`
|
`
|
`
***.***.***.100
`
|
`
|
`
+33 ****5678
`
|
`
|
`
eyJ***.***
`
|
`
|
`
Bearer ***
`
|
`
|
`
FR76****189
`
|
createEvlog()
`
), standalone (
`
initLogger()
`
), and all middleware integrations (Hono, Express, Fastify, Elysia, NestJS).
from
"
evlog/ai
"
;
(
event
)
;
//
or any RequestLogger
(
log
)
;
(
{
(
"
anthropic/claude-sonnet-4.6
"
)
,
//
accepts string or model object
streamText
`
, and
`
ToolLoopAgent
`
.
}
from
"
evlog/ai
"
;
(
log
)
;
ToolLoopAgent
(
{
(
"
anthropic/claude-sonnet-4.6
"
)
,
queryDatabase
}
,
,
)]
,
ai.totalDurationMs
`
to the wide event.
usage
}
=
await
embed
(
{
model
:
embeddingModel
,
value
:
query
}
)
;
,
model
:
"
text-embedding-3-small
"
,
dimensions
:
1536
}
)
;
,
model
:
"
text-embedding-3-small
"
,
count
:
documents
.
length
}
)
;
(
log
,
{
3
,
output
:
15
}
,
2.5
,
output
:
10
}
,
ai
`
field
provider
`
,
`
inputTokens
`
,
`
outputTokens
`
,
`
totalTokens
`
,
`
cacheReadTokens
`
,
`
reasoningTokens
`
,
`
finishReason
`
,
`
toolCalls
`
,
`
steps
`
,
`
msToFirstChunk
`
,
`
msToFinish
`
,
`
tokensPerSecond
`
,
`
error
`
,
`
tools
`
(via telemetry integration),
`
totalDurationMs
`
(via telemetry integration),
`
embedding
`
(via
`
captureEmbed
`
),
`
estimatedCost
`
(via
`
cost
`
option).
`
- middleware captures automatically
|
`
- structured
`
ai.*
`
fields in wide event
|
`
|
`
|
`
|
from
"
evlog
"
;
//
or auto-imported in Nuxt
"
Database connection failed
"
,
status
:
500
}
)
;
"
Payment failed
"
,
status
:
402
,
why
:
"
Card declined by issuer
"
}
)
;
req_abc
"
,
resourceId
:
"
proj_123
"
}
,
from
"
evlog
"
;
(
err
)
;
)
for common patterns and templates.
`
|
`
|
`
|
`
/
`
createRequestLogger()
`
|
`
|
`
+ enable
`
redact: true
`
|
|
Use
`
createError({ ..., internal: { ... } })
`
for non-user-facing diagnostics
|
)
for the full checklist.
)
)
)
)