loggily 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ log.debug?.("cache hit", { key: "user:42" })
23
23
  log.error?.(new Error("connection lost"))
24
24
  ```
25
25
 
26
- Readable, colorized output in development:
26
+ Readable, colorized output in development (colors don't render on GitHub -- run it in a terminal):
27
27
 
28
28
  ```
29
29
  14:32:15 INFO myapp server started {port: 3000}
@@ -55,7 +55,7 @@ log.debug?.(`state: ${JSON.stringify(computeExpensiveState())}`)
55
55
  // nothing runs when debug is off — not the function, not the stringify, not the template
56
56
  ```
57
57
 
58
- In benchmarks with expensive disabled log arguments, this is [~22x faster](https://beorn.codes/loggily/guide/benchmarks) than a conventional noop logger.
58
+ In benchmarks with expensive disabled log arguments, this is [~22x faster](https://loggily.dev/guide/benchmarks) than a conventional noop logger.
59
59
 
60
60
  ## Install
61
61
 
@@ -75,60 +75,128 @@ Loggily uses `Symbol.dispose` (TC39 Explicit Resource Management) for span clean
75
75
 
76
76
  ## Features
77
77
 
78
- - **Config pipeline** -- second arg to `createLogger` is a config array: objects configure (`{ level, ns, format }`), arrays branch, values write. Pass `console` for terminal output, `{ file: "/path" }` for file output, or functions for custom stages.
79
- - **Namespace hierarchy** -- organize logs with `:` separators. `DEBUG=myapp:db` shows only database output, compatible with the same patterns as the `debug` package.
80
- - **Lightweight spans** -- time any operation with `using span = log.span("name")`. Automatic duration, parent-child tracking, and trace IDs.
81
- - **Dev & production** -- colorized console in development, structured JSON in production. Same code, zero config.
82
- - **Child context** -- `log.child({ requestId })` adds structured fields to every message in the chain.
83
- - **Automatic async context** -- enable `AsyncLocalStorage`-based propagation and every log in a request's async chain inherits trace/span IDs without passing loggers around.
84
- - **Lazy messages** -- `log.debug?.(() => expensiveString())` skips the function entirely when disabled.
85
- - **Error overloads** -- `log.error?.(err)`, `log.error?.(err, "msg")`, and `log.error?.(err, "msg", data)`.
86
- - **Worker threads** -- forward logs from workers to the main thread with full type safety.
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
87
96
 
88
- ### Config Array
97
+ ```typescript
98
+ import { createLogger } from "loggily"
99
+
100
+ const log = createLogger("myapp", [{ level: "debug" }, console])
101
+
102
+ log.info?.("server started", { port: 3000 })
103
+ log.debug?.("cache hit", { key: "user:42" })
104
+ log.error?.(new Error("timeout"), "request failed", { url: "/api" })
105
+
106
+ // Child loggers
107
+ const dbLog = log.child("db", { pool: "main" }) // namespace: "myapp:db"
108
+
109
+ // Spans -- time any operation
110
+ {
111
+ using span = dbLog.span("query", { table: "users" })
112
+ const users = await queryUsers() // your DB call
113
+ span.spanData.count = users.length
114
+ }
115
+ // → SPAN myapp:db:query (45ms) {count: 100, table: "users"}
116
+ ```
117
+
118
+ ## Complete Example
119
+
120
+ The config array accepts six element types -- here they are in one pipeline:
89
121
 
90
122
  ```typescript
91
123
  import { createLogger } from "loggily"
124
+ import { toOtel } from "loggily/otel"
125
+ import * as otelApi from "@opentelemetry/api"
92
126
 
93
- // Objects configure, arrays branch, values write
94
127
  const log = createLogger("myapp", [
95
- { level: "debug", ns: "-sql" },
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) }) },
96
140
  console,
97
- { file: "/tmp/app.log", level: "error", format: "json" },
98
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
+ }
99
154
  ```
100
155
 
101
- When no config array is provided, loggily reads `LOG_LEVEL`, `DEBUG`, `LOG_FORMAT`, and `NODE_ENV` from the environment.
156
+ ### Composition with plugins
102
157
 
103
- ### Spans
158
+ `createLogger` is `pipe(baseCreateLogger, withEnvDefaults(), withSpans(), withConfigMetrics())`. For full manual control:
104
159
 
105
160
  ```typescript
