workers-axiom 0.1.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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/producer/index.d.ts +4 -0
- package/dist/producer/index.d.ts.map +1 -0
- package/dist/producer/index.js +5 -0
- package/dist/producer/index.js.map +1 -0
- package/dist/producer/logger.d.ts +146 -0
- package/dist/producer/logger.d.ts.map +1 -0
- package/dist/producer/logger.js +179 -0
- package/dist/producer/logger.js.map +1 -0
- package/dist/producer/tracing/clock.d.ts +21 -0
- package/dist/producer/tracing/clock.d.ts.map +1 -0
- package/dist/producer/tracing/clock.js +17 -0
- package/dist/producer/tracing/clock.js.map +1 -0
- package/dist/producer/tracing/index.d.ts +3 -0
- package/dist/producer/tracing/index.d.ts.map +1 -0
- package/dist/producer/tracing/index.js +3 -0
- package/dist/producer/tracing/index.js.map +1 -0
- package/dist/producer/tracing/span.d.ts +63 -0
- package/dist/producer/tracing/span.d.ts.map +1 -0
- package/dist/producer/tracing/span.js +94 -0
- package/dist/producer/tracing/span.js.map +1 -0
- package/dist/protocol/index.d.ts +58 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +50 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/tail/index.d.ts +43 -0
- package/dist/tail/index.d.ts.map +1 -0
- package/dist/tail/index.js +155 -0
- package/dist/tail/index.js.map +1 -0
- package/dist/tail/spans.d.ts +19 -0
- package/dist/tail/spans.d.ts.map +1 -0
- package/dist/tail/spans.js +93 -0
- package/dist/tail/spans.js.map +1 -0
- package/package.json +41 -0
- package/src/producer/index.ts +28 -0
- package/src/producer/logger.ts +343 -0
- package/src/producer/tracing/clock.ts +38 -0
- package/src/producer/tracing/index.ts +8 -0
- package/src/producer/tracing/span.ts +148 -0
- package/src/protocol/index.ts +104 -0
- package/src/tail/index.ts +237 -0
- package/src/tail/spans.ts +137 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sapt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# workers-axiom
|
|
2
|
+
|
|
3
|
+
Structured logging, tracing, and metrics for Cloudflare Workers, with an Axiom tail-worker sink.
|
|
4
|
+
|
|
5
|
+
> **Status:** used internally by Sapt. Open source, MIT-licensed. PRs welcome, support is best-effort.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- **Producer** (`workers-axiom/producer`): a `Logger` your worker uses to emit structured logs, metrics, and OpenTelemetry-style spans. Output is plain `console.log` JSON — nothing leaves the worker directly.
|
|
10
|
+
- **Tail consumer** (`workers-axiom/tail`): a factory that builds a Cloudflare `tail()` handler. Point your other workers at it via `tail_consumers`. It parses the producer's JSON, batches it, and forwards logs to Axiom's ingest endpoint and sampled spans to Axiom's OTLP traces endpoint.
|
|
11
|
+
|
|
12
|
+
The two halves communicate through a shared wire format (`workers-axiom/protocol`) and nothing else. You can deploy them independently.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add workers-axiom
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Producer usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { withTrace, type Logger } from 'workers-axiom/producer'
|
|
24
|
+
|
|
25
|
+
interface Env { ENVIRONMENT: string }
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
fetch: (request: Request, env: Env, ctx: ExecutionContext) =>
|
|
29
|
+
withTrace(
|
|
30
|
+
{
|
|
31
|
+
name: 'api.fetch',
|
|
32
|
+
kind: 'server',
|
|
33
|
+
service: 'my-api',
|
|
34
|
+
environment: env.ENVIRONMENT,
|
|
35
|
+
headers: request.headers, // continues inbound traceparent if present
|
|
36
|
+
},
|
|
37
|
+
async (logger) => handle(request, env, logger),
|
|
38
|
+
{
|
|
39
|
+
onError: (_err, logger) => {
|
|
40
|
+
logger.error(_err, 'unhandled')
|
|
41
|
+
return new Response('Internal error', { status: 500 })
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function handle(request: Request, env: Env, logger: Logger) {
|
|
48
|
+
logger.info('handling request')
|
|
49
|
+
return logger.span('db.query', { kind: 'client' }, async (logger) => {
|
|
50
|
+
// ... use logger.tracingHeaders() on outbound fetches to propagate the trace
|
|
51
|
+
return new Response('ok')
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tail consumer usage
|
|
57
|
+
|
|
58
|
+
A minimal Worker:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// src/index.ts
|
|
62
|
+
import { createTailHandler } from 'workers-axiom/tail'
|
|
63
|
+
|
|
64
|
+
interface Env {
|
|
65
|
+
AXIOM_TOKEN: string
|
|
66
|
+
AXIOM_DATASET: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
tail: (events: TraceItem[], env: Env, ctx: ExecutionContext) =>
|
|
71
|
+
createTailHandler({
|
|
72
|
+
axiomToken: env.AXIOM_TOKEN,
|
|
73
|
+
axiomDataset: env.AXIOM_DATASET,
|
|
74
|
+
})(events, env, ctx),
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
// wrangler.jsonc for the tail worker
|
|
80
|
+
{
|
|
81
|
+
"name": "my-tail-worker",
|
|
82
|
+
"main": "src/index.ts",
|
|
83
|
+
"compatibility_date": "2026-01-01",
|
|
84
|
+
"vars": { "AXIOM_DATASET": "my-dataset" }
|
|
85
|
+
// AXIOM_TOKEN set via: wrangler secret put AXIOM_TOKEN
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then opt each producing worker in:
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
// wrangler.jsonc for a producer worker
|
|
93
|
+
{
|
|
94
|
+
"tail_consumers": [{ "service": "my-tail-worker" }]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
Three event types flow through `console.log` from producer to tail:
|
|
101
|
+
|
|
102
|
+
- **`type: "log"` / `"metric"` / `"error"`** — forwarded to Axiom's ingest endpoint (`https://us-east-1.aws.edge.axiom.co/v1/ingest/{dataset}` by default).
|
|
103
|
+
- **`type: "span"`** — wire format defined in `workers-axiom/protocol`. The tail worker filters by `sampled === true`, converts to OTLP, and POSTs to `https://api.axiom.co/v1/traces`.
|
|
104
|
+
- **`type: "summary_properties"`** — emitted by `logger.summary({ ... })`. Merged onto a synthesized `invocation_summary` event the tail worker writes for every worker invocation (with CPU/wall time, request metadata from `event.event`, and `trace_id`).
|
|
105
|
+
|
|
106
|
+
Sampling is decided once at the trace root, propagated via `traceparent`, and stamped on every span. Logs, metrics, and errors are forwarded unconditionally — sampling gates traces only.
|
|
107
|
+
|
|
108
|
+
## Config reference
|
|
109
|
+
|
|
110
|
+
### `createTailHandler(config)`
|
|
111
|
+
|
|
112
|
+
| Field | Required | Default | Notes |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `axiomToken` | yes | — | Axiom API token, bearer-auth. |
|
|
115
|
+
| `axiomDataset` | yes | — | Dataset name for both ingest and OTLP traces. |
|
|
116
|
+
| `ingestBaseUrl` | no | `https://us-east-1.aws.edge.axiom.co/v1/ingest` | Override for EU edge or self-hosted Axiom. Dataset name is appended. |
|
|
117
|
+
| `tracesEndpoint` | no | `https://api.axiom.co/v1/traces` | Full URL of the OTLP traces endpoint. |
|
|
118
|
+
|
|
119
|
+
### `withTrace(options, fn, hooks?)` / `createLogger(options)`
|
|
120
|
+
|
|
121
|
+
| Field | Required | Default | Notes |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `service` | yes | — | OTel `service.name`. |
|
|
124
|
+
| `environment` | no | — | OTel `deployment.environment`. `"development"` enables pretty-printed logs and suppresses metrics. |
|
|
125
|
+
| `level` | no | `"info"` | Log level. Forced to `"debug"` when `environment === "development"`. |
|
|
126
|
+
| `context` | no | `{}` | Correlation fields merged into every emitted record. |
|
|
127
|
+
| `isExpectedError` | no | — | Predicate marking expected/business errors so spans aren't flagged as failed. |
|
|
128
|
+
| `headers` | no | — | Inbound `Request.headers`. Continues `traceparent` if present. |
|
|
129
|
+
| `sampleRate` | no | `1` | Trace-root sampling probability. Inbound traces inherit the upstream verdict. |
|
|
130
|
+
| `kv` | no | — | KV namespace for dynamic log-level lookup at key `logLevel` (or `logLevel:{logLevelKey}`). |
|
|
131
|
+
| `name` | yes (`withTrace`) | — | Root span name, e.g. `"api.fetch"`, `"scheduled.tick"`. |
|
|
132
|
+
| `kind` | no (`withTrace`) | `"server"` | Use `"consumer"` for queue/cron handlers. |
|
|
133
|
+
|
|
134
|
+
## Outbound trace propagation
|
|
135
|
+
|
|
136
|
+
The logger doesn't auto-instrument outbound `fetch`. Wrap each outbound call in `logger.span(...)` and copy `logger.tracingHeaders()` onto the request — that's what makes the downstream service join the same trace:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
await logger.span('backend.fetch', { kind: 'client' }, async (logger) => {
|
|
140
|
+
const headers = new Headers(init.headers)
|
|
141
|
+
for (const [k, v] of logger.tracingHeaders()) headers.set(k, v)
|
|
142
|
+
return fetch(url, { ...init, headers })
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`tracingHeaders()` on an unbound root logger returns an empty `Headers` — you must be inside a `.span(...)` for propagation to fire.
|
|
147
|
+
|
|
148
|
+
## Limitations
|
|
149
|
+
|
|
150
|
+
- Axiom-specific. There is no sink abstraction; the tail worker speaks Axiom's ingest API and OTLP/HTTP JSON. If you need a different backend, fork the `tail/` directory.
|
|
151
|
+
- No browser entry. This package is server-side only, targeted at Cloudflare Workers (and works in any environment with `crypto.getRandomValues` and `console.log`).
|
|
152
|
+
- The `tail()` handler runs forwarding synchronously, not via `waitUntil`, so ingest failures surface in tail logs.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { LogLevel, createLogger, noopLogger, withTrace, type KVLike, type LogContext, type Logger, type LoggerOptions, type WithTraceOptions, } from './logger';
|
|
2
|
+
export type { SpanOptions, TraceContext } from './tracing';
|
|
3
|
+
export { SPAN_EVENT_TYPE, SUMMARY_PROPERTIES_TYPE, isSpanEvent, parseTraceparent, formatTraceparent, type AttributeValue, type SpanEvent, type SpanKind, type SpanStatus, type TraceParent, } from '../protocol';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/producer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,EACT,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,UAAU,CAAA;AAEjB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAI1D,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { LogLevel, createLogger, noopLogger, withTrace, } from './logger';
|
|
2
|
+
// Re-export wire-format types that consumers commonly need when extending
|
|
3
|
+
// the producer (custom attributes, propagation helpers in middleware, etc.).
|
|
4
|
+
export { SPAN_EVENT_TYPE, SUMMARY_PROPERTIES_TYPE, isSpanEvent, parseTraceparent, formatTraceparent, } from '../protocol';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/producer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,GAMV,MAAM,UAAU,CAAA;AAIjB,0EAA0E;AAC1E,6EAA6E;AAC7E,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAMlB,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type AttributeValue, type SpanKind } from '../protocol';
|
|
2
|
+
import { type SpanOptions, type TraceContext } from './tracing';
|
|
3
|
+
/**
|
|
4
|
+
* Bag of correlation fields (requestId, actorId, etc.). Has no semantic meaning
|
|
5
|
+
* to the logger — it's just merged into emitted JSON. First-class metadata
|
|
6
|
+
* (`service`, `environment`, trace IDs) lives on the logger directly, not here.
|
|
7
|
+
*/
|
|
8
|
+
export type LogContext = Record<string, unknown>;
|
|
9
|
+
export interface Logger {
|
|
10
|
+
/** OTel `service.name`. */
|
|
11
|
+
readonly service: string;
|
|
12
|
+
/** OTel `deployment.environment`, if set. */
|
|
13
|
+
readonly environment: string | undefined;
|
|
14
|
+
readonly context: Readonly<LogContext>;
|
|
15
|
+
readonly trace: TraceContext;
|
|
16
|
+
debug(message: string): void;
|
|
17
|
+
info(message: string): void;
|
|
18
|
+
warn(message: string): void;
|
|
19
|
+
error(error: unknown, message?: string): void;
|
|
20
|
+
metric(event: string, data?: Record<string, unknown>): void;
|
|
21
|
+
/**
|
|
22
|
+
* Emit fields the tail worker should merge onto this invocation's
|
|
23
|
+
* `invocation_summary` event. Use for cross-cutting correlation fields
|
|
24
|
+
* (`requestId`, `trace_id`, identity, etc.) that should appear on the
|
|
25
|
+
* per-invocation summary regardless of which log/metric also carried them.
|
|
26
|
+
*
|
|
27
|
+
* Last-write-wins on collision per invocation in the tail worker.
|
|
28
|
+
*/
|
|
29
|
+
summary(properties: Record<string, unknown>): void;
|
|
30
|
+
/** Extend the correlation context. Returns a new logger sharing trace state. */
|
|
31
|
+
child(context: LogContext): Logger;
|
|
32
|
+
/**
|
|
33
|
+
* Run an async function inside a new span. The child logger passed to `fn`
|
|
34
|
+
* has the new span's traceId/spanId bound, so any logs/metrics emitted
|
|
35
|
+
* inside automatically correlate with the span.
|
|
36
|
+
*
|
|
37
|
+
* Errors thrown by `fn` are recorded on the span and re-thrown unchanged.
|
|
38
|
+
* Errors matched by the `isExpectedError` predicate (configured at logger
|
|
39
|
+
* creation) are recorded as `status: 'ok'` — they're expected user-facing
|
|
40
|
+
* failures, not span failures.
|
|
41
|
+
*/
|
|
42
|
+
span<T>(name: string, options: SpanOptions, fn: (logger: Logger) => Promise<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Returns headers to propagate the current trace context on outbound calls.
|
|
45
|
+
* Always include the result on outbound `fetch` calls (including service
|
|
46
|
+
* bindings) to maintain a single distributed trace across services.
|
|
47
|
+
*/
|
|
48
|
+
tracingHeaders(): Headers;
|
|
49
|
+
}
|
|
50
|
+
export declare const LogLevel: {
|
|
51
|
+
readonly debug: 0;
|
|
52
|
+
readonly info: 1;
|
|
53
|
+
readonly warn: 2;
|
|
54
|
+
readonly error: 3;
|
|
55
|
+
};
|
|
56
|
+
export type LogLevel = keyof typeof LogLevel;
|
|
57
|
+
export declare function isValidLogLevel(value: unknown): value is LogLevel;
|
|
58
|
+
export interface KVLike {
|
|
59
|
+
get(key: string): Promise<string | null>;
|
|
60
|
+
}
|
|
61
|
+
export interface LoggerOptions {
|
|
62
|
+
/**
|
|
63
|
+
* Logical service name. Required on every logger because OpenTelemetry
|
|
64
|
+
* mandates `service.name` on every span; loggers without it would produce
|
|
65
|
+
* spans tagged `unknown_service` by collectors.
|
|
66
|
+
*/
|
|
67
|
+
service: string;
|
|
68
|
+
/**
|
|
69
|
+
* Deployment environment. Maps to OTel `deployment.environment` on spans.
|
|
70
|
+
* When set to `'development'`, metrics are suppressed and logs are
|
|
71
|
+
* pretty-printed instead of emitted as JSON.
|
|
72
|
+
*/
|
|
73
|
+
environment?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Explicit log level. Used as the fallback when `kv` is also provided but
|
|
76
|
+
* doesn't contain a valid level. Defaults to `'info'`.
|
|
77
|
+
*/
|
|
78
|
+
level?: LogLevel;
|
|
79
|
+
/** Additional correlation fields (requestId, etc.). */
|
|
80
|
+
context?: LogContext;
|
|
81
|
+
/**
|
|
82
|
+
* Predicate identifying expected/business errors that should not mark spans
|
|
83
|
+
* as failed. Typical use: `(err) => err instanceof MyExpectedError`. The
|
|
84
|
+
* library stays domain-free; the predicate is the explicit contract.
|
|
85
|
+
*/
|
|
86
|
+
isExpectedError?: (err: unknown) => boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Inbound headers carrying `traceparent`. When present, the logger continues
|
|
89
|
+
* the inbound trace; otherwise it starts a fresh one. Pass at HTTP entrypoints.
|
|
90
|
+
*/
|
|
91
|
+
headers?: Headers;
|
|
92
|
+
/**
|
|
93
|
+
* Probability (0..1) of sampling a freshly minted trace. The verdict is
|
|
94
|
+
* decided once at the trace root, propagated to all descendants via
|
|
95
|
+
* `traceparent`, and stamped onto every emitted record. Inbound traces
|
|
96
|
+
* inherit the upstream verdict and ignore this. Defaults to 1 (sample all).
|
|
97
|
+
*
|
|
98
|
+
* Logs/metrics/errors are always forwarded regardless — only span forwarding
|
|
99
|
+
* to Axiom's traces store is gated by the verdict.
|
|
100
|
+
*/
|
|
101
|
+
sampleRate?: number;
|
|
102
|
+
/**
|
|
103
|
+
* KV namespace for log-level lookup. When set, the logger reads `logLevel`
|
|
104
|
+
* (or `logLevel:{logLevelKey}` if a suffix is provided) from KV and uses
|
|
105
|
+
* that level if valid; otherwise falls back to `level`. Forced to `'debug'`
|
|
106
|
+
* in development.
|
|
107
|
+
*/
|
|
108
|
+
kv?: KVLike;
|
|
109
|
+
/** Optional KV key suffix; looks up `logLevel:{logLevelKey}` instead of `logLevel`. */
|
|
110
|
+
logLevelKey?: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create a new logger. If `options.headers` carries a `traceparent`, the trace
|
|
114
|
+
* is continued; otherwise a fresh trace starts.
|
|
115
|
+
*
|
|
116
|
+
* Async because the log level may be resolved from KV. If `options.kv` is
|
|
117
|
+
* omitted, no I/O happens but the function is still async for shape consistency.
|
|
118
|
+
*
|
|
119
|
+
* Most entrypoints should use `withTrace` instead, which calls `createLogger`
|
|
120
|
+
* and wraps the handler in a root span. `createLogger` is the right primitive
|
|
121
|
+
* only when there's no enclosing async scope to open a span around.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createLogger(options: LoggerOptions): Promise<Logger>;
|
|
124
|
+
export interface WithTraceOptions extends LoggerOptions {
|
|
125
|
+
/** Span name. Use the entrypoint's logical operation, e.g. `api.fetch`, `scheduled.tick`. */
|
|
126
|
+
name: string;
|
|
127
|
+
/** Span kind. Defaults to `'server'`. Use `'consumer'` for queue/cron. */
|
|
128
|
+
kind?: SpanKind;
|
|
129
|
+
/** Attributes to attach to the root span. */
|
|
130
|
+
attributes?: Record<string, AttributeValue>;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Entrypoint helper: build a logger and wrap the handler in a root span in one
|
|
134
|
+
* call. The root span is what local child spans (`logger.span(...)`) parent to,
|
|
135
|
+
* so without it those children are orphaned in the trace viewer.
|
|
136
|
+
*
|
|
137
|
+
* For HTTP entrypoints, pass `headers` to continue any inbound `traceparent`.
|
|
138
|
+
* For queue/cron entrypoints, omit `headers` and the trace starts fresh.
|
|
139
|
+
* Pass `kv` to resolve the log level from KV.
|
|
140
|
+
*/
|
|
141
|
+
export declare function withTrace<T>(options: WithTraceOptions, fn: (logger: Logger) => Promise<T>, hooks?: {
|
|
142
|
+
onError?: (err: unknown, logger: Logger) => T | Promise<T>;
|
|
143
|
+
}): Promise<T>;
|
|
144
|
+
/** No-op logger for tests and environments where logging should be suppressed. */
|
|
145
|
+
export declare const noopLogger: Logger;
|
|
146
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/producer/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { formatTraceparent, generateTraceId, parseTraceparent, SUMMARY_PROPERTIES_TYPE, } from '../protocol';
|
|
2
|
+
import { createClock, runSpan, toSpanException, } from './tracing';
|
|
3
|
+
const BASE_LOG_LEVEL_KEY = 'logLevel';
|
|
4
|
+
export const LogLevel = {
|
|
5
|
+
debug: 0,
|
|
6
|
+
info: 1,
|
|
7
|
+
warn: 2,
|
|
8
|
+
error: 3,
|
|
9
|
+
};
|
|
10
|
+
export function isValidLogLevel(value) {
|
|
11
|
+
return typeof value === 'string' && value in LogLevel;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a new logger. If `options.headers` carries a `traceparent`, the trace
|
|
15
|
+
* is continued; otherwise a fresh trace starts.
|
|
16
|
+
*
|
|
17
|
+
* Async because the log level may be resolved from KV. If `options.kv` is
|
|
18
|
+
* omitted, no I/O happens but the function is still async for shape consistency.
|
|
19
|
+
*
|
|
20
|
+
* Most entrypoints should use `withTrace` instead, which calls `createLogger`
|
|
21
|
+
* and wraps the handler in a root span. `createLogger` is the right primitive
|
|
22
|
+
* only when there's no enclosing async scope to open a span around.
|
|
23
|
+
*/
|
|
24
|
+
export async function createLogger(options) {
|
|
25
|
+
const level = await resolveLogLevel(options);
|
|
26
|
+
const incoming = options.headers
|
|
27
|
+
? parseTraceparent(options.headers.get('traceparent'))
|
|
28
|
+
: undefined;
|
|
29
|
+
const sampled = incoming?.sampled ?? Math.random() < (options.sampleRate ?? 1);
|
|
30
|
+
return _createLogger({ ...options, level }, createClock(), {
|
|
31
|
+
trace_id: incoming?.trace_id ?? generateTraceId(),
|
|
32
|
+
span_id: undefined,
|
|
33
|
+
parent_span_id: incoming?.span_id,
|
|
34
|
+
sampled,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function resolveLogLevel(opts) {
|
|
38
|
+
if (opts.environment === 'development')
|
|
39
|
+
return 'debug';
|
|
40
|
+
if (!opts.kv)
|
|
41
|
+
return opts.level ?? 'info';
|
|
42
|
+
const key = opts.logLevelKey ? `${BASE_LOG_LEVEL_KEY}:${opts.logLevelKey}` : BASE_LOG_LEVEL_KEY;
|
|
43
|
+
const value = await opts.kv.get(key);
|
|
44
|
+
return isValidLogLevel(value) ? value : (opts.level ?? 'info');
|
|
45
|
+
}
|
|
46
|
+
function _createLogger(options, clock, trace) {
|
|
47
|
+
const { service, environment, isExpectedError, level = 'info' } = options;
|
|
48
|
+
const context = { ...options.context };
|
|
49
|
+
const isDev = environment === 'development';
|
|
50
|
+
const shouldLog = (logLevel) => {
|
|
51
|
+
return LogLevel[logLevel] >= LogLevel[level];
|
|
52
|
+
};
|
|
53
|
+
const emit = (entry) => {
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.log(JSON.stringify({
|
|
56
|
+
service,
|
|
57
|
+
environment,
|
|
58
|
+
...context,
|
|
59
|
+
trace_id: trace.trace_id,
|
|
60
|
+
span_id: trace.span_id,
|
|
61
|
+
parent_span_id: trace.parent_span_id,
|
|
62
|
+
sampled: trace.sampled,
|
|
63
|
+
...entry,
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
const log = (logLevel, message) => {
|
|
67
|
+
if (!shouldLog(logLevel))
|
|
68
|
+
return;
|
|
69
|
+
if (isDev) {
|
|
70
|
+
const levelTag = logLevel.toUpperCase().padEnd(5);
|
|
71
|
+
// eslint-disable-next-line no-console
|
|
72
|
+
console.log(`${levelTag} ${message}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
emit({ type: 'log', level: logLevel, message });
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
service,
|
|
80
|
+
environment,
|
|
81
|
+
context,
|
|
82
|
+
trace,
|
|
83
|
+
debug(message) {
|
|
84
|
+
log('debug', message);
|
|
85
|
+
},
|
|
86
|
+
info(message) {
|
|
87
|
+
log('info', message);
|
|
88
|
+
},
|
|
89
|
+
warn(message) {
|
|
90
|
+
log('warn', message);
|
|
91
|
+
},
|
|
92
|
+
error(error, message) {
|
|
93
|
+
if (!shouldLog('error'))
|
|
94
|
+
return;
|
|
95
|
+
const exception = error instanceof Error ? toSpanException(error) : undefined;
|
|
96
|
+
const body = message ?? (error instanceof Error ? error.message : String(error));
|
|
97
|
+
if (isDev) {
|
|
98
|
+
// eslint-disable-next-line no-console
|
|
99
|
+
console.log(`ERROR ${body}${exception?.stacktrace ? `\n${exception.stacktrace}` : ''}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
emit({ type: 'error', message: body, ...(exception !== undefined ? { exception } : {}) });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
metric(event, data) {
|
|
106
|
+
if (isDev)
|
|
107
|
+
return;
|
|
108
|
+
emit({ type: 'metric', event, ...data });
|
|
109
|
+
},
|
|
110
|
+
summary(properties) {
|
|
111
|
+
// Bypass `emit` so trace-context, service, environment, and inherited
|
|
112
|
+
// context don't ride along. Summary records are invocation-scoped and
|
|
113
|
+
// exist only to feed `invocation_summary` in the tail worker.
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.log(JSON.stringify({ type: SUMMARY_PROPERTIES_TYPE, ...properties }));
|
|
116
|
+
},
|
|
117
|
+
child(newContext) {
|
|
118
|
+
return _createLogger({ ...options, context: { ...context, ...newContext } }, clock, trace);
|
|
119
|
+
},
|
|
120
|
+
async span(name, spanOptions, fn) {
|
|
121
|
+
return runSpan({
|
|
122
|
+
parent: trace,
|
|
123
|
+
resource: { service, environment },
|
|
124
|
+
name,
|
|
125
|
+
options: spanOptions,
|
|
126
|
+
clock,
|
|
127
|
+
isExpectedError,
|
|
128
|
+
fn: (childCtx) => fn(_createLogger(options, clock, childCtx)),
|
|
129
|
+
onUnexpectedError: (err, childCtx) => _createLogger(options, clock, childCtx).error(err, `span "${name}" failed`),
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
tracingHeaders() {
|
|
133
|
+
const headers = new Headers();
|
|
134
|
+
if (trace.span_id !== undefined) {
|
|
135
|
+
headers.set('traceparent', formatTraceparent(trace.trace_id, trace.span_id, trace.sampled));
|
|
136
|
+
}
|
|
137
|
+
return headers;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Entrypoint helper: build a logger and wrap the handler in a root span in one
|
|
143
|
+
* call. The root span is what local child spans (`logger.span(...)`) parent to,
|
|
144
|
+
* so without it those children are orphaned in the trace viewer.
|
|
145
|
+
*
|
|
146
|
+
* For HTTP entrypoints, pass `headers` to continue any inbound `traceparent`.
|
|
147
|
+
* For queue/cron entrypoints, omit `headers` and the trace starts fresh.
|
|
148
|
+
* Pass `kv` to resolve the log level from KV.
|
|
149
|
+
*/
|
|
150
|
+
export async function withTrace(options, fn, hooks) {
|
|
151
|
+
const { name, kind = 'server', attributes, ...loggerOptions } = options;
|
|
152
|
+
const logger = await createLogger(loggerOptions);
|
|
153
|
+
try {
|
|
154
|
+
return await logger.span(name, { kind, attributes }, fn);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (hooks?.onError) {
|
|
158
|
+
return await hooks.onError(err, logger);
|
|
159
|
+
}
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** No-op logger for tests and environments where logging should be suppressed. */
|
|
164
|
+
export const noopLogger = {
|
|
165
|
+
service: '',
|
|
166
|
+
environment: undefined,
|
|
167
|
+
context: {},
|
|
168
|
+
trace: { trace_id: '', span_id: undefined, parent_span_id: undefined, sampled: false },
|
|
169
|
+
debug: () => { },
|
|
170
|
+
info: () => { },
|
|
171
|
+
warn: () => { },
|
|
172
|
+
error: () => { },
|
|
173
|
+
metric: () => { },
|
|
174
|
+
summary: () => { },
|
|
175
|
+
child: () => noopLogger,
|
|
176
|
+
span: (_name, _options, fn) => fn(noopLogger),
|
|
177
|
+
tracingHeaders: () => new Headers(),
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/producer/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monotonic-anchored wall clock for span timing.
|
|
3
|
+
*
|
|
4
|
+
* Span timestamps come from a single anchor captured at root-logger creation:
|
|
5
|
+
* - `unixMsAtAnchor` — Date.now() at anchor time (wall clock)
|
|
6
|
+
* - `perfAtAnchor` — performance.now() at anchor time (monotonic, sub-ms)
|
|
7
|
+
*
|
|
8
|
+
* Subsequent reads compute `unixMsAtAnchor + (performance.now() - perfAtAnchor)`.
|
|
9
|
+
* This preserves sub-ms ordering within a request and avoids drift if Date.now()
|
|
10
|
+
* jumps mid-request (rare on Workers, but free to guard against).
|
|
11
|
+
*
|
|
12
|
+
* Cross-service clock skew is not addressed here — Workers in different colos may
|
|
13
|
+
* have small wall-clock differences. Tracing UIs tolerate this; it's not fixable
|
|
14
|
+
* from inside the worker.
|
|
15
|
+
*/
|
|
16
|
+
export interface Clock {
|
|
17
|
+
/** Returns the current time as Unix epoch nanoseconds (string, no precision loss). */
|
|
18
|
+
nowUnixNano(): string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createClock(): Clock;
|
|
21
|
+
//# sourceMappingURL=clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/producer/tracing/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,KAAK;IACpB,sFAAsF;IACtF,WAAW,IAAI,MAAM,CAAA;CACtB;AAED,wBAAgB,WAAW,IAAI,KAAK,CAWnC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createClock() {
|
|
2
|
+
const unixMsAtAnchor = Date.now();
|
|
3
|
+
const perfAtAnchor = performanceNowOrFallback();
|
|
4
|
+
return {
|
|
5
|
+
nowUnixNano() {
|
|
6
|
+
const elapsedMs = performanceNowOrFallback() - perfAtAnchor;
|
|
7
|
+
const unixMs = unixMsAtAnchor + elapsedMs;
|
|
8
|
+
return BigInt(Math.trunc(unixMs * 1_000_000)).toString();
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function performanceNowOrFallback() {
|
|
13
|
+
return typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
14
|
+
? performance.now()
|
|
15
|
+
: Date.now();
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/producer/tracing/clock.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,WAAW;IACzB,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACjC,MAAM,YAAY,GAAG,wBAAwB,EAAE,CAAA;IAE/C,OAAO;QACL,WAAW;YACT,MAAM,SAAS,GAAG,wBAAwB,EAAE,GAAG,YAAY,CAAA;YAC3D,MAAM,MAAM,GAAG,cAAc,GAAG,SAAS,CAAA;YACzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC1D,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,wBAAwB;IAC/B,OAAO,OAAO,WAAW,KAAK,WAAW,IAAI,OAAO,WAAW,CAAC,GAAG,KAAK,UAAU;QAChF,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE;QACnB,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/producer/tracing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EACL,OAAO,EACP,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/producer/tracing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAc,MAAM,SAAS,CAAA;AACjD,OAAO,EACL,OAAO,EACP,eAAe,GAIhB,MAAM,QAAQ,CAAA"}
|