Skip to content

Commit 37b68bd

Browse files
committed
feat(module): add Cloudflare Workers adapter
- Add initWorkersLogger() and createWorkersLogger() for CF Workers - Auto-extract cf-ray as requestId, request.cf context (colo, country, asn) - Add stringify option to skip JSON.stringify for Workers platform - Fix process undefined in Workers env detection (isDev returns false) - Simplify getConsoleMethod to identity function
1 parent b2c0026 commit 37b68bd

15 files changed

Lines changed: 399 additions & 49 deletions

File tree

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,49 @@ async function processSyncJob(job: Job) {
350350
}
351351
```
352352

353+
## Cloudflare Workers
354+
355+
Use the Workers adapter for structured logs and correct platform severity.
356+
357+
```typescript
358+
// src/index.ts
359+
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
360+
361+
initWorkersLogger({
362+
env: { service: 'edge-api' },
363+
})
364+
365+
export default {
366+
async fetch(request: Request) {
367+
const log = createWorkersLogger(request)
368+
369+
try {
370+
log.set({ route: 'health' })
371+
const response = new Response('ok', { status: 200 })
372+
log.emit({ status: response.status })
373+
return response
374+
} catch (error) {
375+
log.error(error as Error)
376+
log.emit({ status: 500 })
377+
throw error
378+
}
379+
},
380+
}
381+
```
382+
383+
Disable invocation logs to avoid duplicate request logs:
384+
385+
```toml
386+
# wrangler.toml
387+
[observability.logs]
388+
invocation_logs = false
389+
```
390+
391+
Notes:
392+
- `requestId` defaults to `cf-ray` when available
393+
- `request.cf` is included (colo, country, asn) unless disabled
394+
- Use `headerAllowlist` to avoid logging sensitive headers
395+
353396
## API Reference
354397

355398
### `initLogger(config)`
@@ -366,6 +409,7 @@ initLogger({
366409
region?: string // Deployment region
367410
},
368411
pretty?: boolean // Pretty print (default: true in dev)
412+
stringify?: boolean // JSON.stringify output (default: true, false for Workers)
369413
include?: string[] // Route patterns to log (glob), e.g. ['/api/**']
370414
sampling?: {
371415
rates?: {
@@ -437,6 +481,34 @@ log.emit() // Emit final event
437481
log.getContext() // Get current context
438482
```
439483

484+
### `initWorkersLogger(options?)`
485+
486+
Initialize evlog for Cloudflare Workers (object logs + correct severity).
487+
488+
```typescript
489+
import { initWorkersLogger } from 'evlog/workers'
490+
491+
initWorkersLogger({
492+
env: { service: 'edge-api' },
493+
})
494+
```
495+
496+
### `createWorkersLogger(request, options?)`
497+
498+
Create a request-scoped logger for Workers. Auto-extracts `cf-ray`, `request.cf`, method, and path.
499+
500+
```typescript
501+
import { createWorkersLogger } from 'evlog/workers'
502+
503+
const log = createWorkersLogger(request, {
504+
requestId: 'custom-id', // Override cf-ray (default: cf-ray header)
505+
headers: ['x-request-id'], // Headers to include (default: none)
506+
})
507+
508+
log.set({ user: { id: '123' } })
509+
log.emit({ status: 200 })
510+
```
511+
440512
### `createError(options)`
441513

442514
Create a structured error with HTTP status support. Import from `evlog` directly to avoid conflicts with Nuxt/Nitro's `createError`.

apps/docs/content/1.getting-started/2.installation.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Installation
33
description: Install evlog in your Nuxt, Nitro, or standalone TypeScript project.
44
---
55

6-
evlog supports multiple environments: Nuxt, Nitro, and standalone TypeScript.
6+
evlog supports multiple environments: Nuxt, Nitro, Cloudflare Workers, and standalone TypeScript.
77

88
## Nuxt
99

