loggily 0.8.0 → 0.10.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 +43 -221
- package/dist/context.d.mts +2 -2
- package/dist/context.d.mts.map +1 -1
- package/dist/context.mjs +3 -3
- package/dist/context.mjs.map +1 -1
- package/dist/{core-Dm2PQUoS.d.mts → core-DHB4KaG1.d.mts} +74 -5
- package/dist/core-DHB4KaG1.d.mts.map +1 -0
- package/dist/{core-B3pox577.mjs → core-DKwMMOZw.mjs} +344 -47
- package/dist/core-DKwMMOZw.mjs.map +1 -0
- package/dist/{file-writer-DtaY8Njt.d.mts → file-writer-BhJzFNKE.d.mts} +10 -3
- package/dist/file-writer-BhJzFNKE.d.mts.map +1 -0
- package/dist/index.browser.d.mts +4 -4
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/metrics.d.mts +1 -1
- package/dist/metrics.mjs.map +1 -1
- package/dist/otel.d.mts +1 -1
- package/dist/otel.mjs.map +1 -1
- package/dist/{pipeline-Cl9-wCmt.d.mts → pipeline-D0omS7eF.d.mts} +14 -2
- package/dist/pipeline-D0omS7eF.d.mts.map +1 -0
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.mts.map +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/worker.mjs.map +1 -1
- package/package.json +7 -3
- package/dist/core-B3pox577.mjs.map +0 -1
- package/dist/core-Dm2PQUoS.d.mts.map +0 -1
- package/dist/file-writer-DtaY8Njt.d.mts.map +0 -1
- package/dist/pipeline-Cl9-wCmt.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -1,266 +1,88 @@
|
|
|
1
|
-
# Loggily
|
|
2
|
-
|
|
3
|
-
**Clarity without the clutter.**
|
|
4
|
-
|
|
5
|
-
Debugs, logs, and spans -- one API.
|
|
6
|
-
|
|
7
1
|
[](https://github.com/beorn/loggily/actions/workflows/test.yml)
|
|
8
2
|
[](https://www.npmjs.com/package/loggily)
|
|
9
3
|
[](https://bundlephobia.com/package/loggily)
|
|
10
4
|
[](LICENSE)
|
|
11
5
|
|
|
12
|
-
|
|
6
|
+
# Loggily — Clarity without the clutter
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
**Debugs, logs, and spans — structured and dev, server and browser — one API.**
|
|
9
|
+
|
|
10
|
+
Replace `debug` + your JSON logger + ad-hoc timers with one namespace tree and one output pipeline. Pure TypeScript, zero dependencies, ~3 KB.
|
|
15
11
|
|
|
16
12
|
```typescript
|
|
17
13
|
import { createLogger } from "loggily"
|
|
18
14
|
|
|
19
|
-
const log = createLogger("myapp"
|
|
15
|
+
const log = createLogger("myapp") // zero config — reads LOG_LEVEL, DEBUG from env
|
|
20
16
|
|
|
21
17
|
log.info?.("server started", { port: 3000 })
|
|
22
18
|
log.debug?.("cache hit", { key: "user:42" })
|
|
23
19
|
log.error?.(new Error("connection lost"))
|
|
24
20
|
```
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
14:32:15 INFO myapp server started {port: 3000}
|
|
30
|
-
14:32:15 DEBUG myapp cache hit {key: "user:42"}
|
|
31
|
-
14:32:15 ERROR myapp connection lost
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Set `NODE_ENV=production` and the same calls emit structured JSON:
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{ "time": "2024-01-15T14:32:15.123Z", "level": "info", "name": "myapp", "msg": "server started", "port": 3000 }
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Why the `?.`
|
|
22
|
+
**The `?.` optional chaining trick** short-circuits the entire call when a log level is disabled, so nothing evaluates — not the string interpolation, not the function calls, nothing. In benchmarks, that's [~22x faster](https://loggily.dev/guide/benchmarks) than conventional noop loggers. [See how Loggily compares →](https://loggily.dev/guide/comparison)
|
|
41
23
|
|
|
42
|
-
|
|
24
|
+
## Getting Started
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
```console
|
|
27
|
+
$ npm install loggily
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
$ DEBUG='*' node app # show all debug output
|
|
30
|
+
$ DEBUG='myapp:db' node app # only database logs
|
|
31
|
+
$ LOG_FILE=/tmp/app.log node app # write to file
|
|
32
|
+
$ NODE_ENV=production node app # structured JSON output
|
|
33
|
+
$ TRACE=1 node app # enable span timing
|
|
49
34
|
```
|
|
50
35
|
|
|
51
|
-
With Loggily, optional chaining short-circuits the entire call:
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
log.debug?.(`state: ${JSON.stringify(computeExpensiveState())}`)
|
|
55
|
-
// nothing runs when debug is off — not the function, not the stringify, not the template
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
In benchmarks with expensive disabled log arguments, this is [~22x faster](https://loggily.dev/guide/benchmarks) than a conventional noop logger.
|
|
59
|
-
|
|
60
|
-
## Install
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm install loggily
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
| Requirement | Version |
|
|
67
|
-
| ------------- | ----------------------------------------------- |
|
|
68
|
-
| Node.js | >= 23.6 |
|
|
69
|
-
| Bun | 1.0+ |
|
|
70
|
-
| TypeScript | 5.2+ for `using`; `.end()` works on any version |
|
|
71
|
-
| Module format | ESM-only |
|
|
72
|
-
| Browser | Supported via conditional export |
|
|
73
|
-
|
|
74
|
-
Loggily uses `Symbol.dispose` (TC39 Explicit Resource Management) for span cleanup, which requires a modern runtime.
|
|
75
|
-
|
|
76
|
-
## Features
|
|
77
|
-
|
|
78
|
-
- **Zero-cost disabled logs** -- `?.` short-circuits the entire call: no string interpolation, no JSON.stringify, no function evaluation. [~22x faster](https://loggily.dev/guide/benchmarks) than noop loggers.
|
|
79
|
-
- **Config pipeline** -- `createLogger("app", [config, console, { file }, stage, [branch]])`. Objects configure, arrays branch, values write.
|
|
80
|
-
- **Namespace hierarchy** -- `DEBUG=myapp:db` shows only database output. Same patterns as the `debug` package.
|
|
81
|
-
- **Spans** -- `using span = log.span("name")`. Duration, parent-child tracking, trace IDs, custom data. Control per-pipeline with `{ spans: true/false }`.
|
|
82
|
-
- **Dev & production** -- colorized console in development, structured JSON in production. Same code.
|
|
83
|
-
- **Child loggers** -- `log.child("auth")` extends namespace, `log.child({ requestId })` adds context.
|
|
84
|
-
- **Async context** -- `AsyncLocalStorage` propagation: every log in a request's async chain inherits trace/span IDs automatically.
|
|
85
|
-
- **Error cause chains** -- `log.error?.(err)` serializes `Error.cause` recursively (up to 3 levels).
|
|
86
|
-
- **Worker threads** -- pipeline-based: `createWorkerLogger(postMessage, "ns")` on worker, `createWorkerLogHandler()` on main. Same events, same pipeline.
|
|
87
|
-
- **OpenTelemetry bridge** -- `toOtel({ api })` stage forwards events to any OTLP backend. Transparent: events pass through to subsequent pipeline elements.
|
|
88
|
-
- **Pino transport compatible** -- works with object-mode writable sinks (compatible with Pino transport interface). Events use Loggily's record shape.
|
|
89
|
-
- **Span metrics** -- `{ metrics: true }` in config auto-creates a collector on `log.metrics` (p50/p95/p99). Or use `withMetrics(collector)` for shared/custom collectors.
|
|
90
|
-
- **Head-based sampling** -- `setSampleRate(0.1)` to sample 10% of traces.
|
|
91
|
-
- **Composable plugins** -- `pipe(baseCreateLogger, withSpans(), myPlugin())` to build custom factories.
|
|
92
|
-
- **Browser support** -- bundlers auto-select the browser entry point via `browser` condition.
|
|
93
|
-
- **~3 KB, zero dependencies.**
|
|
94
|
-
|
|
95
|
-
## Quick Start
|
|
96
|
-
|
|
97
36
|
```typescript
|
|
98
37
|
import { createLogger } from "loggily"
|
|
38
|
+
import { toOtel } from "loggily/otel"
|
|
99
39
|
|
|
100
|
-
|
|
40
|
+
// Config pipeline — objects configure, arrays branch, values write
|
|
41
|
+
const log = createLogger("myapp", [
|
|
42
|
+
{ level: "debug", metrics: true }, // config object — sets scope
|
|
43
|
+
toOtel({ api: otelApi }), // stage — forwards to Jaeger/Grafana/Datadog
|
|
44
|
+
pinoTransport, // writable — { write } receives events
|
|
45
|
+
{ file: "...", format: "json" }, // file sink — formatted strings
|
|
46
|
+
[{ level: "warn" }, { file: "..." }], // branch — sub-pipeline with own scope
|
|
47
|
+
console, // colorized dev output, JSON in production
|
|
48
|
+
])
|
|
101
49
|
|
|
50
|
+
// Structured logging
|
|
102
51
|
log.info?.("server started", { port: 3000 })
|
|
103
|
-
log.debug?.(
|
|
104
|
-
log.error?.(new Error("
|
|
52
|
+
log.debug?.(`state: ${expensiveFunc()}`) // skipped if debug off
|
|
53
|
+
log.error?.(new Error("connection lost"))
|
|
105
54
|
|
|
106
|
-
// Child loggers
|
|
55
|
+
// Child loggers — extend namespace, add context
|
|
107
56
|
const dbLog = log.child("db", { pool: "main" }) // namespace: "myapp:db"
|
|
108
57
|
|
|
109
|
-
// Spans
|
|
58
|
+
// Spans — time any operation, auto-track parent/child + trace IDs
|
|
59
|
+
// AsyncLocalStorage propagation: logs in async chains inherit span context
|
|
110
60
|
{
|
|
111
|
-
using span = dbLog.span("query", { table: "users" })
|
|
112
|
-
const users = await queryUsers() //
|
|
113
|
-
span.spanData.count = users.length
|
|
61
|
+
using span = dbLog.span?.("query", { table: "users" })
|
|
62
|
+
const users = await queryUsers() // logs inside queryUsers() get trace IDs
|
|
63
|
+
if (span) span.spanData.count = users.length
|
|
114
64
|
}
|
|
115
65
|
// → SPAN myapp:db:query (45ms) {count: 100, table: "users"}
|
|
116
|
-
```
|
|
117
66
|
|
|
118
|
-
|
|
67
|
+
// Metrics — p50/p95/p99 from spans
|
|
68
|
+
log.metrics.summary() // myapp:db:query: 42 spans, mean=3.2ms, p95=8.4ms
|
|
119
69
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
import { createLogger } from "loggily"
|
|
124
|
-
import { toOtel } from "loggily/otel"
|
|
125
|
-
import * as otelApi from "@opentelemetry/api"
|
|
126
|
-
|
|
127
|
-
const log = createLogger("myapp", [
|
|
128
|
-
// "myapp" -- namespace, filter with DEBUG=myapp
|
|
129
|
-
{ level: "debug", metrics: true }, // config object -- sets scope
|
|
130
|
-
toOtel({ api: otelApi }), // stage -- transforms/forwards events
|
|
131
|
-
pinoTransport, // writable -- { write } receives raw Events
|
|
132
|
-
{ file: "/tmp/app.log", format: "json" }, // file sink -- writes formatted strings
|
|
133
|
-
[{ level: "error" }, { file: "/tmp/err.log" }], // branch -- sub-pipeline with own scope
|
|
134
|
-
console, // console -- colorized, human-readable
|
|
135
|
-
])
|
|
136
|
-
|
|
137
|
-
// Custom writable -- any { write } receives raw Event objects
|
|
138
|
-
const log2 = createLogger("ingest", [
|
|
139
|
-
{ write: (event) => fetch("/ingest", { method: "POST", body: JSON.stringify(event) }) },
|
|
140
|
-
console,
|
|
141
|
-
])
|
|
142
|
-
|
|
143
|
-
// Custom stage -- functions transform, filter, or enrich events
|
|
144
|
-
const log3 = createLogger("filtered", [
|
|
145
|
-
(event) => (event.kind === "log" && event.message.includes("secret") ? null : event),
|
|
146
|
-
(event) => ({ ...event, props: { ...event.props, host: os.hostname() } }),
|
|
147
|
-
console,
|
|
148
|
-
])
|
|
149
|
-
|
|
150
|
-
// Metrics -- { metrics: true } auto-creates a collector on log.metrics
|
|
151
|
-
for (const [name, s] of log.metrics.all()) {
|
|
152
|
-
if (s.p95 > 100) console.warn(`${name} is slow: p95=${s.p95}ms`)
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Composition with plugins
|
|
157
|
-
|
|
158
|
-
`createLogger` is `pipe(baseCreateLogger, withEnvDefaults(), withSpans(), withConfigMetrics())`. For full manual control:
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
import { baseCreateLogger, pipe, withSpans, withEnvDefaults } from "loggily"
|
|
162
|
-
|
|
163
|
-
// Custom factory — choose exactly which plugins to include
|
|
70
|
+
// Composable — build custom factories
|
|
164
71
|
const myCreateLogger = pipe(baseCreateLogger, withSpans(), myPlugin())
|
|
165
|
-
const log = myCreateLogger("myapp", [console])
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
`baseCreateLogger` does NOT include `withSpans()` or `withEnvDefaults()` — loggers it creates cannot create spans and do not read environment variables.
|
|
169
|
-
|
|
170
|
-
### Test helper
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
import { createTestLogger } from "loggily"
|
|
174
|
-
const log = createTestLogger("test") // all levels enabled, console output
|
|
175
72
|
```
|
|
176
73
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
| Variable | Values | Default |
|
|
180
|
-
| ------------------- | --------------------------------------- | --------- |
|
|
181
|
-
| `LOG_LEVEL` | trace, debug, info, warn, error, silent | `info` |
|
|
182
|
-
| `LOG_FORMAT` | console, json | `console` |
|
|
183
|
-
| `LOG_FILE` | file path | (none) |
|
|
184
|
-
| `DEBUG` | `*`, namespace prefixes, `-prefix` | (none) |
|
|
185
|
-
| `TRACE` | `1`, `true`, namespace prefixes | (none) |
|
|
186
|
-
| `TRACE_FORMAT` | json | (none) |
|
|
187
|
-
| `TRACE_ID_FORMAT` | simple, w3c | `simple` |
|
|
188
|
-
| `TRACE_SAMPLE_RATE` | 0.0 -- 1.0 | `1.0` |
|
|
189
|
-
| `NODE_ENV` | production | (none) |
|
|
190
|
-
|
|
191
|
-
### Namespace filter patterns
|
|
192
|
-
|
|
193
|
-
| Pattern | Matches |
|
|
194
|
-
| ------------------ | -------------------------------------------------------------- |
|
|
195
|
-
| `*` | Everything |
|
|
196
|
-
| `myapp` | Exact match + children (`myapp`, `myapp:db`, `myapp:db:query`) |
|
|
197
|
-
| `myapp:*` | Same as `myapp` — explicit wildcard |
|
|
198
|
-
| `-myapp:sql` | Exclude `myapp:sql` and its children |
|
|
199
|
-
| `myapp,-myapp:sql` | Include myapp, exclude sql subtree |
|
|
200
|
-
|
|
201
|
-
### Types
|
|
202
|
-
|
|
203
|
-
Key types exported for power users:
|
|
204
|
-
|
|
205
|
-
| Type | Description |
|
|
206
|
-
| ------------------- | ---------------------------------------------------------------- |
|
|
207
|
-
| `LogEvent` | A log message event (kind, level, namespace, message, props) |
|
|
208
|
-
| `SpanEvent` | A span timing event (kind, namespace, duration, spanId, traceId) |
|
|
209
|
-
| `Event` | `LogEvent \| SpanEvent` |
|
|
210
|
-
| `Stage` | `(event: Event) => Event \| null \| void` |
|
|
211
|
-
| `Pipeline` | `{ dispatch, level, dispose }` |
|
|
212
|
-
| `ConditionalLogger` | Logger with `?.`-compatible methods |
|
|
213
|
-
| `ConfigElement` | Union of all valid config array elements |
|
|
214
|
-
| `ConfigObject` | Scope config: `{ level?, ns?, format?, spans? }` |
|
|
215
|
-
| `FileDescriptor` | File output: `{ file, level?, ns?, format? }` |
|
|
216
|
-
| `Writable` | Any object with `{ write, objectMode? }` |
|
|
217
|
-
|
|
218
|
-
`buildPipeline()` is exported for direct pipeline construction.
|
|
219
|
-
|
|
220
|
-
### Subpath exports
|
|
221
|
-
|
|
222
|
-
| Import path | Contents |
|
|
223
|
-
| ----------------- | -------------------------------------------------------------------- |
|
|
224
|
-
| `loggily` | Core API, types, pipeline builder |
|
|
225
|
-
| `loggily/context` | AsyncLocalStorage context propagation (Node.js) |
|
|
226
|
-
| `loggily/worker` | Worker thread logger + message handlers |
|
|
227
|
-
| `loggily/otel` | OpenTelemetry bridge (`toOtel` stage) |
|
|
228
|
-
| `loggily/metrics` | Span metrics collection (`{ metrics: true }` or explicit collectors) |
|
|
229
|
-
|
|
230
|
-
## Compatibility & Destinations
|
|
231
|
-
|
|
232
|
-
- **OpenTelemetry** -- `toOtel({ api })` forwards to Jaeger, Grafana, Datadog, or any OTLP backend
|
|
233
|
-
- **Sentry** -- capture errors via a 3-line stage function
|
|
234
|
-
- **Pino transports** -- works with object-mode writable sinks (compatible with Pino transport interface)
|
|
235
|
-
- **Elasticsearch / OpenSearch** -- post JSON events directly
|
|
236
|
-
- **AWS CloudWatch** -- writable calling `putLogEvents`
|
|
237
|
-
- **Prometheus** -- expose `log.metrics` as a `/metrics` endpoint
|
|
238
|
-
- **`DEBUG=` patterns** -- same namespace filter syntax as the `debug` package
|
|
239
|
-
- **W3C Trace Context** -- `traceparent()` generates standard headers
|
|
240
|
-
- **Browser** -- bundlers auto-select the browser entry point
|
|
241
|
-
- **Worker threads** -- pipeline-based forwarding via `postMessage`
|
|
242
|
-
|
|
243
|
-
See the [Destinations guide](https://loggily.dev/guide/destinations) for copy-paste recipes.
|
|
244
|
-
|
|
245
|
-
## Why this exists
|
|
246
|
-
|
|
247
|
-
Loggily was built while developing a terminal UI where disabled debug logs inside the render loop were eating frame time. No existing logger solved the "disabled calls should cost nothing" problem at the language level, so `?.` became the foundation.
|
|
74
|
+
Also supports [async context propagation](https://loggily.dev/api/context), [worker threads](https://loggily.dev/guide/workers), and [browser](https://loggily.dev/guide/getting-started#browser-support).
|
|
248
75
|
|
|
249
|
-
|
|
76
|
+
**Works with:** [OpenTelemetry](https://loggily.dev/api/otel) (Jaeger, Grafana, Datadog, any OTLP backend) · [Pino transports](https://loggily.dev/guide/destinations#pino) · Sentry · Elasticsearch · AWS CloudWatch · Prometheus · [W3C Trace Context](https://loggily.dev/api/tracing) · [`DEBUG=` patterns](https://loggily.dev/guide/journey#namespaces) · [See all destinations →](https://loggily.dev/guide/destinations)
|
|
250
77
|
|
|
251
|
-
##
|
|
78
|
+
## About
|
|
252
79
|
|
|
253
|
-
|
|
254
|
-
- **You need log rotation, file compression, or dozens of transport plugins.** Pino's transport ecosystem is deeper. (You can use Pino transports with Loggily's `objectMode` writables, but Pino owns that ecosystem.)
|
|
255
|
-
- **You're not on a modern runtime.** Loggily requires Node.js >= 23.6 or Bun >= 1.0.
|
|
80
|
+
Born from the frustration of juggling separate systems for debug logging, structured production logs, metrics, and spans — each with its own API, config, and propagation — and then duplicating the whole setup again because browser and terminal needed completely different pipelines and destinations. Loggily unifies it all: one API, one config, one pipeline that works everywhere, without the overhead when logs are off.
|
|
256
81
|
|
|
257
|
-
|
|
82
|
+
**Requirements:** Node.js ≥ 23.6 or Bun ≥ 1.0. ESM-only. TypeScript 5.2+ for `using` (`.end()` works on any version). [Browser supported](https://loggily.dev/guide/getting-started#browser-support) via conditional export.
|
|
258
83
|
|
|
259
|
-
-
|
|
260
|
-
- **[Full docs site](https://loggily.dev/)** -- guides, API reference, migration guides
|
|
261
|
-
- [Comparison](https://loggily.dev/guide/comparison) -- what Loggily does, compatibility, when to use something else
|
|
262
|
-
- [Migration from debug](https://loggily.dev/guide/migration-from-debug) -- step-by-step migration guide
|
|
84
|
+
**When not to use Loggily:** if you need auto-instrumentation (HTTP, database, gRPC) use OpenTelemetry's SDK directly; if you need log rotation or dozens of transport plugins, Pino's ecosystem is deeper.
|
|
263
85
|
|
|
264
|
-
|
|
86
|
+
**Docs:** [Get Started](https://loggily.dev/guide/getting-started) · [Full docs](https://loggily.dev/) · [Comparison](https://loggily.dev/guide/comparison) · [Why Loggily](https://loggily.dev/guide/why)
|
|
265
87
|
|
|
266
88
|
[MIT](LICENSE)
|
package/dist/context.d.mts
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* const log = createLogger("myapp")
|
|
16
16
|
* {
|
|
17
|
-
* using span = log.span("request")
|
|
17
|
+
* using span = log.span?.("request")
|
|
18
18
|
* // All logs and child spans within this async context
|
|
19
19
|
* // automatically inherit trace_id and span_id
|
|
20
|
-
* log.info("inside span") // auto-tagged with trace_id, span_id
|
|
20
|
+
* log.info?.("inside span") // auto-tagged with trace_id, span_id
|
|
21
21
|
*
|
|
22
22
|
* const current = getCurrentSpan()
|
|
23
23
|
* // current === { spanId: "sp_1", traceId: "tr_1", parentId: null }
|
package/dist/context.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.mts","names":[],"sources":["../src/context.ts"],"mappings":";;AAgCA;;;;;;;;;AA2BA;;;;;AAuBA;;;;;AAMA;;;;;AASA;AAAA,UAjEiB,WAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;AAAA;;;;;;;;iBAwBK,wBAAA,CAAA;
|
|
1
|
+
{"version":3,"file":"context.d.mts","names":[],"sources":["../src/context.ts"],"mappings":";;AAgCA;;;;;;;;;AA2BA;;;;;AAuBA;;;;;AAMA;;;;;AASA;AAAA,UAjEiB,WAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;AAAA;;;;;;;;iBAwBK,wBAAA,CAAA;AA2EhB;;;;AAAA,iBApDgB,yBAAA,CAAA;AA0EhB;AAAA,iBApEgB,2BAAA,CAAA;;;;;;iBASA,cAAA,CAAA,GAAkB,WAAA;;;;;;;;;AAqElC;;;iBArDgB,gBAAA,CACd,MAAA,UACA,OAAA,UACA,QAAA;;;;;;;;iBAkBc,eAAA,CAAgB,MAAA;;;;;;;;;iBAsBhB,gBAAA,GAAA,CAAoB,OAAA,EAAS,WAAA,EAAa,EAAA,QAAU,CAAA,GAAI,CAAA;;;;;;iBAUxD,cAAA,CAAA,GAAkB,MAAA"}
|
package/dist/context.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as _setContextHooks, t as _clearContextHooks } from "./core-
|
|
1
|
+
import { n as _setContextHooks, t as _clearContextHooks } from "./core-DKwMMOZw.mjs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
//#region src/context.ts
|
|
4
4
|
/**
|
|
@@ -16,10 +16,10 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
16
16
|
*
|
|
17
17
|
* const log = createLogger("myapp")
|
|
18
18
|
* {
|
|
19
|
-
* using span = log.span("request")
|
|
19
|
+
* using span = log.span?.("request")
|
|
20
20
|
* // All logs and child spans within this async context
|
|
21
21
|
* // automatically inherit trace_id and span_id
|
|
22
|
-
* log.info("inside span") // auto-tagged with trace_id, span_id
|
|
22
|
+
* log.info?.("inside span") // auto-tagged with trace_id, span_id
|
|
23
23
|
*
|
|
24
24
|
* const current = getCurrentSpan()
|
|
25
25
|
* // current === { spanId: "sp_1", traceId: "tr_1", parentId: null }
|
package/dist/context.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * AsyncLocalStorage-based context propagation for loggily — Node.js/Bun only.\n *\n * Separated from core logger to allow tree-shaking in browser bundles.\n * When enabled, new spans automatically parent to the current context span,\n * and writeLog() auto-tags with trace_id/span_id from context.\n *\n * @example\n * ```typescript\n * import { enableContextPropagation, getCurrentSpan } from \"loggily/context\"\n *\n * enableContextPropagation()\n *\n * const log = createLogger(\"myapp\")\n * {\n * using span = log.span(\"request\")\n * // All logs and child spans within this async context\n * // automatically inherit trace_id and span_id\n * log.info(\"inside span\") // auto-tagged with trace_id, span_id\n *\n * const current = getCurrentSpan()\n * // current === { spanId: \"sp_1\", traceId: \"tr_1\", parentId: null }\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\"\nimport { _setContextHooks, _clearContextHooks } from \"./core.js\"\n\n// ============ Types ============\n\n/** Minimal span context stored in AsyncLocalStorage */\nexport interface SpanContext {\n readonly spanId: string\n readonly traceId: string\n readonly parentId: string | null\n}\n\n// ============ State ============\n\nlet storage: AsyncLocalStorage<SpanContext> | null = null\nlet contextEnabled = false\n\n/**\n * Map from spanId → the SpanContext that was active when the span was entered.\n * Used to restore the exact previous context on exit, avoiding corruption\n * from non-LIFO end() ordering.\n */\nconst previousContexts = new Map<string, SpanContext | null>()\n\n// ============ API ============\n\n/**\n * Enable AsyncLocalStorage-based context propagation.\n * Once enabled, new spans automatically parent to the current context span,\n * and log messages are auto-tagged with trace_id/span_id.\n *\n * **Node.js/Bun only** — not available in browser environments.\n */\nexport function enableContextPropagation(): void {\n if (!storage) {\n storage = new AsyncLocalStorage<SpanContext>()\n }\n contextEnabled = true\n\n // Register hooks with core.ts\n _setContextHooks({\n getContextTags,\n getContextParent() {\n const span = getCurrentSpan()\n if (!span) return null\n return { spanId: span.spanId, traceId: span.traceId }\n },\n enterContext: enterSpanContext,\n exitContext: exitSpanContext,\n })\n}\n\n/**\n * Disable context propagation.\n * Existing spans continue to work, but new spans won't auto-parent.\n */\nexport function disableContextPropagation(): void {\n contextEnabled = false\n _clearContextHooks()\n}\n\n/** Check if context propagation is enabled */\nexport function isContextPropagationEnabled(): boolean {\n return contextEnabled\n}\n\n/**\n * Get the current span context from AsyncLocalStorage.\n * Returns null if no span is active in the current async context,\n * or if context propagation is not enabled.\n */\nexport function getCurrentSpan(): SpanContext | null {\n if (!contextEnabled || !storage) return null\n return storage.getStore() ?? null\n}\n\n/**\n * Enter a span context for the remainder of the current synchronous execution\n * and any async operations started from it. Used by the logger when creating\n * spans with `using` — since `using` doesn't wrap user code in a callback,\n * `enterWith()` is the right primitive.\n *\n * Captures the full previous SpanContext snapshot so it can be restored\n * exactly on exit, even with non-LIFO end() ordering.\n *\n * @internal\n */\nexport function enterSpanContext(spanId: string
|
|
1
|
+
{"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * AsyncLocalStorage-based context propagation for loggily — Node.js/Bun only.\n *\n * Separated from core logger to allow tree-shaking in browser bundles.\n * When enabled, new spans automatically parent to the current context span,\n * and writeLog() auto-tags with trace_id/span_id from context.\n *\n * @example\n * ```typescript\n * import { enableContextPropagation, getCurrentSpan } from \"loggily/context\"\n *\n * enableContextPropagation()\n *\n * const log = createLogger(\"myapp\")\n * {\n * using span = log.span?.(\"request\")\n * // All logs and child spans within this async context\n * // automatically inherit trace_id and span_id\n * log.info?.(\"inside span\") // auto-tagged with trace_id, span_id\n *\n * const current = getCurrentSpan()\n * // current === { spanId: \"sp_1\", traceId: \"tr_1\", parentId: null }\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\"\nimport { _setContextHooks, _clearContextHooks } from \"./core.js\"\n\n// ============ Types ============\n\n/** Minimal span context stored in AsyncLocalStorage */\nexport interface SpanContext {\n readonly spanId: string\n readonly traceId: string\n readonly parentId: string | null\n}\n\n// ============ State ============\n\nlet storage: AsyncLocalStorage<SpanContext> | null = null\nlet contextEnabled = false\n\n/**\n * Map from spanId → the SpanContext that was active when the span was entered.\n * Used to restore the exact previous context on exit, avoiding corruption\n * from non-LIFO end() ordering.\n */\nconst previousContexts = new Map<string, SpanContext | null>()\n\n// ============ API ============\n\n/**\n * Enable AsyncLocalStorage-based context propagation.\n * Once enabled, new spans automatically parent to the current context span,\n * and log messages are auto-tagged with trace_id/span_id.\n *\n * **Node.js/Bun only** — not available in browser environments.\n */\nexport function enableContextPropagation(): void {\n if (!storage) {\n storage = new AsyncLocalStorage<SpanContext>()\n }\n contextEnabled = true\n\n // Register hooks with core.ts\n _setContextHooks({\n getContextTags,\n getContextParent() {\n const span = getCurrentSpan()\n if (!span) return null\n return { spanId: span.spanId, traceId: span.traceId }\n },\n enterContext: enterSpanContext,\n exitContext: exitSpanContext,\n })\n}\n\n/**\n * Disable context propagation.\n * Existing spans continue to work, but new spans won't auto-parent.\n */\nexport function disableContextPropagation(): void {\n contextEnabled = false\n _clearContextHooks()\n}\n\n/** Check if context propagation is enabled */\nexport function isContextPropagationEnabled(): boolean {\n return contextEnabled\n}\n\n/**\n * Get the current span context from AsyncLocalStorage.\n * Returns null if no span is active in the current async context,\n * or if context propagation is not enabled.\n */\nexport function getCurrentSpan(): SpanContext | null {\n if (!contextEnabled || !storage) return null\n return storage.getStore() ?? null\n}\n\n/**\n * Enter a span context for the remainder of the current synchronous execution\n * and any async operations started from it. Used by the logger when creating\n * spans with `using` — since `using` doesn't wrap user code in a callback,\n * `enterWith()` is the right primitive.\n *\n * Captures the full previous SpanContext snapshot so it can be restored\n * exactly on exit, even with non-LIFO end() ordering.\n *\n * @internal\n */\nexport function enterSpanContext(\n spanId: string,\n traceId: string,\n parentId: string | null,\n): void {\n if (!contextEnabled || !storage) return\n\n // Capture the full previous context before overwriting\n const previous = storage.getStore() ?? null\n previousContexts.set(spanId, previous)\n\n storage.enterWith({ spanId, traceId, parentId })\n}\n\n/**\n * Restore the previous span context (called when a span ends).\n * Restores the exact SpanContext snapshot captured at enter time,\n * preventing corruption from non-LIFO end() ordering.\n *\n * @internal\n */\nexport function exitSpanContext(spanId: string): void {\n if (!contextEnabled || !storage) return\n\n const previous = previousContexts.get(spanId)\n previousContexts.delete(spanId)\n\n if (previous) {\n storage.enterWith(previous)\n } else {\n // No previous context — exit entirely\n storage.enterWith(undefined as unknown as SpanContext)\n }\n}\n\n/**\n * Run a function within a span context.\n * Used for explicit context scoping (e.g., in request handlers).\n *\n * @param context - The span context to set\n * @param fn - The function to run within the context\n * @returns The return value of fn\n */\nexport function runInSpanContext<T>(context: SpanContext, fn: () => T): T {\n if (!contextEnabled || !storage) return fn()\n return storage.run(context, fn)\n}\n\n/**\n * Get the context tags (trace_id, span_id) for the current async context.\n * Used by writeLog() to auto-tag log messages.\n * Returns empty object if context propagation is disabled or no span is active.\n */\nexport function getContextTags(): Record<string, string> {\n const span = getCurrentSpan()\n if (!span) return {}\n return {\n trace_id: span.traceId,\n span_id: span.spanId,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAI,UAAiD;AACrD,IAAI,iBAAiB;;;;;;AAOrB,MAAM,mCAAmB,IAAI,KAAiC;;;;;;;;AAW9D,SAAgB,2BAAiC;AAC/C,KAAI,CAAC,QACH,WAAU,IAAI,mBAAgC;AAEhD,kBAAiB;AAGjB,kBAAiB;EACf;EACA,mBAAmB;GACjB,MAAM,OAAO,gBAAgB;AAC7B,OAAI,CAAC,KAAM,QAAO;AAClB,UAAO;IAAE,QAAQ,KAAK;IAAQ,SAAS,KAAK;IAAS;;EAEvD,cAAc;EACd,aAAa;EACd,CAAC;;;;;;AAOJ,SAAgB,4BAAkC;AAChD,kBAAiB;AACjB,qBAAoB;;;AAItB,SAAgB,8BAAuC;AACrD,QAAO;;;;;;;AAQT,SAAgB,iBAAqC;AACnD,KAAI,CAAC,kBAAkB,CAAC,QAAS,QAAO;AACxC,QAAO,QAAQ,UAAU,IAAI;;;;;;;;;;;;;AAc/B,SAAgB,iBACd,QACA,SACA,UACM;AACN,KAAI,CAAC,kBAAkB,CAAC,QAAS;CAGjC,MAAM,WAAW,QAAQ,UAAU,IAAI;AACvC,kBAAiB,IAAI,QAAQ,SAAS;AAEtC,SAAQ,UAAU;EAAE;EAAQ;EAAS;EAAU,CAAC;;;;;;;;;AAUlD,SAAgB,gBAAgB,QAAsB;AACpD,KAAI,CAAC,kBAAkB,CAAC,QAAS;CAEjC,MAAM,WAAW,iBAAiB,IAAI,OAAO;AAC7C,kBAAiB,OAAO,OAAO;AAE/B,KAAI,SACF,SAAQ,UAAU,SAAS;KAG3B,SAAQ,UAAU,KAAA,EAAoC;;;;;;;;;;AAY1D,SAAgB,iBAAoB,SAAsB,IAAgB;AACxE,KAAI,CAAC,kBAAkB,CAAC,QAAS,QAAO,IAAI;AAC5C,QAAO,QAAQ,IAAI,SAAS,GAAG;;;;;;;AAQjC,SAAgB,iBAAyC;CACvD,MAAM,OAAO,gBAAgB;AAC7B,KAAI,CAAC,KAAM,QAAO,EAAE;AACpB,QAAO;EACL,UAAU,KAAK;EACf,SAAS,KAAK;EACf"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as LogLevel, r as Event, s as LogFormat, t as ConfigElement } from "./pipeline-
|
|
1
|
+
import { c as LogLevel, n as ConfigObject, r as Event, s as LogFormat, t as ConfigElement } from "./pipeline-D0omS7eF.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/metrics.d.ts
|
|
4
4
|
interface SpanStats {
|
|
@@ -79,7 +79,35 @@ interface SpanLogger extends ConditionalLogger, Disposable {
|
|
|
79
79
|
readonly spanData: SpanData & {
|
|
80
80
|
[key: string]: unknown;
|
|
81
81
|
};
|
|
82
|
+
span(namespace?: string, props?: LazyProps): SpanLogger;
|
|
83
|
+
/**
|
|
84
|
+
* Record a stopwatch-style checkpoint within this span. Each `lap(name)`
|
|
85
|
+
* captures the time since the previous lap (or span start for the first
|
|
86
|
+
* lap). On span end, laps are emitted in the span event's props as
|
|
87
|
+
* `laps: { name1: deltaMs, name2: deltaMs, ... }` for compact output, plus
|
|
88
|
+
* stored verbatim on `spanData.laps` for programmatic consumers.
|
|
89
|
+
*
|
|
90
|
+
* Use laps when you want sub-span timing without the noise of nested span
|
|
91
|
+
* lines. Nested spans give per-phase log lines + parent context; laps give
|
|
92
|
+
* one summary line with phase deltas in props.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* using span = log.span?.("materialize")
|
|
96
|
+
* loadInputs()
|
|
97
|
+
* span?.lap("inputs")
|
|
98
|
+
* computeMatches()
|
|
99
|
+
* span?.lap("matches")
|
|
100
|
+
* writeOutputs()
|
|
101
|
+
* span?.lap("outputs")
|
|
102
|
+
* // Span end → SPAN ... laps: { inputs: 12, matches: 340, outputs: 8 }
|
|
103
|
+
*/
|
|
104
|
+
lap(name: string): void;
|
|
82
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* @deprecated `createLogger()` now returns `ConditionalLogger`; use
|
|
108
|
+
* `log.span?.("op")` because spans are only present when enabled.
|
|
109
|
+
*/
|
|
110
|
+
type SpannedLogger = ConditionalLogger;
|
|
83
111
|
interface ConditionalLogger extends Disposable {
|
|
84
112
|
readonly name: string;
|
|
85
113
|
readonly props: Readonly<Record<string, unknown>>;
|
|
@@ -149,6 +177,20 @@ interface PluginCtx {
|
|
|
149
177
|
}
|
|
150
178
|
type LoggerPlugin = (factory: LoggerFactory, ctx: PluginCtx) => LoggerFactory;
|
|
151
179
|
declare function pipe(base: LoggerFactory, ...plugins: LoggerPlugin[]): LoggerFactory;
|
|
180
|
+
/**
|
|
181
|
+
* Writer signature for {@link addWriter}.
|
|
182
|
+
*
|
|
183
|
+
* Receives the pre-formatted text (per the active LOG_FORMAT) plus the
|
|
184
|
+
* structural fields needed to route or filter writes downstream:
|
|
185
|
+
* - `level`: log level (or `"span"`) for level-aware sinks
|
|
186
|
+
* - `namespace`: emitting namespace; the `{ ns }` filter on
|
|
187
|
+
* {@link addWriter} routes by this value
|
|
188
|
+
* - `event`: full structured Event so JSONL sinks can re-serialize with
|
|
189
|
+
* custom fields (e.g., {@link createLogger}'s `props` end up here).
|
|
190
|
+
*
|
|
191
|
+
* Two-arg writers (legacy shape) keep working — JS ignores extra arguments.
|
|
192
|
+
*/
|
|
193
|
+
type WriterFn = (formatted: string, level: string, namespace: string, event: Event) => void;
|
|
152
194
|
/**
|
|
153
195
|
* Plugin: read defaults from environment variables (LOG_LEVEL, DEBUG, LOG_FORMAT, TRACE, LOG_FILE).
|
|
154
196
|
* Included by default. Omit to disable env-var behavior entirely.
|
|
@@ -165,7 +207,7 @@ declare function withEnvDefaults(): LoggerPlugin;
|
|
|
165
207
|
*/
|
|
166
208
|
declare function withConfigMetrics(): LoggerPlugin;
|
|
167
209
|
/** Default createLogger — includes withEnvDefaults + withSpans + withConfigMetrics. */
|
|
168
|
-
declare const createLogger:
|
|
210
|
+
declare const createLogger: (name: string, configOrProps?: ConfigElement[] | Record<string, unknown>) => ConditionalLogger;
|
|
169
211
|
/** Test helper — all levels, console output. */
|
|
170
212
|
declare function createTestLogger(name: string): ConditionalLogger;
|
|
171
213
|
/** @deprecated Use config array */
|
|
@@ -184,8 +226,35 @@ declare function setSuppressConsole(value: boolean): void;
|
|
|
184
226
|
type OutputMode = "console" | "stderr" | "writers-only";
|
|
185
227
|
declare function setOutputMode(_mode: OutputMode): void;
|
|
186
228
|
declare function getOutputMode(): OutputMode;
|
|
187
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Register a writer for log records. A writer is the terminal stage of a
|
|
231
|
+
* sub-pipeline — it receives formatted records that pass any filters
|
|
232
|
+
* declared by the optional config.
|
|
233
|
+
*
|
|
234
|
+
* Three forms:
|
|
235
|
+
*
|
|
236
|
+
* - **catch-all**: `addWriter(writer)` — every record reaches the writer.
|
|
237
|
+
* - **scoped**: `addWriter({ ns, level }, writer)` — only records matching
|
|
238
|
+
* the namespace pattern and at-or-above the level reach the writer.
|
|
239
|
+
* Same `ConfigObject` shape used in `createLogger("x", [config, sink])`.
|
|
240
|
+
*
|
|
241
|
+
* `ConfigObject` filters supported here:
|
|
242
|
+
*
|
|
243
|
+
* - `ns: string | string[]` — DEBUG-style glob (e.g. `"bg-recall:*"`,
|
|
244
|
+
* `"myapp,-myapp:noisy"`). Substring excludes count.
|
|
245
|
+
* - `level: LogLevel` — record's level must be >= this. `"trace"` < `"debug"`
|
|
246
|
+
* < `"info"` < `"warn"` < `"error"` < `"silent"`.
|
|
247
|
+
*
|
|
248
|
+
* Returns an unsubscribe handle.
|
|
249
|
+
*
|
|
250
|
+
* Per-namespace fan-out replaces hand-rolled `appendFileSync(path, line)`
|
|
251
|
+
* patterns: every subsystem writes through `createLogger("ns:thing")`, and
|
|
252
|
+
* the host registers per-namespace writers via this function instead of
|
|
253
|
+
* each subsystem reinventing its own env-var + writer.
|
|
254
|
+
*/
|
|
255
|
+
declare function addWriter(writer: WriterFn): () => void;
|
|
256
|
+
declare function addWriter(config: ConfigObject, writer: WriterFn): () => void;
|
|
188
257
|
declare function writeSpan(namespace: string, duration: number, attrs: Record<string, unknown>): void;
|
|
189
258
|
//#endregion
|
|
190
|
-
export {
|
|
191
|
-
//# sourceMappingURL=core-
|
|
259
|
+
export { getTraceFilter as A, startCollecting as B, disableSpans as C, getLogFormat as D, getDebugFilter as E, setLogLevel as F, writeSpan as G, withConfigMetrics as H, setOutputMode as I, createMetricsCollector as J, MetricsCollector as K, setSuppressConsole as L, resetIds as M, setDebugFilter as N, getLogLevel as O, setLogFormat as P, setTraceFilter as R, createTestLogger as S, getCollectedSpans as T, withEnvDefaults as U, stopCollecting as V, withSpans as W, withMetrics as Y, addWriter as _, LoggerFactory as a, createLogger as b, PluginCtx as c, SpanRecord as d, SpanRecorder as f, _setContextHooks as g, _clearContextHooks as h, Logger as i, pipe as j, getOutputMode as k, SpanData as l, WriterFn as m, LazyMessage as n, LoggerPlugin as o, SpannedLogger as p, SpanStats as q, LazyProps as r, OutputMode as s, ConditionalLogger as t, SpanLogger as u, baseCreateLogger as v, enableSpans as w, createSpanDataProxy as x, clearCollectedSpans as y, spansAreEnabled as z };
|
|
260
|
+
//# sourceMappingURL=core-DHB4KaG1.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-DHB4KaG1.d.mts","names":[],"sources":["../src/metrics.ts","../src/core.ts"],"mappings":"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"}
|