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 +145 -54
- package/dist/context.mjs +1 -1
- package/dist/{core-Du3sIje6.mjs → core-B3pox577.mjs} +521 -347
- package/dist/core-B3pox577.mjs.map +1 -0
- package/dist/core-Dm2PQUoS.d.mts +191 -0
- package/dist/core-Dm2PQUoS.d.mts.map +1 -0
- package/dist/{index-Co4jC3mx.d.mts → file-writer-DtaY8Njt.d.mts} +60 -49
- package/dist/file-writer-DtaY8Njt.d.mts.map +1 -0
- package/dist/index.browser.d.mts +10 -0
- package/dist/index.browser.d.mts.map +1 -0
- package/dist/index.browser.mjs +10 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +7 -2
- package/dist/index.mjs.map +1 -0
- package/dist/metrics.d.mts +2 -48
- package/dist/metrics.mjs +13 -43
- package/dist/metrics.mjs.map +1 -1
- package/dist/otel.d.mts +63 -0
- package/dist/otel.d.mts.map +1 -0
- package/dist/otel.mjs +82 -0
- package/dist/otel.mjs.map +1 -0
- package/dist/pipeline-Cl9-wCmt.d.mts +73 -0
- package/dist/pipeline-Cl9-wCmt.d.mts.map +1 -0
- package/dist/worker.d.mts +64 -94
- package/dist/worker.d.mts.map +1 -1
- package/dist/worker.mjs +107 -281
- package/dist/worker.mjs.map +1 -1
- package/package.json +22 -3
- package/dist/core-DAFH-huv.d.mts +0 -199
- package/dist/core-DAFH-huv.d.mts.map +0 -1
- package/dist/core-Du3sIje6.mjs.map +0 -1
- package/dist/index-Co4jC3mx.d.mts.map +0 -1
- package/dist/metrics.d.mts.map +0 -1
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://
|
|
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
|
-
- **
|
|
79
|
-
- **
|
|
80
|
-
- **
|
|
81
|
-
- **
|
|
82
|
-
- **
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
- **Error
|
|
86
|
-
- **Worker threads** --
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
+
### Composition with plugins
|
|
102
157
|
|
|
103
|
-
|
|
158
|
+
`createLogger` is `pipe(baseCreateLogger, withEnvDefaults(), withSpans(), withConfigMetrics())`. For full manual control:
|
|
104
159
|
|
|
105
160
|
```typescript
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
168
|
+
`baseCreateLogger` does NOT include `withSpans()` or `withEnvDefaults()` — loggers it creates cannot create spans and do not read environment variables.
|
|
123
169
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
164
|
-
- **You need
|
|
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://
|
|
169
|
-
- **[Full docs site](https://
|
|
170
|
-
- [Comparison](https://
|
|
171
|
-
- [Migration from debug](https://
|
|
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