@@ -259,6 +259,47 @@ export default defineNitroConfig({
259259
})
260260
```
261261

262+
## Cloudflare Workers
263+
264+
Use the Workers adapter for structured logs and correct platform severity.
265+
266+
```typescript [src/index.ts]
267+
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
268+
269+
initWorkersLogger({
270+
env: { service: 'edge-api' },
271+
})
272+
273+
export default {
274+
async fetch(request: Request) {
275+
const log = createWorkersLogger(request)
276+
277+
try {
278+
log.set({ route: 'health' })
279+
const response = new Response('ok', { status: 200 })
280+
log.emit({ status: response.status })
281+
return response
282+
} catch (error) {
283+
log.error(error as Error)
284+
log.emit({ status: 500 })
285+
throw error
286+
}
287+
},
288+
}
289+
```
290+
291+
Disable invocation logs to avoid duplicate request logs:
292+
293+
```toml [wrangler.toml]
294+
[observability.logs]
295+
invocation_logs = false
296+
```
297+
298+
Notes:
299+
- `requestId` defaults to `cf-ray` when available
300+
- `request.cf` is included (colo, country, asn) unless disabled
301+
- Use `headerAllowlist` to avoid logging sensitive headers
302+
262303
## Standalone TypeScript
263304

264305
Install evlog via your preferred package manager:

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/workers/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Cloudflare Workers Example
2+
3+
```bash
4+
pnpm add evlog
5+
pnpm dlx wrangler dev
6+
```
7+
8+
This example uses `evlog/workers` and disables invocation logs to avoid duplicate request logs.

examples/workers/src/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
2+
3+
initWorkersLogger({
4+
env: { service: 'workers-example' },
5+
})
6+
7+
export default {
8+
async fetch(request: Request) {
9+
const log = createWorkersLogger(request)
10+
11+
try {
12+
log.set({ route: 'health' })
13+
const response = new Response('ok', { status: 200 })
14+
log.emit({ status: response.status })
15+
return response
16+
} catch (error) {
17+
log.error(error as Error)
18+
log.emit({ status: 500 })
19+
throw error
20+
}
21+
},
22+
}

examples/workers/wrangler.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name = "evlog-workers-example"
2+
main = "src/index.ts"
3+
compatibility_date = "2026-02-03"
4+
5+
[observability.logs]
6+
enabled = true
7+
invocation_logs = false

packages/evlog/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,49 @@ async function processSyncJob(job: Job) {
346346
}
347347
```
348348

349+
## Cloudflare Workers
350+
351+
Use the Workers adapter for structured logs and correct platform severity.
352+
353+
```typescript
354+
// src/index.ts
355+
import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
356+
357+
initWorkersLogger({
358+
env: { service: 'edge-api' },
359+
})
360+
361+
export default {
362+
async fetch(request: Request) {
363+
const log = createWorkersLogger(request)
364+
365+
try {
366+
log.set({ route: 'health' })
367+
const response = new Response('ok', { status: 200 })
368+
log.emit({ status: response.status })
369+
return response
370+
} catch (error) {
371+
log.error(error as Error)
372+
log.emit({ status: 500 })
373+
throw error
374+
}
375+
},
376+
}
377+
```
378+
379+
Disable invocation logs to avoid duplicate request logs:
380+
381+
```toml
382+
# wrangler.toml
383+
[observability.logs]
384+
invocation_logs = false
385+
```
386+
387+
Notes:
388+
- `requestId` defaults to `cf-ray` when available
389+
- `request.cf` is included (colo, country, asn) unless disabled
390+
- Use `headerAllowlist` to avoid logging sensitive headers
391+
349392
## API Reference
350393

