evlog 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/adapters/axiom.d.mts +62 -0
- package/dist/adapters/axiom.d.ts +62 -0
- package/dist/adapters/axiom.mjs +65 -0
- package/dist/adapters/otlp.d.mts +83 -0
- package/dist/adapters/otlp.d.ts +83 -0
- package/dist/adapters/otlp.mjs +202 -0
- package/dist/logger.mjs +7 -3
- package/dist/nuxt/module.d.mts +50 -0
- package/dist/nuxt/module.d.ts +50 -0
- package/dist/types.d.mts +6 -0
- package/dist/types.d.ts +6 -0
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.mjs +8 -4
- package/dist/workers.d.mts +45 -0
- package/dist/workers.d.ts +45 -0
- package/dist/workers.mjs +53 -0
- package/package.json +22 -1
package/README.md
CHANGED
|
@@ -346,6 +346,49 @@ async function processSyncJob(job: Job) {
|
|
|
346
346
|
}
|
|
347
347
|
```
|
|
348
348
|
|
|
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
|
+
|
|
349
392
|
## API Reference
|
|
350
393
|
|
|
351
394
|
### `initLogger(config)`
|
|
@@ -362,6 +405,7 @@ initLogger({
|
|
|
362
405
|
region?: string // Deployment region
|
|
363
406
|
},
|
|
364
407
|
pretty?: boolean // Pretty print (default: true in dev)
|
|
408
|
+
stringify?: boolean // JSON.stringify output (default: true, false for Workers)
|
|
365
409
|
include?: string[] // Route patterns to log (glob), e.g. ['/api/**']
|
|
366
410
|
sampling?: {
|
|
367
411
|
rates?: { // Head sampling (random per level)
|
|
@@ -479,6 +523,34 @@ log.emit() // Emit final event
|
|
|
479
523
|
log.getContext() // Get current context
|
|
480
524
|
```
|
|
481
525
|
|
|
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
|
+
|
|
482
554
|
### `createError(options)`
|
|
483
555
|
|
|
484
556
|
Create a structured error with HTTP status support. Import from `evlog` directly to avoid conflicts with Nuxt/Nitro's `createError`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DrainContext, WideEvent } from '../types.mjs';
|
|
2
|
+
|
|
3
|
+
interface AxiomConfig {
|
|
4
|
+
/** Axiom dataset name */
|
|
5
|
+
dataset: string;
|
|
6
|
+
/** Axiom API token */
|
|
7
|
+
token: string;
|
|
8
|
+
/** Organization ID (required for Personal Access Tokens) */
|
|
9
|
+
orgId?: string;
|
|
10
|
+
/** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a drain function for sending logs to Axiom.
|
|
17
|
+
*
|
|
18
|
+
* Configuration priority (highest to lowest):
|
|
19
|
+
* 1. Overrides passed to createAxiomDrain()
|
|
20
|
+
* 2. runtimeConfig.evlog.axiom
|
|
21
|
+
* 3. runtimeConfig.axiom
|
|
22
|
+
* 4. Environment variables: NUXT_AXIOM_*, AXIOM_*
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars
|
|
27
|
+
* nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
|
|
28
|
+
*
|
|
29
|
+
* // With overrides
|
|
30
|
+
* nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
|
|
31
|
+
* dataset: 'my-dataset',
|
|
32
|
+
* }))
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function createAxiomDrain(overrides?: Partial<AxiomConfig>): (ctx: DrainContext) => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Send a single event to Axiom.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* await sendToAxiom(event, {
|
|
42
|
+
* dataset: 'my-logs',
|
|
43
|
+
* token: process.env.AXIOM_TOKEN!,
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Send a batch of events to Axiom.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* await sendBatchToAxiom(events, {
|
|
54
|
+
* dataset: 'my-logs',
|
|
55
|
+
* token: process.env.AXIOM_TOKEN!,
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void>;
|
|
60
|
+
|
|
61
|
+
export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
|
|
62
|
+
export type { AxiomConfig };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DrainContext, WideEvent } from '../types.js';
|
|
2
|
+
|
|
3
|
+
interface AxiomConfig {
|
|
4
|
+
/** Axiom dataset name */
|
|
5
|
+
dataset: string;
|
|
6
|
+
/** Axiom API token */
|
|
7
|
+
token: string;
|
|
8
|
+
/** Organization ID (required for Personal Access Tokens) */
|
|
9
|
+
orgId?: string;
|
|
10
|
+
/** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a drain function for sending logs to Axiom.
|
|
17
|
+
*
|
|
18
|
+
* Configuration priority (highest to lowest):
|
|
19
|
+
* 1. Overrides passed to createAxiomDrain()
|
|
20
|
+
* 2. runtimeConfig.evlog.axiom
|
|
21
|
+
* 3. runtimeConfig.axiom
|
|
22
|
+
* 4. Environment variables: NUXT_AXIOM_*, AXIOM_*
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars
|
|
27
|
+
* nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
|
|
28
|
+
*
|
|
29
|
+
* // With overrides
|
|
30
|
+
* nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
|
|
31
|
+
* dataset: 'my-dataset',
|
|
32
|
+
* }))
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function createAxiomDrain(overrides?: Partial<AxiomConfig>): (ctx: DrainContext) => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Send a single event to Axiom.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* await sendToAxiom(event, {
|
|
42
|
+
* dataset: 'my-logs',
|
|
43
|
+
* token: process.env.AXIOM_TOKEN!,
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Send a batch of events to Axiom.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* await sendBatchToAxiom(events, {
|
|
54
|
+
* dataset: 'my-logs',
|
|
55
|
+
* token: process.env.AXIOM_TOKEN!,
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void>;
|
|
60
|
+
|
|
61
|
+
export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
|
|
62
|
+
export type { AxiomConfig };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function getRuntimeConfig() {
|
|
2
|
+
try {
|
|
3
|
+
const { useRuntimeConfig } = require("nitropack/runtime");
|
|
4
|
+
return useRuntimeConfig();
|
|
5
|
+
} catch {
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function createAxiomDrain(overrides) {
|
|
10
|
+
return async (ctx) => {
|
|
11
|
+
const runtimeConfig = getRuntimeConfig();
|
|
12
|
+
const evlogAxiom = runtimeConfig?.evlog?.axiom;
|
|
13
|
+
const rootAxiom = runtimeConfig?.axiom;
|
|
14
|
+
const config = {
|
|
15
|
+
dataset: overrides?.dataset ?? evlogAxiom?.dataset ?? rootAxiom?.dataset ?? process.env.NUXT_AXIOM_DATASET ?? process.env.AXIOM_DATASET,
|
|
16
|
+
token: overrides?.token ?? evlogAxiom?.token ?? rootAxiom?.token ?? process.env.NUXT_AXIOM_TOKEN ?? process.env.AXIOM_TOKEN,
|
|
17
|
+
orgId: overrides?.orgId ?? evlogAxiom?.orgId ?? rootAxiom?.orgId ?? process.env.NUXT_AXIOM_ORG_ID ?? process.env.AXIOM_ORG_ID,
|
|
18
|
+
baseUrl: overrides?.baseUrl ?? evlogAxiom?.baseUrl ?? rootAxiom?.baseUrl ?? process.env.NUXT_AXIOM_URL ?? process.env.AXIOM_URL,
|
|
19
|
+
timeout: overrides?.timeout ?? evlogAxiom?.timeout ?? rootAxiom?.timeout
|
|
20
|
+
};
|
|
21
|
+
if (!config.dataset || !config.token) {
|
|
22
|
+
console.error("[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await sendToAxiom(ctx.event, config);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error("[evlog/axiom] Failed to send event:", error);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function sendToAxiom(event, config) {
|
|
33
|
+
await sendBatchToAxiom([event], config);
|
|
34
|
+
}
|
|
35
|
+
async function sendBatchToAxiom(events, config) {
|
|
36
|
+
const baseUrl = config.baseUrl ?? "https://api.axiom.co";
|
|
37
|
+
const timeout = config.timeout ?? 5e3;
|
|
38
|
+
const url = `${baseUrl}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`;
|
|
39
|
+
const headers = {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"Authorization": `Bearer ${config.token}`
|
|
42
|
+
};
|
|
43
|
+
if (config.orgId) {
|
|
44
|
+
headers["X-Axiom-Org-Id"] = config.orgId;
|
|
45
|
+
}
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(events),
|
|
53
|
+
signal: controller.signal
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const text = await response.text().catch(() => "Unknown error");
|
|
57
|
+
const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
|
|
58
|
+
throw new Error(`Axiom API error: ${response.status} ${response.statusText} - ${safeText}`);
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { DrainContext, WideEvent } from '../types.mjs';
|
|
2
|
+
|
|
3
|
+
interface OTLPConfig {
|
|
4
|
+
/** OTLP HTTP endpoint (e.g., http://localhost:4318) */
|
|
5
|
+
endpoint: string;
|
|
6
|
+
/** Override service name (defaults to event.service) */
|
|
7
|
+
serviceName?: string;
|
|
8
|
+
/** Additional resource attributes */
|
|
9
|
+
resourceAttributes?: Record<string, string | number | boolean>;
|
|
10
|
+
/** Custom headers (e.g., for authentication) */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/** OTLP Log Record structure */
|
|
16
|
+
interface OTLPLogRecord {
|
|
17
|
+
timeUnixNano: string;
|
|
18
|
+
severityNumber: number;
|
|
19
|
+
severityText: string;
|
|
20
|
+
body: {
|
|
21
|
+
stringValue: string;
|
|
22
|
+
};
|
|
23
|
+
attributes: Array<{
|
|
24
|
+
key: string;
|
|
25
|
+
value: {
|
|
26
|
+
stringValue?: string;
|
|
27
|
+
intValue?: string;
|
|
28
|
+
boolValue?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
traceId?: string;
|
|
32
|
+
spanId?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert an evlog WideEvent to an OTLP LogRecord.
|
|
36
|
+
*/
|
|
37
|
+
declare function toOTLPLogRecord(event: WideEvent): OTLPLogRecord;
|
|
38
|
+
/**
|
|
39
|
+
* Create a drain function for sending logs to an OTLP endpoint.
|
|
40
|
+
*
|
|
41
|
+
* Configuration priority (highest to lowest):
|
|
42
|
+
* 1. Overrides passed to createOTLPDrain()
|
|
43
|
+
* 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)
|
|
44
|
+
* 3. runtimeConfig.otlp (NUXT_OTLP_*)
|
|
45
|
+
* 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // Zero config - reads from runtimeConfig or env vars
|
|
50
|
+
* nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
|
|
51
|
+
*
|
|
52
|
+
* // With overrides
|
|
53
|
+
* nitroApp.hooks.hook('evlog:drain', createOTLPDrain({
|
|
54
|
+
* endpoint: 'http://localhost:4318',
|
|
55
|
+
* }))
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createOTLPDrain(overrides?: Partial<OTLPConfig>): (ctx: DrainContext) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Send a single event to an OTLP endpoint.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* await sendToOTLP(event, {
|
|
65
|
+
* endpoint: 'http://localhost:4318',
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Send a batch of events to an OTLP endpoint.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* await sendBatchToOTLP(events, {
|
|
76
|
+
* endpoint: 'http://localhost:4318',
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void>;
|
|
81
|
+
|
|
82
|
+
export { createOTLPDrain, sendBatchToOTLP, sendToOTLP, toOTLPLogRecord };
|
|
83
|
+
export type { OTLPConfig, OTLPLogRecord };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { DrainContext, WideEvent } from '../types.js';
|
|
2
|
+
|
|
3
|
+
interface OTLPConfig {
|
|
4
|
+
/** OTLP HTTP endpoint (e.g., http://localhost:4318) */
|
|
5
|
+
endpoint: string;
|
|
6
|
+
/** Override service name (defaults to event.service) */
|
|
7
|
+
serviceName?: string;
|
|
8
|
+
/** Additional resource attributes */
|
|
9
|
+
resourceAttributes?: Record<string, string | number | boolean>;
|
|
10
|
+
/** Custom headers (e.g., for authentication) */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/** OTLP Log Record structure */
|
|
16
|
+
interface OTLPLogRecord {
|
|
17
|
+
timeUnixNano: string;
|
|
18
|
+
severityNumber: number;
|
|
19
|
+
severityText: string;
|
|
20
|
+
body: {
|
|
21
|
+
stringValue: string;
|
|
22
|
+
};
|
|
23
|
+
attributes: Array<{
|
|
24
|
+
key: string;
|
|
25
|
+
value: {
|
|
26
|
+
stringValue?: string;
|
|
27
|
+
intValue?: string;
|
|
28
|
+
boolValue?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
traceId?: string;
|
|
32
|
+
spanId?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert an evlog WideEvent to an OTLP LogRecord.
|
|
36
|
+
*/
|
|
37
|
+
declare function toOTLPLogRecord(event: WideEvent): OTLPLogRecord;
|
|
38
|
+
/**
|
|
39
|
+
* Create a drain function for sending logs to an OTLP endpoint.
|
|
40
|
+
*
|
|
41
|
+
* Configuration priority (highest to lowest):
|
|
42
|
+
* 1. Overrides passed to createOTLPDrain()
|
|
43
|
+
* 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)
|
|
44
|
+
* 3. runtimeConfig.otlp (NUXT_OTLP_*)
|
|
45
|
+
* 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // Zero config - reads from runtimeConfig or env vars
|
|
50
|
+
* nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
|
|
51
|
+
*
|
|
52
|
+
* // With overrides
|
|
53
|
+
* nitroApp.hooks.hook('evlog:drain', createOTLPDrain({
|
|
54
|
+
* endpoint: 'http://localhost:4318',
|
|
55
|
+
* }))
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createOTLPDrain(overrides?: Partial<OTLPConfig>): (ctx: DrainContext) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Send a single event to an OTLP endpoint.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* await sendToOTLP(event, {
|
|
65
|
+
* endpoint: 'http://localhost:4318',
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Send a batch of events to an OTLP endpoint.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* await sendBatchToOTLP(events, {
|
|
76
|
+
* endpoint: 'http://localhost:4318',
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void>;
|
|
81
|
+
|
|
82
|
+
export { createOTLPDrain, sendBatchToOTLP, sendToOTLP, toOTLPLogRecord };
|
|
83
|
+
export type { OTLPConfig, OTLPLogRecord };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const SEVERITY_MAP = {
|
|
2
|
+
debug: 5,
|
|
3
|
+
// DEBUG
|
|
4
|
+
info: 9,
|
|
5
|
+
// INFO
|
|
6
|
+
warn: 13,
|
|
7
|
+
// WARN
|
|
8
|
+
error: 17
|
|
9
|
+
// ERROR
|
|
10
|
+
};
|
|
11
|
+
const SEVERITY_TEXT_MAP = {
|
|
12
|
+
debug: "DEBUG",
|
|
13
|
+
info: "INFO",
|
|
14
|
+
warn: "WARN",
|
|
15
|
+
error: "ERROR"
|
|
16
|
+
};
|
|
17
|
+
function getRuntimeConfig() {
|
|
18
|
+
try {
|
|
19
|
+
const { useRuntimeConfig } = require("nitropack/runtime");
|
|
20
|
+
return useRuntimeConfig();
|
|
21
|
+
} catch {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function toAttributeValue(value) {
|
|
26
|
+
if (typeof value === "boolean") {
|
|
27
|
+
return { boolValue: value };
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
30
|
+
return { intValue: String(value) };
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === "string") {
|
|
33
|
+
return { stringValue: value };
|
|
34
|
+
}
|
|
35
|
+
return { stringValue: JSON.stringify(value) };
|
|
36
|
+
}
|
|
37
|
+
function toOTLPLogRecord(event) {
|
|
38
|
+
const timestamp = new Date(event.timestamp).getTime() * 1e6;
|
|
39
|
+
const { level, traceId, spanId, ...rest } = event;
|
|
40
|
+
delete rest.timestamp;
|
|
41
|
+
delete rest.service;
|
|
42
|
+
delete rest.environment;
|
|
43
|
+
delete rest.version;
|
|
44
|
+
delete rest.commitHash;
|
|
45
|
+
delete rest.region;
|
|
46
|
+
const attributes = [];
|
|
47
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
48
|
+
if (value !== void 0 && value !== null) {
|
|
49
|
+
attributes.push({
|
|
50
|
+
key,
|
|
51
|
+
value: toAttributeValue(value)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const record = {
|
|
56
|
+
timeUnixNano: String(timestamp),
|
|
57
|
+
severityNumber: SEVERITY_MAP[level] ?? 9,
|
|
58
|
+
severityText: SEVERITY_TEXT_MAP[level] ?? "INFO",
|
|
59
|
+
body: { stringValue: JSON.stringify(event) },
|
|
60
|
+
attributes
|
|
61
|
+
};
|
|
62
|
+
if (typeof traceId === "string") {
|
|
63
|
+
record.traceId = traceId;
|
|
64
|
+
}
|
|
65
|
+
if (typeof spanId === "string") {
|
|
66
|
+
record.spanId = spanId;
|
|
67
|
+
}
|
|
68
|
+
return record;
|
|
69
|
+
}
|
|
70
|
+
function buildResourceAttributes(event, config) {
|
|
71
|
+
const attributes = [];
|
|
72
|
+
attributes.push({
|
|
73
|
+
key: "service.name",
|
|
74
|
+
value: { stringValue: config.serviceName ?? event.service }
|
|
75
|
+
});
|
|
76
|
+
if (event.environment) {
|
|
77
|
+
attributes.push({
|
|
78
|
+
key: "deployment.environment",
|
|
79
|
+
value: { stringValue: event.environment }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (event.version) {
|
|
83
|
+
attributes.push({
|
|
84
|
+
key: "service.version",
|
|
85
|
+
value: { stringValue: event.version }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (event.region) {
|
|
89
|
+
attributes.push({
|
|
90
|
+
key: "cloud.region",
|
|
91
|
+
value: { stringValue: event.region }
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (event.commitHash) {
|
|
95
|
+
attributes.push({
|
|
96
|
+
key: "vcs.commit.id",
|
|
97
|
+
value: { stringValue: event.commitHash }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (config.resourceAttributes) {
|
|
101
|
+
for (const [key, value] of Object.entries(config.resourceAttributes)) {
|
|
102
|
+
attributes.push({
|
|
103
|
+
key,
|
|
104
|
+
value: toAttributeValue(value)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return attributes;
|
|
109
|
+
}
|
|
110
|
+
function createOTLPDrain(overrides) {
|
|
111
|
+
return async (ctx) => {
|
|
112
|
+
const runtimeConfig = getRuntimeConfig();
|
|
113
|
+
const evlogOtlp = runtimeConfig?.evlog?.otlp;
|
|
114
|
+
const rootOtlp = runtimeConfig?.otlp;
|
|
115
|
+
const getHeadersFromEnv = () => {
|
|
116
|
+
const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS;
|
|
117
|
+
if (headersEnv) {
|
|
118
|
+
const headers = {};
|
|
119
|
+
const decoded = decodeURIComponent(headersEnv);
|
|
120
|
+
for (const pair of decoded.split(",")) {
|
|
121
|
+
const eqIndex = pair.indexOf("=");
|
|
122
|
+
if (eqIndex > 0) {
|
|
123
|
+
const key = pair.slice(0, eqIndex).trim();
|
|
124
|
+
const value = pair.slice(eqIndex + 1).trim();
|
|
125
|
+
if (key && value) {
|
|
126
|
+
headers[key] = value;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (Object.keys(headers).length > 0) return headers;
|
|
131
|
+
}
|
|
132
|
+
const auth = process.env.NUXT_OTLP_AUTH;
|
|
133
|
+
if (auth) {
|
|
134
|
+
return { Authorization: auth };
|
|
135
|
+
}
|
|
136
|
+
return void 0;
|
|
137
|
+
};
|
|
138
|
+
const config = {
|
|
139
|
+
endpoint: overrides?.endpoint ?? evlogOtlp?.endpoint ?? rootOtlp?.endpoint ?? process.env.NUXT_OTLP_ENDPOINT ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
140
|
+
serviceName: overrides?.serviceName ?? evlogOtlp?.serviceName ?? rootOtlp?.serviceName ?? process.env.NUXT_OTLP_SERVICE_NAME ?? process.env.OTEL_SERVICE_NAME,
|
|
141
|
+
headers: overrides?.headers ?? evlogOtlp?.headers ?? rootOtlp?.headers ?? getHeadersFromEnv(),
|
|
142
|
+
resourceAttributes: overrides?.resourceAttributes ?? evlogOtlp?.resourceAttributes ?? rootOtlp?.resourceAttributes,
|
|
143
|
+
timeout: overrides?.timeout ?? evlogOtlp?.timeout ?? rootOtlp?.timeout
|
|
144
|
+
};
|
|
145
|
+
if (!config.endpoint) {
|
|
146
|
+
console.error("[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await sendToOTLP(ctx.event, config);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[evlog/otlp] Failed to send event:", error);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function sendToOTLP(event, config) {
|
|
157
|
+
await sendBatchToOTLP([event], config);
|
|
158
|
+
}
|
|
159
|
+
async function sendBatchToOTLP(events, config) {
|
|
160
|
+
if (events.length === 0) return;
|
|
161
|
+
const timeout = config.timeout ?? 5e3;
|
|
162
|
+
const url = `${config.endpoint.replace(/\/$/, "")}/v1/logs`;
|
|
163
|
+
const [firstEvent] = events;
|
|
164
|
+
const resourceAttributes = buildResourceAttributes(firstEvent, config);
|
|
165
|
+
const logRecords = events.map(toOTLPLogRecord);
|
|
166
|
+
const payload = {
|
|
167
|
+
resourceLogs: [
|
|
168
|
+
{
|
|
169
|
+
resource: { attributes: resourceAttributes },
|
|
170
|
+
scopeLogs: [
|
|
171
|
+
{
|
|
172
|
+
scope: { name: "evlog", version: "1.0.0" },
|
|
173
|
+
logRecords
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
const headers = {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
...config.headers
|
|
182
|
+
};
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers,
|
|
189
|
+
body: JSON.stringify(payload),
|
|
190
|
+
signal: controller.signal
|
|
191
|
+
});
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
const text = await response.text().catch(() => "Unknown error");
|
|
194
|
+
const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
|
|
195
|
+
throw new Error(`OTLP API error: ${response.status} ${response.statusText} - ${safeText}`);
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
clearTimeout(timeoutId);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export { createOTLPDrain, sendBatchToOTLP, sendToOTLP, toOTLPLogRecord };
|
package/dist/logger.mjs
CHANGED
|
@@ -7,6 +7,7 @@ let globalEnv = {
|
|
|
7
7
|
};
|
|
8
8
|
let globalPretty = isDev();
|
|
9
9
|
let globalSampling = {};
|
|
10
|
+
let globalStringify = true;
|
|
10
11
|
function initLogger(config = {}) {
|
|
11
12
|
const detected = detectEnvironment();
|
|
12
13
|
globalEnv = {
|
|
@@ -18,6 +19,7 @@ function initLogger(config = {}) {
|
|
|
18
19
|
};
|
|
19
20
|
globalPretty = config.pretty ?? isDev();
|
|
20
21
|
globalSampling = config.sampling ?? {};
|
|
22
|
+
globalStringify = config.stringify ?? true;
|
|
21
23
|
}
|
|
22
24
|
function shouldSample(level) {
|
|
23
25
|
const { rates } = globalSampling;
|
|
@@ -57,8 +59,10 @@ function emitWideEvent(level, event, skipSamplingCheck = false) {
|
|
|
57
59
|
};
|
|
58
60
|
if (globalPretty) {
|
|
59
61
|
prettyPrintWideEvent(formatted);
|
|
60
|
-
} else {
|
|
62
|
+
} else if (globalStringify) {
|
|
61
63
|
console[getConsoleMethod(level)](JSON.stringify(formatted));
|
|
64
|
+
} else {
|
|
65
|
+
console[getConsoleMethod(level)](formatted);
|
|
62
66
|
}
|
|
63
67
|
return formatted;
|
|
64
68
|
}
|
|
@@ -70,9 +74,9 @@ function emitTaggedLog(level, tag, message) {
|
|
|
70
74
|
const color = getLevelColor(level);
|
|
71
75
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
72
76
|
console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`);
|
|
73
|
-
|
|
74
|
-
emitWideEvent(level, { tag, message });
|
|
77
|
+
return;
|
|
75
78
|
}
|
|
79
|
+
emitWideEvent(level, { tag, message });
|
|
76
80
|
}
|
|
77
81
|
function formatValue(value) {
|
|
78
82
|
if (value === null || value === void 0) {
|
package/dist/nuxt/module.d.mts
CHANGED
|
@@ -54,6 +54,56 @@ interface ModuleOptions {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
transport?: TransportConfig;
|
|
57
|
+
/**
|
|
58
|
+
* Axiom adapter configuration.
|
|
59
|
+
* When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* axiom: {
|
|
64
|
+
* dataset: 'my-app-logs',
|
|
65
|
+
* token: process.env.AXIOM_TOKEN,
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
axiom?: {
|
|
70
|
+
/** Axiom dataset name */
|
|
71
|
+
dataset: string;
|
|
72
|
+
/** Axiom API token */
|
|
73
|
+
token: string;
|
|
74
|
+
/** Organization ID (required for Personal Access Tokens) */
|
|
75
|
+
orgId?: string;
|
|
76
|
+
/** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
79
|
+
timeout?: number;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* OTLP adapter configuration.
|
|
83
|
+
* When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* otlp: {
|
|
88
|
+
* endpoint: 'http://localhost:4318',
|
|
89
|
+
* headers: {
|
|
90
|
+
* 'Authorization': `Basic ${process.env.GRAFANA_TOKEN}`,
|
|
91
|
+
* },
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
otlp?: {
|
|
96
|
+
/** OTLP HTTP endpoint (e.g., http://localhost:4318) */
|
|
97
|
+
endpoint: string;
|
|
98
|
+
/** Override service name (defaults to event.service) */
|
|
99
|
+
serviceName?: string;
|
|
100
|
+
/** Additional resource attributes */
|
|
101
|
+
resourceAttributes?: Record<string, string | number | boolean>;
|
|
102
|
+
/** Custom headers (e.g., for authentication) */
|
|
103
|
+
headers?: Record<string, string>;
|
|
104
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
105
|
+
timeout?: number;
|
|
106
|
+
};
|
|
57
107
|
}
|
|
58
108
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
59
109
|
|
package/dist/nuxt/module.d.ts
CHANGED
|
@@ -54,6 +54,56 @@ interface ModuleOptions {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
transport?: TransportConfig;
|
|
57
|
+
/**
|
|
58
|
+
* Axiom adapter configuration.
|
|
59
|
+
* When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* axiom: {
|
|
64
|
+
* dataset: 'my-app-logs',
|
|
65
|
+
* token: process.env.AXIOM_TOKEN,
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
axiom?: {
|
|
70
|
+
/** Axiom dataset name */
|
|
71
|
+
dataset: string;
|
|
72
|
+
/** Axiom API token */
|
|
73
|
+
token: string;
|
|
74
|
+
/** Organization ID (required for Personal Access Tokens) */
|
|
75
|
+
orgId?: string;
|
|
76
|
+
/** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
79
|
+
timeout?: number;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* OTLP adapter configuration.
|
|
83
|
+
* When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* otlp: {
|
|
88
|
+
* endpoint: 'http://localhost:4318',
|
|
89
|
+
* headers: {
|
|
90
|
+
* 'Authorization': `Basic ${process.env.GRAFANA_TOKEN}`,
|
|
91
|
+
* },
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
otlp?: {
|
|
96
|
+
/** OTLP HTTP endpoint (e.g., http://localhost:4318) */
|
|
97
|
+
endpoint: string;
|
|
98
|
+
/** Override service name (defaults to event.service) */
|
|
99
|
+
serviceName?: string;
|
|
100
|
+
/** Additional resource attributes */
|
|
101
|
+
resourceAttributes?: Record<string, string | number | boolean>;
|
|
102
|
+
/** Custom headers (e.g., for authentication) */
|
|
103
|
+
headers?: Record<string, string>;
|
|
104
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
105
|
+
timeout?: number;
|
|
106
|
+
};
|
|
57
107
|
}
|
|
58
108
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
59
109
|
|
package/dist/types.d.mts
CHANGED
|
@@ -185,6 +185,12 @@ interface LoggerConfig {
|
|
|
185
185
|
pretty?: boolean;
|
|
186
186
|
/** Sampling configuration for filtering logs */
|
|
187
187
|
sampling?: SamplingConfig;
|
|
188
|
+
/**
|
|
189
|
+
* When pretty is disabled, emit JSON strings (default) or raw objects.
|
|
190
|
+
* Set to false for environments like Cloudflare Workers that expect objects.
|
|
191
|
+
* @default true
|
|
192
|
+
*/
|
|
193
|
+
stringify?: boolean;
|
|
188
194
|
}
|
|
189
195
|
/**
|
|
190
196
|
* Base structure for all wide events
|
package/dist/types.d.ts
CHANGED
|
@@ -185,6 +185,12 @@ interface LoggerConfig {
|
|
|
185
185
|
pretty?: boolean;
|
|
186
186
|
/** Sampling configuration for filtering logs */
|
|
187
187
|
sampling?: SamplingConfig;
|
|
188
|
+
/**
|
|
189
|
+
* When pretty is disabled, emit JSON strings (default) or raw objects.
|
|
190
|
+
* Set to false for environments like Cloudflare Workers that expect objects.
|
|
191
|
+
* @default true
|
|
192
|
+
*/
|
|
193
|
+
stringify?: boolean;
|
|
188
194
|
}
|
|
189
195
|
/**
|
|
190
196
|
* Base structure for all wide events
|
package/dist/utils.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ declare function isServer(): boolean;
|
|
|
5
5
|
declare function isClient(): boolean;
|
|
6
6
|
declare function isDev(): boolean;
|
|
7
7
|
declare function detectEnvironment(): Partial<EnvironmentContext>;
|
|
8
|
-
declare function getConsoleMethod(level: LogLevel):
|
|
8
|
+
declare function getConsoleMethod(level: LogLevel): LogLevel;
|
|
9
9
|
declare const colors: {
|
|
10
10
|
readonly reset: "\u001B[0m";
|
|
11
11
|
readonly bold: "\u001B[1m";
|
package/dist/utils.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ declare function isServer(): boolean;
|
|
|
5
5
|
declare function isClient(): boolean;
|
|
6
6
|
declare function isDev(): boolean;
|
|
7
7
|
declare function detectEnvironment(): Partial<EnvironmentContext>;
|
|
8
|
-
declare function getConsoleMethod(level: LogLevel):
|
|
8
|
+
declare function getConsoleMethod(level: LogLevel): LogLevel;
|
|
9
9
|
declare const colors: {
|
|
10
10
|
readonly reset: "\u001B[0m";
|
|
11
11
|
readonly bold: "\u001B[1m";
|
package/dist/utils.mjs
CHANGED
|
@@ -11,15 +11,19 @@ function isClient() {
|
|
|
11
11
|
return typeof window !== "undefined";
|
|
12
12
|
}
|
|
13
13
|
function isDev() {
|
|
14
|
-
if (typeof process !== "undefined"
|
|
14
|
+
if (typeof process !== "undefined") {
|
|
15
15
|
return process.env.NODE_ENV !== "production";
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
if (typeof window !== "undefined") {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
18
21
|
}
|
|
19
22
|
function detectEnvironment() {
|
|
20
23
|
const env = typeof process !== "undefined" ? process.env : {};
|
|
24
|
+
const defaultEnvironment = isDev() ? "development" : "production";
|
|
21
25
|
return {
|
|
22
|
-
environment: env.NODE_ENV ||
|
|
26
|
+
environment: env.NODE_ENV || defaultEnvironment,
|
|
23
27
|
service: env.SERVICE_NAME || "app",
|
|
24
28
|
version: env.APP_VERSION,
|
|
25
29
|
commitHash: env.COMMIT_SHA || env.GITHUB_SHA || env.VERCEL_GIT_COMMIT_SHA || env.CF_PAGES_COMMIT_SHA,
|
|
@@ -27,7 +31,7 @@ function detectEnvironment() {
|
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
function getConsoleMethod(level) {
|
|
30
|
-
return level
|
|
34
|
+
return level;
|
|
31
35
|
}
|
|
32
36
|
const colors = {
|
|
33
37
|
reset: "\x1B[0m",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RequestLogger, LoggerConfig } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for createWorkersLogger
|
|
5
|
+
*/
|
|
6
|
+
interface WorkersLoggerOptions {
|
|
7
|
+
/** Override the request ID (default: cf-ray header) */
|
|
8
|
+
requestId?: string;
|
|
9
|
+
/** Headers to include in logs (default: none) */
|
|
10
|
+
headers?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Initialize evlog for Cloudflare Workers.
|
|
14
|
+
* Call once at module scope.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* initWorkersLogger({
|
|
19
|
+
* env: { service: 'my-api' },
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function initWorkersLogger(options?: LoggerConfig): void;
|
|
24
|
+
/**
|
|
25
|
+
* Create a request-scoped logger for Cloudflare Workers.
|
|
26
|
+
* Auto-extracts cf-ray, request.cf context, method, and path.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* export default {
|
|
31
|
+
* async fetch(request: Request) {
|
|
32
|
+
* const log = createWorkersLogger(request)
|
|
33
|
+
*
|
|
34
|
+
* log.set({ user: { id: '123' } })
|
|
35
|
+
* log.emit({ status: 200 })
|
|
36
|
+
*
|
|
37
|
+
* return new Response('ok')
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createWorkersLogger(request: Request, options?: WorkersLoggerOptions): RequestLogger;
|
|
43
|
+
|
|
44
|
+
export { createWorkersLogger, initWorkersLogger };
|
|
45
|
+
export type { WorkersLoggerOptions };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RequestLogger, LoggerConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for createWorkersLogger
|
|
5
|
+
*/
|
|
6
|
+
interface WorkersLoggerOptions {
|
|
7
|
+
/** Override the request ID (default: cf-ray header) */
|
|
8
|
+
requestId?: string;
|
|
9
|
+
/** Headers to include in logs (default: none) */
|
|
10
|
+
headers?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Initialize evlog for Cloudflare Workers.
|
|
14
|
+
* Call once at module scope.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* initWorkersLogger({
|
|
19
|
+
* env: { service: 'my-api' },
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function initWorkersLogger(options?: LoggerConfig): void;
|
|
24
|
+
/**
|
|
25
|
+
* Create a request-scoped logger for Cloudflare Workers.
|
|
26
|
+
* Auto-extracts cf-ray, request.cf context, method, and path.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* export default {
|
|
31
|
+
* async fetch(request: Request) {
|
|
32
|
+
* const log = createWorkersLogger(request)
|
|
33
|
+
*
|
|
34
|
+
* log.set({ user: { id: '123' } })
|
|
35
|
+
* log.emit({ status: 200 })
|
|
36
|
+
*
|
|
37
|
+
* return new Response('ok')
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createWorkersLogger(request: Request, options?: WorkersLoggerOptions): RequestLogger;
|
|
43
|
+
|
|
44
|
+
export { createWorkersLogger, initWorkersLogger };
|
|
45
|
+
export type { WorkersLoggerOptions };
|
package/dist/workers.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createRequestLogger, initLogger } from './logger.mjs';
|
|
2
|
+
import 'defu';
|
|
3
|
+
import './utils.mjs';
|
|
4
|
+
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function collectHeaders(headers, include) {
|
|
9
|
+
if (!include || include.length === 0) return void 0;
|
|
10
|
+
const normalized = new Set(include.map((h) => h.toLowerCase()));
|
|
11
|
+
const result = {};
|
|
12
|
+
headers.forEach((value, key) => {
|
|
13
|
+
if (normalized.has(key.toLowerCase())) {
|
|
14
|
+
result[key] = value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
18
|
+
}
|
|
19
|
+
function initWorkersLogger(options = {}) {
|
|
20
|
+
initLogger({
|
|
21
|
+
...options,
|
|
22
|
+
pretty: false,
|
|
23
|
+
stringify: false
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function pickCfContext(request) {
|
|
27
|
+
const cf = Reflect.get(request, "cf");
|
|
28
|
+
if (!isRecord(cf)) return {};
|
|
29
|
+
const out = {};
|
|
30
|
+
if (typeof cf.colo === "string") out.colo = cf.colo;
|
|
31
|
+
if (typeof cf.country === "string") out.country = cf.country;
|
|
32
|
+
if (typeof cf.asn === "number") out.asn = cf.asn;
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function createWorkersLogger(request, options = {}) {
|
|
36
|
+
const url = new URL(request.url);
|
|
37
|
+
const cfRay = request.headers.get("cf-ray") ?? void 0;
|
|
38
|
+
const traceparent = request.headers.get("traceparent") ?? void 0;
|
|
39
|
+
const log = createRequestLogger({
|
|
40
|
+
method: request.method,
|
|
41
|
+
path: url.pathname,
|
|
42
|
+
requestId: options.requestId ?? cfRay
|
|
43
|
+
});
|
|
44
|
+
log.set({
|
|
45
|
+
cfRay,
|
|
46
|
+
traceparent,
|
|
47
|
+
...pickCfContext(request),
|
|
48
|
+
...options.headers ? { requestHeaders: collectHeaders(request.headers, options.headers) } : {}
|
|
49
|
+
});
|
|
50
|
+
return log;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { createWorkersLogger, initWorkersLogger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evlog",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Wide event logging library with structured error handling. Inspired by LoggingSucks.",
|
|
5
5
|
"author": "HugoRCD <contact@hrcd.fr>",
|
|
6
6
|
"homepage": "https://evlog.dev",
|
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
"./nitro": {
|
|
35
35
|
"types": "./dist/nitro/plugin.d.mts",
|
|
36
36
|
"import": "./dist/nitro/plugin.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./workers": {
|
|
39
|
+
"types": "./dist/workers.d.mts",
|
|
40
|
+
"import": "./dist/workers.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./axiom": {
|
|
43
|
+
"types": "./dist/adapters/axiom.d.mts",
|
|
44
|
+
"import": "./dist/adapters/axiom.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./otlp": {
|
|
47
|
+
"types": "./dist/adapters/otlp.d.mts",
|
|
48
|
+
"import": "./dist/adapters/otlp.mjs"
|
|
37
49
|
}
|
|
38
50
|
},
|
|
39
51
|
"main": "./dist/index.mjs",
|
|
@@ -48,6 +60,15 @@
|
|
|
48
60
|
],
|
|
49
61
|
"nitro": [
|
|
50
62
|
"./dist/nitro/plugin.d.mts"
|
|
63
|
+
],
|
|
64
|
+
"workers": [
|
|
65
|
+
"./dist/workers.d.mts"
|
|
66
|
+
],
|
|
67
|
+
"axiom": [
|
|
68
|
+
"./dist/adapters/axiom.d.mts"
|
|
69
|
+
],
|
|
70
|
+
"otlp": [
|
|
71
|
+
"./dist/adapters/otlp.d.mts"
|
|
51
72
|
]
|
|
52
73
|
}
|
|
53
74
|
},
|