evlog 1.3.0 → 1.4.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 +81 -1
- package/dist/nitro/plugin.mjs +24 -8
- package/dist/nuxt/module.d.mts +15 -1
- package/dist/nuxt/module.d.ts +15 -1
- package/dist/runtime/server/useLogger.d.mts +10 -1
- package/dist/runtime/server/useLogger.d.ts +10 -1
- package/dist/runtime/server/useLogger.mjs +4 -1
- package/dist/types.d.mts +18 -2
- package/dist/types.d.ts +18 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
[](https://npmjs.com/package/evlog)
|
|
4
4
|
[](https://npm.chart.dev/evlog)
|
|
5
5
|
[](https://github.com/HugoRCD/evlog/actions/workflows/ci.yml)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://nuxt.com/)
|
|
8
|
+
[](https://evlog.dev)
|
|
8
9
|
[](https://github.com/HugoRCD/evlog/blob/main/LICENSE)
|
|
9
10
|
|
|
10
11
|
**Your logs are lying to you.**
|
|
@@ -389,6 +390,85 @@ Notes:
|
|
|
389
390
|
- `request.cf` is included (colo, country, asn) unless disabled
|
|
390
391
|
- Use `headerAllowlist` to avoid logging sensitive headers
|
|
391
392
|
|
|
393
|
+
## Adapters
|
|
394
|
+
|
|
395
|
+
Send your logs to external observability platforms with built-in adapters.
|
|
396
|
+
|
|
397
|
+
### Axiom
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// server/plugins/evlog-drain.ts
|
|
401
|
+
import { createAxiomDrain } from 'evlog/axiom'
|
|
402
|
+
|
|
403
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
404
|
+
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
|
|
405
|
+
})
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Set environment variables:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
NUXT_AXIOM_TOKEN=xaat-your-token
|
|
412
|
+
NUXT_AXIOM_DATASET=your-dataset
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### OTLP (OpenTelemetry)
|
|
416
|
+
|
|
417
|
+
Works with Grafana, Datadog, Honeycomb, and any OTLP-compatible backend.
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// server/plugins/evlog-drain.ts
|
|
421
|
+
import { createOTLPDrain } from 'evlog/otlp'
|
|
422
|
+
|
|
423
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
424
|
+
nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
|
|
425
|
+
})
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Set environment variables:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
NUXT_OTLP_ENDPOINT=http://localhost:4318
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Multiple Destinations
|
|
435
|
+
|
|
436
|
+
Send logs to multiple services:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// server/plugins/evlog-drain.ts
|
|
440
|
+
import { createAxiomDrain } from 'evlog/axiom'
|
|
441
|
+
import { createOTLPDrain } from 'evlog/otlp'
|
|
442
|
+
|
|
443
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
444
|
+
const axiom = createAxiomDrain()
|
|
445
|
+
const otlp = createOTLPDrain()
|
|
446
|
+
|
|
447
|
+
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
|
|
448
|
+
await Promise.allSettled([axiom(ctx), otlp(ctx)])
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Custom Adapters
|
|
454
|
+
|
|
455
|
+
Build your own adapter for any destination:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// server/plugins/evlog-drain.ts
|
|
459
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
460
|
+
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
|
|
461
|
+
await fetch('https://your-service.com/logs', {
|
|
462
|
+
method: 'POST',
|
|
463
|
+
headers: { 'Content-Type': 'application/json' },
|
|
464
|
+
body: JSON.stringify(ctx.event),
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
> See the [full documentation](https://evlog.hrcd.fr/adapters/overview) for adapter configuration options, troubleshooting, and advanced patterns.
|
|
471
|
+
|
|
392
472
|
## API Reference
|
|
393
473
|
|
|
394
474
|
### `initLogger(config)`
|
package/dist/nitro/plugin.mjs
CHANGED
|
@@ -15,6 +15,15 @@ function shouldLog(path, include, exclude) {
|
|
|
15
15
|
}
|
|
16
16
|
return include.some((pattern) => matchesPattern(path, pattern));
|
|
17
17
|
}
|
|
18
|
+
function getServiceForPath(path, routes) {
|
|
19
|
+
if (!routes) return void 0;
|
|
20
|
+
for (const [pattern, config] of Object.entries(routes)) {
|
|
21
|
+
if (matchesPattern(path, pattern)) {
|
|
22
|
+
return config.service;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
18
27
|
const SENSITIVE_HEADERS = [
|
|
19
28
|
"authorization",
|
|
20
29
|
"cookie",
|
|
@@ -46,14 +55,17 @@ function getResponseStatus(event) {
|
|
|
46
55
|
return 200;
|
|
47
56
|
}
|
|
48
57
|
function callDrainHook(nitroApp, emittedEvent, event) {
|
|
49
|
-
if (emittedEvent)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
if (!emittedEvent) return;
|
|
59
|
+
const drainPromise = nitroApp.hooks.callHook("evlog:drain", {
|
|
60
|
+
event: emittedEvent,
|
|
61
|
+
request: { method: event.method, path: event.path, requestId: event.context.requestId },
|
|
62
|
+
headers: getSafeHeaders(event)
|
|
63
|
+
}).catch((err) => {
|
|
64
|
+
console.error("[evlog] drain failed:", err);
|
|
65
|
+
});
|
|
66
|
+
const waitUntil = event.context.cloudflare?.context?.waitUntil ?? event.context.waitUntil;
|
|
67
|
+
if (typeof waitUntil === "function") {
|
|
68
|
+
waitUntil(drainPromise);
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
const plugin = defineNitroPlugin((nitroApp) => {
|
|
@@ -75,6 +87,10 @@ const plugin = defineNitroPlugin((nitroApp) => {
|
|
|
75
87
|
path: e.path,
|
|
76
88
|
requestId: e.context.requestId || crypto.randomUUID()
|
|
77
89
|
});
|
|
90
|
+
const routeService = getServiceForPath(e.path, evlogConfig?.routes);
|
|
91
|
+
if (routeService) {
|
|
92
|
+
log.set({ service: routeService });
|
|
93
|
+
}
|
|
78
94
|
e.context.log = log;
|
|
79
95
|
});
|
|
80
96
|
nitroApp.hooks.hook("error", async (error, { event }) => {
|
package/dist/nuxt/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { EnvironmentContext, SamplingConfig, TransportConfig } from '../types.mjs';
|
|
2
|
+
import { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types.mjs';
|
|
3
3
|
|
|
4
4
|
interface ModuleOptions {
|
|
5
5
|
/**
|
|
@@ -25,6 +25,20 @@ interface ModuleOptions {
|
|
|
25
25
|
* @example ['/api/_nuxt_icon/**', '/health']
|
|
26
26
|
*/
|
|
27
27
|
exclude?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Route-specific service configuration.
|
|
30
|
+
* Allows setting different service names for different routes.
|
|
31
|
+
* Patterns are matched using glob syntax.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* routes: {
|
|
36
|
+
* '/api/foo/**': { service: 'service1' },
|
|
37
|
+
* '/api/bar/**': { service: 'service2' }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
routes?: Record<string, RouteConfig>;
|
|
28
42
|
/**
|
|
29
43
|
* Sampling configuration for filtering logs.
|
|
30
44
|
* Allows configuring what percentage of logs to keep per level.
|
package/dist/nuxt/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { EnvironmentContext, SamplingConfig, TransportConfig } from '../types.js';
|
|
2
|
+
import { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types.js';
|
|
3
3
|
|
|
4
4
|
interface ModuleOptions {
|
|
5
5
|
/**
|
|
@@ -25,6 +25,20 @@ interface ModuleOptions {
|
|
|
25
25
|
* @example ['/api/_nuxt_icon/**', '/health']
|
|
26
26
|
*/
|
|
27
27
|
exclude?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Route-specific service configuration.
|
|
30
|
+
* Allows setting different service names for different routes.
|
|
31
|
+
* Patterns are matched using glob syntax.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* routes: {
|
|
36
|
+
* '/api/foo/**': { service: 'service1' },
|
|
37
|
+
* '/api/bar/**': { service: 'service2' }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
routes?: Record<string, RouteConfig>;
|
|
28
42
|
/**
|
|
29
43
|
* Sampling configuration for filtering logs.
|
|
30
44
|
* Allows configuring what percentage of logs to keep per level.
|
|
@@ -4,6 +4,7 @@ import { ServerEvent, RequestLogger } from '../../types.mjs';
|
|
|
4
4
|
* Returns the request logger attached to the given server event.
|
|
5
5
|
*
|
|
6
6
|
* @param event - The current server event containing the context with the logger.
|
|
7
|
+
* @param service - Optional service name to override the default service.
|
|
7
8
|
* @returns The request-scoped logger.
|
|
8
9
|
* @throws Error if the logger is not initialized on the event context.
|
|
9
10
|
*
|
|
@@ -13,7 +14,15 @@ import { ServerEvent, RequestLogger } from '../../types.mjs';
|
|
|
13
14
|
* log.set({ foo: 'bar' })
|
|
14
15
|
* // ...
|
|
15
16
|
* })
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Override service name for specific routes
|
|
20
|
+
* export default defineEventHandler((event) => {
|
|
21
|
+
* const log = useLogger(event, 'payment-service')
|
|
22
|
+
* log.set({ foo: 'bar' })
|
|
23
|
+
* // ...
|
|
24
|
+
* })
|
|
16
25
|
*/
|
|
17
|
-
declare function useLogger(event: ServerEvent): RequestLogger;
|
|
26
|
+
declare function useLogger(event: ServerEvent, service?: string): RequestLogger;
|
|
18
27
|
|
|
19
28
|
export { useLogger };
|
|
@@ -4,6 +4,7 @@ import { ServerEvent, RequestLogger } from '../../types.js';
|
|
|
4
4
|
* Returns the request logger attached to the given server event.
|
|
5
5
|
*
|
|
6
6
|
* @param event - The current server event containing the context with the logger.
|
|
7
|
+
* @param service - Optional service name to override the default service.
|
|
7
8
|
* @returns The request-scoped logger.
|
|
8
9
|
* @throws Error if the logger is not initialized on the event context.
|
|
9
10
|
*
|
|
@@ -13,7 +14,15 @@ import { ServerEvent, RequestLogger } from '../../types.js';
|
|
|
13
14
|
* log.set({ foo: 'bar' })
|
|
14
15
|
* // ...
|
|
15
16
|
* })
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Override service name for specific routes
|
|
20
|
+
* export default defineEventHandler((event) => {
|
|
21
|
+
* const log = useLogger(event, 'payment-service')
|
|
22
|
+
* log.set({ foo: 'bar' })
|
|
23
|
+
* // ...
|
|
24
|
+
* })
|
|
16
25
|
*/
|
|
17
|
-
declare function useLogger(event: ServerEvent): RequestLogger;
|
|
26
|
+
declare function useLogger(event: ServerEvent, service?: string): RequestLogger;
|
|
18
27
|
|
|
19
28
|
export { useLogger };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
function useLogger(event) {
|
|
1
|
+
function useLogger(event, service) {
|
|
2
2
|
const log = event.context.log;
|
|
3
3
|
if (!log) {
|
|
4
4
|
throw new Error(
|
|
5
5
|
'[evlog] Logger not initialized. Make sure the evlog Nitro plugin is registered. If using Nuxt, add "evlog" to your modules.'
|
|
6
6
|
);
|
|
7
7
|
}
|
|
8
|
+
if (service) {
|
|
9
|
+
log.set({ service });
|
|
10
|
+
}
|
|
8
11
|
return log;
|
|
9
12
|
}
|
|
10
13
|
|
package/dist/types.d.mts
CHANGED
|
@@ -160,6 +160,13 @@ interface SamplingConfig {
|
|
|
160
160
|
*/
|
|
161
161
|
keep?: TailSamplingCondition[];
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Route-based service configuration
|
|
165
|
+
*/
|
|
166
|
+
interface RouteConfig {
|
|
167
|
+
/** Service name to use for routes matching this pattern */
|
|
168
|
+
service: string;
|
|
169
|
+
}
|
|
163
170
|
/**
|
|
164
171
|
* Environment context automatically included in every log event
|
|
165
172
|
*/
|
|
@@ -325,7 +332,16 @@ interface H3EventContext {
|
|
|
325
332
|
interface ServerEvent {
|
|
326
333
|
method: string;
|
|
327
334
|
path: string;
|
|
328
|
-
context: H3EventContext
|
|
335
|
+
context: H3EventContext & {
|
|
336
|
+
/** Cloudflare Workers context (available when deployed to CF Workers) */
|
|
337
|
+
cloudflare?: {
|
|
338
|
+
context: {
|
|
339
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
/** Vercel Edge context (available when deployed to Vercel Edge) */
|
|
343
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
344
|
+
};
|
|
329
345
|
node?: {
|
|
330
346
|
res?: {
|
|
331
347
|
statusCode?: number;
|
|
@@ -345,4 +361,4 @@ interface ParsedError {
|
|
|
345
361
|
raw: unknown;
|
|
346
362
|
}
|
|
347
363
|
|
|
348
|
-
export type { BaseWideEvent, DrainContext, EnvironmentContext, ErrorOptions, H3EventContext, IngestPayload, Log, LogLevel, LoggerConfig, ParsedError, RequestLogger, RequestLoggerOptions, SamplingConfig, SamplingRates, ServerEvent, TailSamplingCondition, TailSamplingContext, TransportConfig, WideEvent };
|
|
364
|
+
export type { BaseWideEvent, DrainContext, EnvironmentContext, ErrorOptions, H3EventContext, IngestPayload, Log, LogLevel, LoggerConfig, ParsedError, RequestLogger, RequestLoggerOptions, RouteConfig, SamplingConfig, SamplingRates, ServerEvent, TailSamplingCondition, TailSamplingContext, TransportConfig, WideEvent };
|
package/dist/types.d.ts
CHANGED
|
@@ -160,6 +160,13 @@ interface SamplingConfig {
|
|
|
160
160
|
*/
|
|
161
161
|
keep?: TailSamplingCondition[];
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Route-based service configuration
|
|
165
|
+
*/
|
|
166
|
+
interface RouteConfig {
|
|
167
|
+
/** Service name to use for routes matching this pattern */
|
|
168
|
+
service: string;
|
|
169
|
+
}
|
|
163
170
|
/**
|
|
164
171
|
* Environment context automatically included in every log event
|
|
165
172
|
*/
|
|
@@ -325,7 +332,16 @@ interface H3EventContext {
|
|
|
325
332
|
interface ServerEvent {
|
|
326
333
|
method: string;
|
|
327
334
|
path: string;
|
|
328
|
-
context: H3EventContext
|
|
335
|
+
context: H3EventContext & {
|
|
336
|
+
/** Cloudflare Workers context (available when deployed to CF Workers) */
|
|
337
|
+
cloudflare?: {
|
|
338
|
+
context: {
|
|
339
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
/** Vercel Edge context (available when deployed to Vercel Edge) */
|
|
343
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
344
|
+
};
|
|
329
345
|
node?: {
|
|
330
346
|
res?: {
|
|
331
347
|
statusCode?: number;
|
|
@@ -345,4 +361,4 @@ interface ParsedError {
|
|
|
345
361
|
raw: unknown;
|
|
346
362
|
}
|
|
347
363
|
|
|
348
|
-
export type { BaseWideEvent, DrainContext, EnvironmentContext, ErrorOptions, H3EventContext, IngestPayload, Log, LogLevel, LoggerConfig, ParsedError, RequestLogger, RequestLoggerOptions, SamplingConfig, SamplingRates, ServerEvent, TailSamplingCondition, TailSamplingContext, TransportConfig, WideEvent };
|
|
364
|
+
export type { BaseWideEvent, DrainContext, EnvironmentContext, ErrorOptions, H3EventContext, IngestPayload, Log, LogLevel, LoggerConfig, ParsedError, RequestLogger, RequestLoggerOptions, RouteConfig, SamplingConfig, SamplingRates, ServerEvent, TailSamplingCondition, TailSamplingContext, TransportConfig, WideEvent };
|
package/package.json
CHANGED