351394
### `initLogger(config)`
@@ -362,6 +405,7 @@ initLogger({
362405
region?: string // Deployment region
363406
},
364407
pretty?: boolean // Pretty print (default: true in dev)
408+
stringify?: boolean // JSON.stringify output (default: true, false for Workers)
365409
include?: string[] // Route patterns to log (glob), e.g. ['/api/**']
366410
sampling?: {
367411
rates?: { // Head sampling (random per level)
@@ -479,6 +523,34 @@ log.emit() // Emit final event
479523
log.getContext() // Get current context
480524
```
481525

526+
### `initWorkersLogger(options?)`
527+
528+
Initialize evlog for Cloudflare Workers (object logs + correct severity).
529+
530+
```typescript
531+
import { initWorkersLogger } from 'evlog/workers'
532+
533+
initWorkersLogger({
534+
env: { service: 'edge-api' },
535+
})
536+
```
537+
538+
### `createWorkersLogger(request, options?)`
539+
540+
Create a request-scoped logger for Workers. Auto-extracts `cf-ray`, `request.cf`, method, and path.
541+
542+
```typescript
543+
import { createWorkersLogger } from 'evlog/workers'
544+
545+
const log = createWorkersLogger(request, {
546+
requestId: 'custom-id', // Override cf-ray (default: cf-ray header)
547+
headers: ['x-request-id'], // Headers to include (default: none)
548+
})
549+
550+
log.set({ user: { id: '123' } })
551+
log.emit({ status: 200 })
552+
```
553+
482554
### `createError(options)`
483555

484556
Create a structured error with HTTP status support. Import from `evlog` directly to avoid conflicts with Nuxt/Nitro's `createError`.

packages/evlog/build.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default defineBuildConfig({
1414
{ input: 'src/logger', name: 'logger' },
1515
{ input: 'src/utils', name: 'utils' },
1616
{ input: 'src/types', name: 'types' },
17+
{ input: 'src/workers/index', name: 'workers' },
1718
],
1819
declaration: true,
1920
clean: true,

packages/evlog/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
"./nitro": {
3535
"types": "./dist/nitro/plugin.d.mts",
3636
"import": "./dist/nitro/plugin.mjs"
37+
},
38+
"./workers": {
39+
"types": "./dist/workers.d.mts",
40+
"import": "./dist/workers.mjs"
3741
}
3842
},
3943
"main": "./dist/index.mjs",
@@ -48,6 +52,9 @@
4852
],
4953
"nitro": [
5054
"./dist/nitro/plugin.d.mts"
55+
],
56+
"workers": [
57+
"./dist/workers.d.mts"
5158
]
5259
}
5360
},

packages/evlog/src/logger.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let globalEnv: EnvironmentContext = {
99

1010
let globalPretty = isDev()
1111
let globalSampling: SamplingConfig = {}
12+
let globalStringify = true
1213

1314
/**
1415
* Initialize the logger with configuration.
@@ -27,6 +28,7 @@ export function initLogger(config: LoggerConfig = {}): void {
2728

2829
globalPretty = config.pretty ?? isDev()
2930
globalSampling = config.sampling ?? {}
31+
globalStringify = config.stringify ?? true
3032
}
3133

3234
/**
@@ -87,8 +89,10 @@ function emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamp
8789

8890
if (globalPretty) {
8991
prettyPrintWideEvent(formatted)
90-
} else {
92+
} else if (globalStringify) {
9193
console[getConsoleMethod(level)](JSON.stringify(formatted))
94+
} else {
95+
console[getConsoleMethod(level)](formatted)
9296
}
9397

9498
return formatted
@@ -102,9 +106,9 @@ function emitTaggedLog(level: LogLevel, tag: string, message: string): void {
102106
const color = getLevelColor(level)
103107
const timestamp = new Date().toISOString().slice(11, 23)
104108
console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)
105-
} else {
106-
emitWideEvent(level, { tag, message })
109+
return
107110
}
111+
emitWideEvent(level, { tag, message })
108112
}
109113

110114
function formatValue(value: unknown): string {

0 commit comments

Comments
 (0)