evlog 2.16.0 → 2.18.1
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 +102 -9
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/axiom.mjs +4 -2
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/better-stack.mjs +4 -2
- package/dist/adapters/better-stack.mjs.map +1 -1
- package/dist/adapters/datadog.d.mts +1 -1
- package/dist/adapters/datadog.mjs +4 -2
- package/dist/adapters/datadog.mjs.map +1 -1
- package/dist/adapters/fs.d.mts +64 -2
- package/dist/adapters/fs.d.mts.map +1 -1
- package/dist/adapters/fs.mjs +222 -3
- package/dist/adapters/fs.mjs.map +1 -1
- package/dist/adapters/hyperdx.d.mts +1 -1
- package/dist/adapters/hyperdx.mjs +1 -1
- package/dist/adapters/memory.d.mts +116 -0
- package/dist/adapters/memory.d.mts.map +1 -0
- package/dist/adapters/memory.mjs +191 -0
- package/dist/adapters/memory.mjs.map +1 -0
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/otlp.mjs +6 -4
- package/dist/adapters/otlp.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/posthog.mjs +4 -2
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/adapters/sentry.mjs +5 -3
- package/dist/adapters/sentry.mjs.map +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/{audit-pV5aLGP0.mjs → audit-BUI3af4w.mjs} +121 -53
- package/dist/audit-BUI3af4w.mjs.map +1 -0
- package/dist/{audit-X1uUukm3.d.mts → audit-DVdkntSO.d.mts} +76 -5
- package/dist/audit-DVdkntSO.d.mts.map +1 -0
- package/dist/better-auth/index.d.mts +14 -7
- package/dist/better-auth/index.d.mts.map +1 -1
- package/dist/better-auth/index.mjs +11 -1
- package/dist/better-auth/index.mjs.map +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/{define-CuXOqecD.d.mts → define-D-BVMf2l.d.mts} +3 -3
- package/dist/{define-CuXOqecD.d.mts.map → define-D-BVMf2l.d.mts.map} +1 -1
- package/dist/define-D6OJdSUH.mjs.map +1 -1
- package/dist/{dist-BIlS38vi.mjs → dist-H3GIh-KK.mjs} +1 -1
- package/dist/{dist-BIlS38vi.mjs.map → dist-H3GIh-KK.mjs.map} +1 -1
- package/dist/{drain-ByWUeOQC.mjs → drain-7n3K6kPe.mjs} +6 -49
- package/dist/drain-7n3K6kPe.mjs.map +1 -0
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.mjs +2 -2
- package/dist/{enricher-Dy06T17G.mjs → enricher-N0erZS87.mjs} +2 -2
- package/dist/{enricher-Dy06T17G.mjs.map → enricher-N0erZS87.mjs.map} +1 -1
- package/dist/{enricher-DYTr9I16.d.mts → enricher-UW9npoB2.d.mts} +2 -2
- package/dist/{enricher-DYTr9I16.d.mts.map → enricher-UW9npoB2.d.mts.map} +1 -1
- package/dist/enrichers.d.mts +2 -2
- package/dist/enrichers.mjs +1 -1
- package/dist/{error-Cpc7RVz6.d.mts → error-CVtn5U7b.d.mts} +2 -2
- package/dist/{error-Cpc7RVz6.d.mts.map → error-CVtn5U7b.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/{errors-prnQ3kES.d.mts → errors-dEMNQCiL.d.mts} +2 -2
- package/dist/{errors-prnQ3kES.d.mts.map → errors-dEMNQCiL.d.mts.map} +1 -1
- package/dist/{event-DcHmEm3O.mjs → event-1BMl7o0k.mjs} +1 -1
- package/dist/{event-DcHmEm3O.mjs.map → event-1BMl7o0k.mjs.map} +1 -1
- package/dist/express/index.d.mts +3 -3
- package/dist/express/index.d.mts.map +1 -1
- package/dist/express/index.mjs +5 -6
- package/dist/express/index.mjs.map +1 -1
- package/dist/fastify/index.d.mts +9 -4
- package/dist/fastify/index.d.mts.map +1 -1
- package/dist/fastify/index.mjs +10 -8
- package/dist/fastify/index.mjs.map +1 -1
- package/dist/{fork-DPN8aL8O.mjs → fork-Bga8x-X4.mjs} +4 -3
- package/dist/fork-Bga8x-X4.mjs.map +1 -0
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +1 -1
- package/dist/http-B6YgAhyN.mjs +82 -0
- package/dist/http-B6YgAhyN.mjs.map +1 -0
- package/dist/http.d.mts +1 -1
- package/dist/http.mjs +1 -0
- package/dist/http.mjs.map +1 -1
- package/dist/index-ZSRQP_BI.d.mts +213 -0
- package/dist/index-ZSRQP_BI.d.mts.map +1 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +210 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{integration-DSZPbI9N.mjs → integration-Dhig7ae6.mjs} +2 -2
- package/dist/{integration-DSZPbI9N.mjs.map → integration-Dhig7ae6.mjs.map} +1 -1
- package/dist/{logger-U8lgdc9x.d.mts → logger-CTcvd5Cc.d.mts} +7 -3
- package/dist/logger-CTcvd5Cc.d.mts.map +1 -0
- package/dist/logger.d.mts +2 -2
- package/dist/logger.mjs +2 -2
- package/dist/{middleware-CAQHJRN1.d.mts → middleware-31KhtiEF.d.mts} +2 -2
- package/dist/{middleware-CAQHJRN1.d.mts.map → middleware-31KhtiEF.d.mts.map} +1 -1
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.d.mts.map +1 -1
- package/dist/nestjs/index.mjs +4 -5
- package/dist/nestjs/index.mjs.map +1 -1
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +6 -5
- package/dist/next/index.d.mts.map +1 -1
- package/dist/next/index.mjs +4 -4
- package/dist/next/index.mjs.map +1 -1
- package/dist/next/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/next/instrumentation.mjs.map +1 -1
- package/dist/next/stream.d.mts +29 -0
- package/dist/next/stream.d.mts.map +1 -0
- package/dist/next/stream.mjs +78 -0
- package/dist/next/stream.mjs.map +1 -0
- package/dist/nitro/errorHandler.mjs +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/module.d.mts.map +1 -1
- package/dist/nitro/module.mjs +7 -2
- package/dist/nitro/module.mjs.map +1 -1
- package/dist/nitro/plugin.mjs +13 -3
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.mjs +2 -2
- package/dist/nitro/v3/index.d.mts +2 -2
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/module.d.mts.map +1 -1
- package/dist/nitro/v3/module.mjs +9 -4
- package/dist/nitro/v3/module.mjs.map +1 -1
- package/dist/nitro/v3/plugin.mjs +5 -4
- package/dist/nitro/v3/plugin.mjs.map +1 -1
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-C6Bd682U.d.mts → nitro-BRddgqSb.d.mts} +2 -2
- package/dist/{nitro-C6Bd682U.d.mts.map → nitro-BRddgqSb.d.mts.map} +1 -1
- package/dist/{nitro-DavLelNz.mjs → nitro-DErMq_Zj.mjs} +1 -1
- package/dist/{nitro-DavLelNz.mjs.map → nitro-DErMq_Zj.mjs.map} +1 -1
- package/dist/nitroConfigBridge-NbFn-sIK.mjs +157 -0
- package/dist/nitroConfigBridge-NbFn-sIK.mjs.map +1 -0
- package/dist/nodeResponse-BkkionWl.mjs +42 -0
- package/dist/nodeResponse-BkkionWl.mjs.map +1 -0
- package/dist/nuxt/module.d.mts +35 -4
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +11 -4
- package/dist/nuxt/module.mjs.map +1 -1
- package/dist/orpc/index.d.mts +115 -0
- package/dist/orpc/index.d.mts.map +1 -0
- package/dist/orpc/index.mjs +144 -0
- package/dist/orpc/index.mjs.map +1 -0
- package/dist/package-B23bR3tK.mjs +7 -0
- package/dist/package-B23bR3tK.mjs.map +1 -0
- package/dist/{parseError-B-dKF6Fd.d.mts → parseError-D4PIxEWo.d.mts} +2 -2
- package/dist/parseError-D4PIxEWo.d.mts.map +1 -0
- package/dist/react-router/index.d.mts +2 -2
- package/dist/react-router/index.mjs +2 -2
- package/dist/{routes-B48wm7Pb.mjs → routes-CnIgYWf8.mjs} +1 -1
- package/dist/{routes-B48wm7Pb.mjs.map → routes-CnIgYWf8.mjs.map} +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +28 -12
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -1
- package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts +18 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts.map +1 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.mjs +28 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.mjs.map +1 -0
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/{severity-BYWZ96Sb.mjs → severity-R5Egq3qz.mjs} +1 -1
- package/dist/{severity-BYWZ96Sb.mjs.map → severity-R5Egq3qz.mjs.map} +1 -1
- package/dist/{storage-BT-3fT1-.mjs → storage-BNubsWwz.mjs} +2 -1
- package/dist/{storage-BT-3fT1-.mjs.map → storage-BNubsWwz.mjs.map} +1 -1
- package/dist/stream.d.mts +185 -0
- package/dist/stream.d.mts.map +1 -0
- package/dist/stream.mjs +374 -0
- package/dist/stream.mjs.map +1 -0
- package/dist/sveltekit/index.d.mts +3 -3
- package/dist/sveltekit/index.d.mts.map +1 -1
- package/dist/sveltekit/index.mjs +44 -11
- package/dist/sveltekit/index.mjs.map +1 -1
- package/dist/toolkit.d.mts +43 -8
- package/dist/toolkit.d.mts.map +1 -1
- package/dist/toolkit.mjs +11 -10
- package/dist/types.d.mts +2 -2
- package/dist/{useLogger-CoNgTjp5.d.mts → useLogger-CqvH6qOf.d.mts} +2 -2
- package/dist/{useLogger-CoNgTjp5.d.mts.map → useLogger-CqvH6qOf.d.mts.map} +1 -1
- package/dist/{utils-Db4qhBWn.d.mts → utils-DxqvIOyR.d.mts} +12 -3
- package/dist/{utils-Db4qhBWn.d.mts.map → utils-DxqvIOyR.d.mts.map} +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/utils.mjs +10 -1
- package/dist/utils.mjs.map +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/workers.d.mts +1 -1
- package/dist/workers.mjs +1 -1
- package/package.json +64 -15
- package/dist/audit-X1uUukm3.d.mts.map +0 -1
- package/dist/audit-pV5aLGP0.mjs.map +0 -1
- package/dist/drain-ByWUeOQC.mjs.map +0 -1
- package/dist/fork-DPN8aL8O.mjs.map +0 -1
- package/dist/logger-U8lgdc9x.d.mts.map +0 -1
- package/dist/nitroConfigBridge-aZ1e5upQ.mjs +0 -92
- package/dist/nitroConfigBridge-aZ1e5upQ.mjs.map +0 -1
- package/dist/parseError-B-dKF6Fd.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -528,6 +528,38 @@ log.set({ users: { count: 42 } })
|
|
|
528
528
|
|
|
529
529
|
See the full [nestjs example](https://github.com/HugoRCD/evlog/tree/main/examples/nestjs) for a complete working project.
|
|
530
530
|
|
|
531
|
+
## oRPC
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
// server/orpc.ts
|
|
535
|
+
import { os } from '@orpc/server'
|
|
536
|
+
import { RPCHandler } from '@orpc/server/fetch'
|
|
537
|
+
import { initLogger } from 'evlog'
|
|
538
|
+
import { evlog, withEvlog, type EvlogOrpcContext } from 'evlog/orpc'
|
|
539
|
+
|
|
540
|
+
initLogger({ env: { service: 'orpc-api' } })
|
|
541
|
+
|
|
542
|
+
const base = os.$context<EvlogOrpcContext>().use(evlog())
|
|
543
|
+
|
|
544
|
+
const router = {
|
|
545
|
+
ping: base.handler(({ context }) => {
|
|
546
|
+
context.log.set({ pinged: true })
|
|
547
|
+
return { ok: true }
|
|
548
|
+
}),
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const handler = withEvlog(new RPCHandler(router))
|
|
552
|
+
|
|
553
|
+
export default async function fetch(request: Request) {
|
|
554
|
+
const { matched, response } = await handler.handle(request, { prefix: '/rpc' })
|
|
555
|
+
return matched ? response : new Response('Not Found', { status: 404 })
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
`withEvlog()` wraps the handler and emits one wide event per request; `os.use(evlog())` exposes `context.log` to procedures and tags each event with the procedure path as `operation`. Use `useLogger()` from `evlog/orpc` to access the logger off-context.
|
|
560
|
+
|
|
561
|
+
See the full [orpc example](https://github.com/HugoRCD/evlog/tree/main/examples/orpc) for a complete working project.
|
|
562
|
+
|
|
531
563
|
## Browser
|
|
532
564
|
|
|
533
565
|
Use the `log` API on the client side for structured browser logging:
|
|
@@ -571,7 +603,7 @@ When enabled:
|
|
|
571
603
|
3. `evlog:drain` hook is called with `source: 'client'`
|
|
572
604
|
4. External services receive the log
|
|
573
605
|
|
|
574
|
-
For a **framework-agnostic** batched HTTP drain (e.g. vanilla JS or custom endpoints), use `createHttpLogDrain` from [`evlog/http`](https://www.evlog.dev/
|
|
606
|
+
For a **framework-agnostic** batched HTTP drain (e.g. vanilla JS or custom endpoints), use `createHttpLogDrain` from [`evlog/http`](https://www.evlog.dev/extend/drain-pipeline#http-drain-browser-to-server). The legacy import path `evlog/browser` is deprecated and will be removed in the next major release.
|
|
575
607
|
|
|
576
608
|
## Structured Errors
|
|
577
609
|
|
|
@@ -758,7 +790,7 @@ export default defineEventHandler(async (event) => {
|
|
|
758
790
|
|
|
759
791
|
`AuditFields` is exported and merges with `BaseWideEvent` — augment it with `declare module` if you need extra typed fields. Audit events are always force-kept by tail sampling and get a deterministic `idempotencyKey` so retries are safe across drains.
|
|
760
792
|
|
|
761
|
-
See [the Audit Logs guide](https://evlog.dev/
|
|
793
|
+
See [the Audit Logs guide](https://evlog.dev/use-cases/audit/overview) for compliance, GDPR, and recipe details.
|
|
762
794
|
|
|
763
795
|
## AI SDK Integration
|
|
764
796
|
|
|
@@ -821,7 +853,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
821
853
|
Set environment variables:
|
|
822
854
|
|
|
823
855
|
```bash
|
|
824
|
-
|
|
856
|
+
NUXT_AXIOM_API_KEY=xaat-your-token
|
|
825
857
|
NUXT_AXIOM_DATASET=your-dataset
|
|
826
858
|
```
|
|
827
859
|
|
|
@@ -865,7 +897,7 @@ NUXT_DATADOG_SITE=datadoghq.eu
|
|
|
865
897
|
|
|
866
898
|
You can also use standard Datadog names: `DD_API_KEY` and `DD_SITE`.
|
|
867
899
|
|
|
868
|
-
Wide events are sent with a short **`message` line** (method, path, level) and full context under the **`evlog`** attribute (facets like `@evlog.path`). See the [Datadog adapter docs](https://www.evlog.dev/adapters/datadog).
|
|
900
|
+
Wide events are sent with a short **`message` line** (method, path, level) and full context under the **`evlog`** attribute (facets like `@evlog.path`). See the [Datadog adapter docs](https://www.evlog.dev/integrate/adapters/datadog).
|
|
869
901
|
|
|
870
902
|
### PostHog
|
|
871
903
|
|
|
@@ -916,9 +948,69 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
916
948
|
Set environment variables:
|
|
917
949
|
|
|
918
950
|
```bash
|
|
919
|
-
|
|
951
|
+
NUXT_BETTER_STACK_API_KEY=your-source-token
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
### HyperDX
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
// server/plugins/evlog-drain.ts
|
|
958
|
+
import { createHyperDXDrain } from 'evlog/hyperdx'
|
|
959
|
+
|
|
960
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
961
|
+
nitroApp.hooks.hook('evlog:drain', createHyperDXDrain())
|
|
962
|
+
})
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
Set environment variables:
|
|
966
|
+
|
|
967
|
+
```bash
|
|
968
|
+
NUXT_HYPERDX_API_KEY=your-api-key
|
|
969
|
+
# Optional — defaults to https://in-otel.hyperdx.io
|
|
970
|
+
NUXT_HYPERDX_ENDPOINT=https://in-otel.hyperdx.io
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
### File System
|
|
974
|
+
|
|
975
|
+
Write wide events to local NDJSON files (`.evlog/logs/` by default):
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
// server/plugins/evlog-drain.ts
|
|
979
|
+
import { createFsDrain } from 'evlog/fs'
|
|
980
|
+
|
|
981
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
982
|
+
nitroApp.hooks.hook('evlog:drain', createFsDrain())
|
|
983
|
+
})
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
Set environment variables:
|
|
987
|
+
|
|
988
|
+
```bash
|
|
989
|
+
NUXT_EVLOG_FS_DIR=.evlog/logs
|
|
920
990
|
```
|
|
921
991
|
|
|
992
|
+
### Memory
|
|
993
|
+
|
|
994
|
+
In-memory ring buffer — works in any runtime, including Cloudflare Workers:
|
|
995
|
+
|
|
996
|
+
```typescript
|
|
997
|
+
// server/plugins/evlog-drain.ts
|
|
998
|
+
import { createMemoryDrain } from 'evlog/memory'
|
|
999
|
+
|
|
1000
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
1001
|
+
nitroApp.hooks.hook('evlog:drain', createMemoryDrain())
|
|
1002
|
+
})
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
Optional environment variables:
|
|
1006
|
+
|
|
1007
|
+
```bash
|
|
1008
|
+
NUXT_EVLOG_MEMORY_STORE=default
|
|
1009
|
+
NUXT_EVLOG_MEMORY_MAX_EVENTS=1000
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
Pair with `readMemoryLogs()` for dev-only agent access over HTTP. See the [Memory adapter docs](https://www.evlog.dev/integrate/adapters/self-hosted/memory).
|
|
1013
|
+
|
|
922
1014
|
### Multiple Destinations
|
|
923
1015
|
|
|
924
1016
|
Send logs to multiple services:
|
|
@@ -955,7 +1047,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
955
1047
|
})
|
|
956
1048
|
```
|
|
957
1049
|
|
|
958
|
-
> See the [full documentation](https://evlog.
|
|
1050
|
+
> See the [full documentation](https://www.evlog.dev/integrate/adapters/overview) for adapter configuration options, troubleshooting, and advanced patterns.
|
|
959
1051
|
|
|
960
1052
|
## Drain Pipeline
|
|
961
1053
|
|
|
@@ -1163,7 +1255,7 @@ The framework emits **one wide event per HTTP request** when the response finish
|
|
|
1163
1255
|
| Express, Fastify, NestJS, SvelteKit, React Router, Elysia | Yes |
|
|
1164
1256
|
| Next.js `withEvlog` | Yes |
|
|
1165
1257
|
| Hono (`c.get('log')` only) | Not yet |
|
|
1166
|
-
| Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/
|
|
1258
|
+
| Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/learn/wide-events) |
|
|
1167
1259
|
|
|
1168
1260
|
```typescript
|
|
1169
1261
|
import { evlog, useLogger } from 'evlog/express'
|
|
@@ -1297,15 +1389,16 @@ try {
|
|
|
1297
1389
|
| **Hono** | `app.use(evlog())` with `import { evlog } from 'evlog/hono'` ([example](./examples/hono)) |
|
|
1298
1390
|
| **Fastify** | `app.register(evlog)` with `import { evlog } from 'evlog/fastify'` ([example](./examples/fastify)) |
|
|
1299
1391
|
| **Elysia** | `.use(evlog())` with `import { evlog } from 'evlog/elysia'` ([example](./examples/elysia)) |
|
|
1392
|
+
| **oRPC** | `withEvlog(handler)` + `os.use(evlog())` with `import { evlog, withEvlog } from 'evlog/orpc'` ([example](./examples/orpc)) |
|
|
1300
1393
|
| **Cloudflare Workers** | Manual setup with `import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'` ([example](./examples/workers)) |
|
|
1301
|
-
| **Custom** | Build your own with `import { createMiddlewareLogger } from 'evlog/toolkit'` ([guide](https://evlog.dev/
|
|
1394
|
+
| **Custom** | Build your own with `import { createMiddlewareLogger } from 'evlog/toolkit'` ([guide](https://evlog.dev/extend/custom-framework)) |
|
|
1302
1395
|
| **Analog** | Nitro v2 module setup |
|
|
1303
1396
|
| **Vinxi** | Nitro v2 module setup |
|
|
1304
1397
|
| **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
|
|
1305
1398
|
|
|
1306
1399
|
## Agent Skills
|
|
1307
1400
|
|
|
1308
|
-
evlog provides [Agent Skills](https://www.evlog.dev/
|
|
1401
|
+
evlog provides [Agent Skills](https://www.evlog.dev/reference/agent-skills) to help AI coding assistants understand and implement proper logging patterns in your codebase.
|
|
1309
1402
|
|
|
1310
1403
|
### Installation
|
|
1311
1404
|
|
package/dist/adapters/axiom.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-B6YgAhyN.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-7n3K6kPe.mjs";
|
|
2
3
|
//#region src/adapters/axiom.ts
|
|
3
4
|
const AXIOM_FIELDS = [
|
|
4
5
|
{
|
|
@@ -111,7 +112,8 @@ async function sendBatchToAxiom(events, config) {
|
|
|
111
112
|
body: JSON.stringify(events),
|
|
112
113
|
timeout: config.timeout ?? 5e3,
|
|
113
114
|
retries: config.retries,
|
|
114
|
-
label: "Axiom"
|
|
115
|
+
label: "Axiom",
|
|
116
|
+
source: "axiom"
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
function resolveIngestUrl(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name. */\n dataset: string\n /**\n * Axiom API key.\n *\n * @example `xaat-...`\n */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BaseAxiomConfig.apiKey}. Will be removed in\n * the next major version. Pass `apiKey` instead.\n */\n token?: string\n /** Organization ID (required for Personal Access Tokens). */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'apiKey', env: ['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutToken = false\n\nfunction applyApiKeyAlias(config: ResolvedAxiomConfig): ResolvedAxiomConfig {\n if (!config.apiKey && config.token) {\n if (!warnedAboutToken) {\n warnedAboutToken = true\n console.warn('[evlog/axiom] `token` is deprecated, use `apiKey` instead. (Env: NUXT_AXIOM_TOKEN/AXIOM_TOKEN → NUXT_AXIOM_API_KEY/AXIOM_API_KEY.)')\n }\n config.apiKey = config.token\n }\n return config\n}\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_API_KEY, AXIOM_API_KEY (or legacy `*_TOKEN`)\n *\n * @example\n * ```ts\n * // Zero config — set NUXT_AXIOM_API_KEY and NUXT_AXIOM_DATASET\n * initLogger({ drain: createAxiomDrain() })\n *\n * // With overrides\n * initLogger({ drain: createAxiomDrain({ dataset: 'my-dataset' }) })\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineHttpDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n const config = applyApiKeyAlias(resolved)\n if (!config.dataset || !config.apiKey) {\n console.error('[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n return config as AxiomConfig\n },\n encode: (events, config) => {\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n return { url, headers, body: JSON.stringify(events) }\n },\n })\n}\n\n/**\n * Send a single event to Axiom.\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.token\n if (!apiKey) {\n throw new Error('[evlog/axiom] Missing apiKey')\n }\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name. */\n dataset: string\n /**\n * Axiom API key.\n *\n * @example `xaat-...`\n */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BaseAxiomConfig.apiKey}. Will be removed in\n * the next major version. Pass `apiKey` instead.\n */\n token?: string\n /** Organization ID (required for Personal Access Tokens). */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'apiKey', env: ['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutToken = false\n\nfunction applyApiKeyAlias(config: Partial<ResolvedAxiomConfig>): Partial<ResolvedAxiomConfig> {\n if (!config.apiKey && config.token) {\n if (!warnedAboutToken) {\n warnedAboutToken = true\n console.warn('[evlog/axiom] `token` is deprecated, use `apiKey` instead. (Env: NUXT_AXIOM_TOKEN/AXIOM_TOKEN → NUXT_AXIOM_API_KEY/AXIOM_API_KEY.)')\n }\n config.apiKey = config.token\n }\n return config\n}\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_API_KEY, AXIOM_API_KEY (or legacy `*_TOKEN`)\n *\n * @example\n * ```ts\n * // Zero config — set NUXT_AXIOM_API_KEY and NUXT_AXIOM_DATASET\n * initLogger({ drain: createAxiomDrain() })\n *\n * // With overrides\n * initLogger({ drain: createAxiomDrain({ dataset: 'my-dataset' }) })\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineHttpDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n const config = applyApiKeyAlias(resolved)\n if (!config.dataset || !config.apiKey) {\n console.error('[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n return config as AxiomConfig\n },\n encode: (events, config) => {\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n return { url, headers, body: JSON.stringify(events) }\n },\n })\n}\n\n/**\n * Send a single event to Axiom.\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.token\n if (!apiKey) {\n throw new Error('[evlog/axiom] Missing apiKey')\n }\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n source: 'axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":";;;AAqDA,MAAM,eAAmD;CACvD;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAU,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAE/D;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,uBAAuB,iBAAiB;EAAE;CAClE;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,IAAI,mBAAmB;AAEvB,SAAS,iBAAiB,QAAoE;AAC5F,KAAI,CAAC,OAAO,UAAU,OAAO,OAAO;AAClC,MAAI,CAAC,kBAAkB;AACrB,sBAAmB;AACnB,WAAQ,KAAK,qIAAqI;;AAEpJ,SAAO,SAAS,OAAO;;AAEzB,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,gBAA6B;EAClC,MAAM;EACN,SAAS,YAAY;GAMnB,MAAM,SAAS,iBAAiB,MALT,qBACrB,SACA,cACA,UACD,CACwC;AACzC,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,QAAQ;AACrC,YAAQ,MAAM,4HAA4H;AAC1I,WAAO;;AAET,OAAI,OAAO,WAAW,OAAO,SAAS;AACpC,YAAQ,KAAK,uFAAuF;AACpG,WAAO,OAAO;;AAEhB,UAAO;;EAET,SAAS,QAAQ,WAAW;GAC1B,MAAM,MAAM,iBAAiB,OAAO;GACpC,MAAM,UAAkC;IACtC,gBAAgB;IAChB,iBAAiB,UAAU,OAAO;IACnC;AACD,OAAI,OAAO,MAAO,SAAQ,oBAAoB,OAAO;AACrD,UAAO;IAAE;IAAK;IAAS,MAAM,KAAK,UAAU,OAAO;IAAE;;EAExD,CAAC;;;;;AAMJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;AAMzC,eAAsB,iBAAiB,QAAqB,QAAoC;CAC9F,MAAM,SAAS,OAAO,UAAU,OAAO;AACvC,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,+BAA+B;CAEjD,MAAM,MAAM,iBAAiB,OAAO;CACpC,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU;EAC5B;AACD,KAAI,OAAO,MAAO,SAAQ,oBAAoB,OAAO;AACrD,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACP,QAAQ;EACT,CAAC;;AAGJ,SAAS,iBAAiB,QAA6B;CACrD,MAAM,iBAAiB,mBAAmB,OAAO,QAAQ;AAEzD,KAAI,CAAC,OAAO,QAEV,QAAO,GADS,OAAO,WAAW,uBAChB,eAAe,eAAe;AAGlD,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ;AACtC,MAAI,OAAO,aAAa,MAAM,OAAO,aAAa,KAAK;AACrD,UAAO,WAAW,cAAc;AAChC,UAAO,OAAO,UAAU;;AAE1B,SAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,GAAG;AACrD,SAAO,OAAO,UAAU;SAClB;AACN,UAAQ,KAAK,0BAA0B,OAAO,QAAQ,6DAA6D;AAEnH,SAAO,GADS,OAAO,QAAQ,QAAQ,QAAQ,GAC9B,CAAC,aAAa"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, ct as WideEvent } from "../audit-DVdkntSO.mjs";
|
|
2
2
|
//#region src/adapters/better-stack.d.ts
|
|
3
3
|
interface BetterStackConfig {
|
|
4
4
|
/** Better Stack API key (replaces deprecated `sourceToken`). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-B6YgAhyN.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-7n3K6kPe.mjs";
|
|
2
3
|
//#region src/adapters/better-stack.ts
|
|
3
4
|
const BETTER_STACK_FIELDS = [
|
|
4
5
|
{
|
|
@@ -96,7 +97,8 @@ async function sendBatchToBetterStack(events, config) {
|
|
|
96
97
|
body: JSON.stringify(events.map(toBetterStackEvent)),
|
|
97
98
|
timeout: config.timeout ?? 5e3,
|
|
98
99
|
retries: config.retries,
|
|
99
|
-
label: "Better Stack"
|
|
100
|
+
label: "Better Stack",
|
|
101
|
+
source: "better-stack"
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\nexport interface BetterStackConfig {\n /** Better Stack API key (replaces deprecated `sourceToken`). */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BetterStackConfig.apiKey}. Will be removed\n * in the next major version. Pass `apiKey` instead.\n */\n sourceToken?: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'apiKey', env: ['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutSourceToken = false\n\nfunction applyApiKeyAlias(config: BetterStackConfig): BetterStackConfig {\n if (!config.apiKey && config.sourceToken) {\n if (!warnedAboutSourceToken) {\n warnedAboutSourceToken = true\n console.warn('[evlog/better-stack] `sourceToken` is deprecated, use `apiKey` instead. (Env: NUXT_BETTER_STACK_SOURCE_TOKEN → NUXT_BETTER_STACK_API_KEY.)')\n }\n config.apiKey = config.sourceToken\n }\n return config\n}\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_API_KEY, BETTER_STACK_API_KEY (or legacy `*_SOURCE_TOKEN`)\n *\n * @example\n * ```ts\n * initLogger({ drain: createBetterStackDrain() })\n *\n * initLogger({ drain: createBetterStackDrain({ apiKey: 'my-key' }) })\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineHttpDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n const config = applyApiKeyAlias(resolved as BetterStackConfig)\n if (!config.apiKey) {\n console.error('[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()')\n return null\n }\n return config\n },\n encode: (events, config) => ({\n url: (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, ''),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n }),\n })\n}\n\n/**\n * Send a single event to Better Stack.\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.sourceToken\n if (!apiKey) throw new Error('[evlog/better-stack] Missing apiKey')\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\nexport interface BetterStackConfig {\n /** Better Stack API key (replaces deprecated `sourceToken`). */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BetterStackConfig.apiKey}. Will be removed\n * in the next major version. Pass `apiKey` instead.\n */\n sourceToken?: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'apiKey', env: ['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutSourceToken = false\n\nfunction applyApiKeyAlias(config: BetterStackConfig): BetterStackConfig {\n if (!config.apiKey && config.sourceToken) {\n if (!warnedAboutSourceToken) {\n warnedAboutSourceToken = true\n console.warn('[evlog/better-stack] `sourceToken` is deprecated, use `apiKey` instead. (Env: NUXT_BETTER_STACK_SOURCE_TOKEN → NUXT_BETTER_STACK_API_KEY.)')\n }\n config.apiKey = config.sourceToken\n }\n return config\n}\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_API_KEY, BETTER_STACK_API_KEY (or legacy `*_SOURCE_TOKEN`)\n *\n * @example\n * ```ts\n * initLogger({ drain: createBetterStackDrain() })\n *\n * initLogger({ drain: createBetterStackDrain({ apiKey: 'my-key' }) })\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineHttpDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n const config = applyApiKeyAlias(resolved as BetterStackConfig)\n if (!config.apiKey) {\n console.error('[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()')\n return null\n }\n return config\n },\n encode: (events, config) => ({\n url: (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, ''),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n }),\n })\n}\n\n/**\n * Send a single event to Better Stack.\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.sourceToken\n if (!apiKey) throw new Error('[evlog/better-stack] Missing apiKey')\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n source: 'better-stack',\n })\n}\n"],"mappings":";;;AAsBA,MAAM,sBAAwD;CAC5D;EAAE,KAAK;EAAU,KAAK,CAAC,6BAA6B,uBAAuB;EAAE;CAE7E;EAAE,KAAK;EAAe,KAAK,CAAC,kCAAkC,4BAA4B;EAAE;CAC5F;EAAE,KAAK;EAAY,KAAK,CAAC,8BAA8B,wBAAwB;EAAE;CACjF,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,IAAI,yBAAyB;AAE7B,SAAS,iBAAiB,QAA8C;AACtE,KAAI,CAAC,OAAO,UAAU,OAAO,aAAa;AACxC,MAAI,CAAC,wBAAwB;AAC3B,4BAAyB;AACzB,WAAQ,KAAK,6IAA6I;;AAE5J,SAAO,SAAS,OAAO;;AAEzB,QAAO;;;;;;AAOT,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,EAAE,WAAW,GAAG,SAAS;AAC/B,QAAO;EAAE,GAAG;EAAM,IAAI;EAAW;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,uBAAuB,WAAwC;AAC7E,QAAO,gBAAmC;EACxC,MAAM;EACN,SAAS,YAAY;GAEnB,MAAM,SAAS,iBAAiB,MADT,qBAAwC,eAAe,qBAAqB,UAAU,CAC/C;AAC9D,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,iHAAiH;AAC/H,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,YAAY;GAC3B,MAAM,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAAG;GAC/E,SAAS;IACP,gBAAgB;IAChB,iBAAiB,UAAU,OAAO;IACnC;GACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;GACrD;EACF,CAAC;;;;;AAMJ,eAAsB,kBAAkB,OAAkB,QAA0C;AAClG,OAAM,uBAAuB,CAAC,MAAM,EAAE,OAAO;;;;;AAM/C,eAAsB,uBAAuB,QAAqB,QAA0C;CAC1G,MAAM,SAAS,OAAO,UAAU,OAAO;AACvC,KAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,sCAAsC;AAGnE,OAAM,SAAS;EACb,MAHgB,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAGzE;EACb,SAAS;GACP,gBAAgB;GAChB,iBAAiB,UAAU;GAC5B;EACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;EACpD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACP,QAAQ;EACT,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, ct as WideEvent } from "../audit-DVdkntSO.mjs";
|
|
2
2
|
//#region src/adapters/datadog.d.ts
|
|
3
3
|
interface DatadogConfig {
|
|
4
4
|
/** Datadog API key with Logs intake permission */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-B6YgAhyN.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-7n3K6kPe.mjs";
|
|
2
3
|
//#region src/adapters/datadog.ts
|
|
3
4
|
const DATADOG_FIELDS = [
|
|
4
5
|
{
|
|
@@ -169,7 +170,8 @@ async function sendBatchToDatadog(events, config) {
|
|
|
169
170
|
body: JSON.stringify(events.map(toDatadogLog)),
|
|
170
171
|
timeout: config.timeout ?? 5e3,
|
|
171
172
|
retries: config.retries,
|
|
172
|
-
label: "Datadog"
|
|
173
|
+
label: "Datadog",
|
|
174
|
+
source: "datadog"
|
|
173
175
|
});
|
|
174
176
|
}
|
|
175
177
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineHttpDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n encode: (events, config) => ({\n url: resolveDatadogIntakeUrl(config),\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n }),\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineHttpDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n encode: (events, config) => ({\n url: resolveDatadogIntakeUrl(config),\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n }),\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n source: 'datadog',\n })\n}\n"],"mappings":"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"}
|
package/dist/adapters/fs.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, K as LogLevel, ct as WideEvent } from "../audit-DVdkntSO.mjs";
|
|
2
2
|
//#region src/adapters/fs.d.ts
|
|
3
3
|
interface FsConfig {
|
|
4
4
|
/** Directory for log files. Default: `.evlog/logs` */
|
|
@@ -32,6 +32,68 @@ declare function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
declare function createFsDrain(overrides?: Partial<FsConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
|
|
35
|
+
/** Options accepted by {@link readFsLogs}. */
|
|
36
|
+
interface ReadFsLogsOptions {
|
|
37
|
+
/** Directory to read from. Default: `.evlog/logs` */
|
|
38
|
+
dir?: string;
|
|
39
|
+
/** Only yield events with `event.timestamp >= since`. */
|
|
40
|
+
since?: Date | string;
|
|
41
|
+
/** Only yield events with `event.timestamp <= until`. */
|
|
42
|
+
until?: Date | string;
|
|
43
|
+
/** Filter by event level. */
|
|
44
|
+
level?: LogLevel | LogLevel[];
|
|
45
|
+
/** Custom predicate — return `false` to skip the event. */
|
|
46
|
+
filter?: (event: WideEvent) => boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Options accepted by {@link tailFsLogs}. */
|
|
49
|
+
interface TailFsLogsOptions extends ReadFsLogsOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Polling interval (ms) used to detect new bytes / new files.
|
|
52
|
+
* @default 500
|
|
53
|
+
*/
|
|
54
|
+
pollIntervalMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Skip existing events and only yield events appended after the tailer
|
|
57
|
+
* starts. Useful for "live tail" UX.
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
fromEnd?: boolean;
|
|
61
|
+
/** Stop tailing when this signal aborts. */
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read past events from the local file system drain (NDJSON). Files are
|
|
66
|
+
* iterated in chronological order; events are yielded as they appear in
|
|
67
|
+
* each file.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { readFsLogs } from 'evlog/fs'
|
|
72
|
+
*
|
|
73
|
+
* for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {
|
|
74
|
+
* console.log(event)
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function readFsLogs(options?: ReadFsLogsOptions): AsyncGenerator<WideEvent>;
|
|
79
|
+
/**
|
|
80
|
+
* Follow the local file system drain in real time. Yields existing events
|
|
81
|
+
* (unless `fromEnd: true`) then keeps yielding new events as they are
|
|
82
|
+
* appended. Automatically picks up newly created daily files.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { tailFsLogs } from 'evlog/fs'
|
|
87
|
+
*
|
|
88
|
+
* const ac = new AbortController()
|
|
89
|
+
* setTimeout(() => ac.abort(), 60_000)
|
|
90
|
+
*
|
|
91
|
+
* for await (const event of tailFsLogs({ signal: ac.signal })) {
|
|
92
|
+
* console.log('live:', event.action ?? event.message)
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare function tailFsLogs(options?: TailFsLogsOptions): AsyncGenerator<WideEvent>;
|
|
35
97
|
//#endregion
|
|
36
|
-
export { FsConfig, createFsDrain, writeBatchToFs, writeToFs };
|
|
98
|
+
export { FsConfig, ReadFsLogsOptions, TailFsLogsOptions, createFsDrain, readFsLogs, tailFsLogs, writeBatchToFs, writeToFs };
|
|
37
99
|
//# sourceMappingURL=fs.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.mts","names":[],"sources":["../../src/adapters/fs.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"fs.d.mts","names":[],"sources":["../../src/adapters/fs.ts"],"mappings":";;UASiB,QAAA;;EAEf,GAAA;EAFe;EAIf,QAAA;;EAEA,cAAA;EAJA;EAMA,MAAA;AAAA;AAAA,iBA+EoB,SAAA,CAAU,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;AAAA,iBAI/C,cAAA,CAAe,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,QAAA,GAAW,OAAA;;AAJ7E;;;;;;;;;;;;;;;AAIA;;;iBAqCgB,aAAA,CAAc,SAAA,GAAY,OAAA,CAAQ,QAAA,KAAS,GAAA,EAAV,YAAA,GAAU,YAAA,OAAA,OAAA;;UAiB1C,iBAAA;EAtDmE;EAwDlF,GAAA;EAxD2C;EA0D3C,KAAA,GAAQ,IAAA;EA1DwD;EA4DhE,KAAA,GAAQ,IAAA;EA5DmE;EA8D3E,KAAA,GAAQ,QAAA,GAAW,QAAA;EA9D+D;EAgElF,MAAA,IAAU,KAAA,EAAO,SAAA;AAAA;;UAIF,iBAAA,SAA0B,iBAAA;EA/BD;;;;EAoCxC,cAAA;EApCyD;;;;;EA0CzD,OAAA;EA1CyD;EA4CzD,MAAA,GAAS,WAAA;AAAA;;AA3BX;;;;;;;;;;;;;iBAmIuB,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,cAAA,CAAe,SAAA;;;;;;;;;;AArHnF;;;;;;;;iBA8MuB,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,cAAA,CAAe,SAAA"}
|