106
- {
107
- using span = log.span("db:query", { table: "users" })
108
- const users = await db.query("SELECT * FROM users")
109
- span.spanData.count = users.length
110
- }
111
- // SPAN myapp:db:query (45ms) {count: 100, table: "users"}
112
-
113
- // Without `using` — call .end() manually
114
- const span = log.span("db:query")
115
- try {
116
- /* ... */
117
- } finally {
118
- span.end()
119
- }
161
+ import { baseCreateLogger, pipe, withSpans, withEnvDefaults } from "loggily"
162
+
163
+ // Custom factory choose exactly which plugins to include
164
+ const myCreateLogger = pipe(baseCreateLogger, withSpans(), myPlugin())
165
+ const log = myCreateLogger("myapp", [console])
120
166
  ```
121
167
 
122
- ### Common configuration
168
+ `baseCreateLogger` does NOT include `withSpans()` or `withEnvDefaults()` — loggers it creates cannot create spans and do not read environment variables.
123
169
 
124
- | Variable | Example | Effect |
125
- | ------------ | ------------------------- | ------------------------------------------------------ |
126
- | `DEBUG` | `myapp:db,-myapp:sql` | Namespace filter (compatible with the `debug` package) |
127
- | `LOG_LEVEL` | `debug`, `info`, `warn` | Minimum output level |
128
- | `LOG_FORMAT` | `console`, `json` | Override output format |
129
- | `TRACE` | `1` or namespace prefixes | Enable span output |
170
+ ### Test helper
171
+
172
+ ```typescript
173
+ import { createTestLogger } from "loggily"
174
+ const log = createTestLogger("test") // all levels enabled, console output
175
+ ```
130
176
 
131
- See the [full environment variable reference](https://beorn.codes/loggily/api/configuration).
177
+ ### Environment variables
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 |
132
200
 
133
201
  ### Types
134
202
 
@@ -142,15 +210,37 @@ Key types exported for power users:
142
210
  | `Stage` | `(event: Event) => Event \| null \| void` |
143
211
  | `Pipeline` | `{ dispatch, level, dispose }` |
144
212
  | `ConditionalLogger` | Logger with `?.`-compatible methods |
145
-
146
- `buildPipeline()` and `defaultPipeline()` are exported for direct pipeline construction.
147
-
148
- ## Compatibility
149
-
150
- - **`DEBUG=` compatible** -- uses the same namespace filter patterns as the `debug` package
151
- - **Works with Pino transports** -- custom stage functions can forward events to any sink
152
- - **W3C Trace Context** -- `traceparent()` generates standard trace headers
153
- - **OpenTelemetry compatible** -- span events include `spanId`, `traceId`, `parentId`
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.
154
244
 
155
245
  ## Why this exists
156
246
 
@@ -160,15 +250,16 @@ Loggily was built while developing a terminal UI where disabled debug logs insid
160
250
 
161
251
  ## When not to use Loggily
162
252
 
163
- - **You need worker-thread transport pipelines with log rotation and dozens of plugins.** [Pino](https://getpino.io/) has a mature transport ecosystem for this.
164
- - **You need distributed tracing with vendor exporters and auto-instrumentation.** [OpenTelemetry](https://opentelemetry.io/) is the industry standard.
253
+ - **You need auto-instrumentation** (HTTP, database, gRPC). Use OpenTelemetry's SDK directly -- Loggily's OTEL bridge forwards events but doesn't instrument frameworks.
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.
165
256
 
166
257
  ## Documentation
167
258
 
168
- - **[Get Started](https://beorn.codes/loggily/guide/journey)** -- progressive guide from first log to full observability
169
- - **[Full docs site](https://beorn.codes/loggily/)** -- guides, API reference, migration guides
170
- - [Comparison](https://beorn.codes/loggily/guide/comparison) -- what Loggily does, compatibility, when to use something else
171
- - [Migration from debug](https://beorn.codes/loggily/guide/migration-from-debug) -- step-by-step migration guide
259
+ - **[Get Started](https://loggily.dev/guide/journey)** -- progressive guide from first log to full observability
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
172
263
 
173
264
  ## License
174
265
 
package/dist/context.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as _setContextHooks, n as _clearContextHooks } from "./core-Du3sIje6.mjs";
1
+ import { n as _setContextHooks, t as _clearContextHooks } from "./core-B3pox577.mjs";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  //#region src/context.ts
4
4
  /**