loggily 0.6.1 → 0.6.2

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.
Files changed (2) hide show
  1. package/README.md +70 -90
  2. package/package.json +4 -3
package/README.md CHANGED
@@ -2,42 +2,28 @@
2
2
 
3
3
  **Clarity without the clutter.**
4
4
 
5
- One library. One namespace tree. One output pipeline. For logs (structured JSON or console), debug(), and tracing spans. Near-zero overhead from disabled log levels. Pure TypeScript. ~3KB. Zero dependencies.
5
+ Debugs, logs, and spans one API.
6
6
 
7
7
  [![Tests](https://github.com/beorn/loggily/actions/workflows/test.yml/badge.svg)](https://github.com/beorn/loggily/actions/workflows/test.yml)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.2+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![npm version](https://img.shields.io/npm/v/loggily.svg)](https://www.npmjs.com/package/loggily)
9
+ [![size](https://img.shields.io/bundlephobia/minzip/loggily)](https://bundlephobia.com/package/loggily)
9
10
  [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
10
11
 
11
- > Early release (0.x) -- API may evolve before 1.0.
12
+ Most apps end up with three logging tools: `debug` for local troubleshooting, a JSON logger like Pino for production, and ad-hoc timers or a tracing SDK for performance. Three APIs, three configs, three output formats.
12
13
 
13
- ## Install
14
-
15
- ```bash
16
- npm install loggily
17
- ```
18
-
19
- | Requirement | Version |
20
- | ------------- | ------------------------------------------------- |
21
- | Node.js | >= 23.6 |
22
- | Bun | 1.0+ |
23
- | TypeScript | 5.2+ (for `using`; `.end()` works on any version) |
24
- | Module format | ESM-only |
25
- | Browser | Supported via conditional export |
26
-
27
- ## Quick Start
14
+ Loggily replaces all three with one namespace tree and one output pipeline. Pure TypeScript, zero dependencies, ~3 KB.
28
15
 
29
16
  ```typescript
30
17
  import { createLogger } from "loggily"
31
18
 
32
19
  const log = createLogger("myapp")
33
20
 
34
- // ?. skips the entire call — including argument evaluation — when the level is disabled
35
21
  log.info?.("server started", { port: 3000 })
36
22
  log.debug?.("cache hit", { key: "user:42" })
37
23
  log.error?.(new Error("connection lost"))
38
24
  ```
39
25
 
40
- Output in development (colorized with timestamps and clickable source lines):
26
+ Readable, colorized output in development:
41
27
 
42
28
  ```
43
29
  14:32:15 INFO myapp server started {port: 3000}
@@ -45,75 +31,95 @@ Output in development (colorized with timestamps and clickable source lines):
45
31
  14:32:15 ERROR myapp connection lost
46
32
  ```
47
33
 
48
- Set `NODE_ENV=production` or `LOG_FORMAT=json` and the same code emits structured JSON:
34
+ Set `NODE_ENV=production` and the same calls emit structured JSON:
49
35
 
50
36
  ```json
51
37
  { "time": "2024-01-15T14:32:15.123Z", "level": "info", "name": "myapp", "msg": "server started", "port": 3000 }
52
38
  ```
53
39
 
54
- ### Spans
40
+ ## Why the `?.`
55
41
 
56
- Time operations with lightweight spans. Uses TC39 [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) (`using` requires TypeScript 5.2+ and runtime support). For other environments, call `.end()` manually:
42
+ Disabled logs should not build strings, serialize objects, or compute snapshots just to throw them away.
57
43
 
58
- ```typescript
59
- // With `using` (TS 5.2+, Bun 1.0+, Node 22+)
60
- {
61
- using span = log.span("db:query", { table: "users" })
62
- const users = await db.query("SELECT * FROM users")
63
- span.spanData.count = users.length
64
- }
65
- // Output: SPAN myapp:db:query (45ms) {count: 100, table: "users"}
44
+ With most loggers, this work still happens:
66
45
 
67
- // Without `using` — works on any runtime
68
- const span = log.span("db:query", { table: "users" })
69
- try {
70
- const users = await db.query("SELECT * FROM users")
71
- span.spanData.count = users.length
72
- } finally {
73
- span.end()
74
- }
46
+ ```typescript
47
+ log.debug(`state: ${JSON.stringify(computeExpensiveState())}`)
48
+ // computeExpensiveState() runs even when debug is off
75
49
  ```
76
50
 
77
- ## Why Loggily?
51
+ With Loggily, optional chaining short-circuits the entire call:
78
52
 
79
- One API for debug-style namespace logging, structured JSON output, and lightweight spans. Many projects end up with separate tools for these -- **debug** for conditional output, **pino/winston** for production logs, a tracing SDK for timings -- with separate configs, formats, and APIs. Loggily integrates all three into one namespace tree, one output pipeline, one `?.` pattern.
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
+ ```
80
57
 
81
- ### Near-zero cost for disabled logs
58
+ In benchmarks with expensive disabled log arguments, this is [~22x faster](https://beorn.codes/loggily/guide/benchmarks) than a conventional noop logger.
82
59
 
83
- Most loggers waste work when logging is disabled. Even with a noop function, arguments are still evaluated:
60
+ ## Install
84
61
 
85
- ```typescript
86
- // Traditional — args are ALWAYS evaluated, even when debug is off
87
- log.debug(`state: ${JSON.stringify(computeExpensiveState())}`)
62
+ ```bash
63
+ npm install loggily
88
64
  ```
89
65
 
90
- Loggily uses optional chaining to skip the entire call — including argument evaluation:
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
+ - **Namespace hierarchy** — organize logs with `:` separators. `DEBUG=myapp:db` shows only database output, just like the `debug` package.
79
+ - **Lightweight spans** — time any operation with `using span = log.span("name")`. Automatic duration, parent-child tracking, and trace IDs.
80
+ - **Dev & production** — colorized console in development, structured JSON in production. Same code, zero config.
81
+ - **Child context** — `log.child({ requestId })` adds structured fields to every message in the chain.
82
+ - **Automatic async context** — enable `AsyncLocalStorage`-based propagation and every log in a request's async chain inherits trace/span IDs without passing loggers around.
83
+ - **Lazy messages** — `log.debug?.(() => expensiveString())` skips the function entirely when disabled.
84
+ - **File writer** — `addWriter()` + `createFileWriter()` for buffered file output.
85
+ - **Worker threads** — forward logs from workers to the main thread with full type safety.
86
+
87
+ ### Spans
91
88
 
92
89
  ```typescript
93
- // Loggily — args are NOT evaluated when disabled
94
- log.debug?.(`state: ${JSON.stringify(computeExpensiveState())}`)
90
+ {
91
+ using span = log.span("db:query", { table: "users" })
92
+ const users = await db.query("SELECT * FROM users")
93
+ span.spanData.count = users.length
94
+ }
95
+ // SPAN myapp:db:query (45ms) {count: 100, table: "users"}
96
+
97
+ // Without `using` — call .end() manually
98
+ const span = log.span("db:query")
99
+ try { /* ... */ } finally { span.end() }
95
100
  ```
96
101
 
97
- For trivial arguments the difference is negligible. But for real-world logging — string interpolation, JSON serialization, state snapshots — optional chaining is typically **10x+ faster** because it skips the work entirely. The more expensive your arguments, the bigger the win.
102
+ ### Common configuration
98
103
 
99
- > **Note**: The big performance advantage is specifically for disabled logging with expensive arguments, not universal logger throughput. Pino is optimized for high-throughput enabled JSON logging; Loggily's biggest advantage is skipping work when logs are disabled. See [benchmarks](https://beorn.codes/loggily/guide/benchmarks) for detailed numbers per scenario.
104
+ | Variable | Example | Effect |
105
+ |---|---|---|
106
+ | `DEBUG` | `myapp:db,-myapp:sql` | Namespace filter (same syntax as the `debug` package) |
107
+ | `LOG_LEVEL` | `debug`, `info`, `warn` | Minimum output level |
108
+ | `LOG_FORMAT` | `console`, `json` | Override output format |
109
+ | `TRACE` | `1` or namespace prefixes | Enable span output |
100
110
 
101
- ## Features
111
+ See the [full environment variable reference](https://beorn.codes/loggily/api/configuration).
102
112
 
103
- - **Namespace hierarchy** — organize logs with `:` separators. `log.logger("db")` creates `myapp:db`. Children inherit parent context.
104
- - **Lightweight spans and trace IDs** — time any operation with `using span = log.span("name")`. Automatic duration, parent-child tracking, and trace IDs. For full OpenTelemetry interoperability with exporters and propagation, use OpenTelemetry.
105
- - **Lazy messages** — `log.debug?.(() => expensiveString())` skips the function entirely when disabled.
106
- - **Child context** — `log.child({ requestId })` adds structured fields to every message in the chain.
107
- - **Dev & production** — colorized console with timestamps, level colors, and clickable source lines in development. Structured JSON in production. Switches automatically via `NODE_ENV` — same code, zero config.
108
- - **File writer** — `addWriter()` + `createFileWriter()` for buffered file output with auto-flush.
109
- - **Worker threads** — forward logs from workers to the main thread with full type safety (`loggily/worker`).
110
- - **debug-compatible namespace filtering** — reads `DEBUG=myapp:*` just like the debug package. Easy migration from debug — see the [migration guide](https://beorn.codes/loggily/guide/migration-from-debug).
113
+ ## Why this exists
111
114
 
112
- ## When Not to Use Loggily
115
+ 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.
113
116
 
114
- - **Max-throughput transport pipelines** — use [Pino](https://getpino.io/) for worker-thread transports, custom serializers, and log rotation.
115
- - **Vendor/exporter interop** — use [OpenTelemetry](https://opentelemetry.io/) for distributed tracing with propagation, semantic conventions, and backend integrations.
116
- - **Tiny dev-only namespace logs** — use [debug](https://github.com/debug-js/debug) if all you need is conditional dev output with zero ceremony.
117
+ > **Status:** Early release (0.x). The core API is stable, but details may evolve before 1.0.
118
+
119
+ ## When not to use Loggily
120
+
121
+ - **You need the absolute fastest structured logger with a transport ecosystem.** Use [Pino](https://getpino.io/) for worker-thread transports, custom serializers, and log rotation.
122
+ - **You need distributed tracing with vendor exporters and auto-instrumentation.** Use [OpenTelemetry](https://opentelemetry.io/).
117
123
 
118
124
  ## Documentation
119
125
 
@@ -122,32 +128,6 @@ For trivial arguments the difference is negligible. But for real-world logging
122
128
  - [Comparison](https://beorn.codes/loggily/guide/comparison) — vs Pino, Winston, Bunyan, debug
123
129
  - [Migration from debug](https://beorn.codes/loggily/guide/migration-from-debug) — step-by-step migration guide
124
130
 
125
- ## Environment Variables
126
-
127
- | Variable | Values | Effect |
128
- | -------------- | --------------------------------------- | --------------------------------------- |
129
- | `LOG_LEVEL` | trace, debug, info, warn, error, silent | Minimum output level |
130
- | `LOG_FORMAT` | console, json | Output format |
131
- | `DEBUG` | `*`, namespace prefixes, `-prefix` | Namespace filter (like `debug` package) |
132
- | `TRACE` | `1`, `true`, or namespace prefixes | Enable span output |
133
- | `TRACE_FORMAT` | json | Force JSON for spans |
134
- | `NODE_ENV` | production | Auto-enable JSON format |
135
-
136
- ## API
137
-
138
- | Function | Description |
139
- | ---------------------------------------------------------------------- | ------------------------------------------------------------- |
140
- | `createLogger(name, props?)` | Create a logger (disabled levels return `undefined` for `?.`) |
141
- | `.trace?.()` / `.debug?.()` / `.info?.()` / `.warn?.()` / `.error?.()` | Log at level (message + optional data) |
142
- | `.logger(namespace)` | Create child logger with extended namespace |
143
- | `.span(namespace, props?)` | Create timed span (implements `Disposable`) |
144
- | `.child(context)` | Create child with structured context fields |
145
- | `addWriter(fn)` / `createFileWriter(path)` | Custom output writers |
146
- | `setLogLevel()` / `setLogFormat()` / `enableSpans()` | Runtime configuration |
147
- | `createWorkerLogger()` / `createWorkerLogHandler()` | Worker thread support (`loggily/worker`) |
148
-
149
- See the [full API reference](https://beorn.codes/loggily/api/) for all functions and options.
150
-
151
131
  ## License
152
132
 
153
133
  [MIT](LICENSE)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loggily",
3
- "version": "0.6.1",
4
- "description": "TypeScript logger with debug-style namespaces, structured JSON output, and lightweight spans. Disabled logs skip argument evaluation via optional chaining.",
3
+ "version": "0.6.2",
4
+ "description": "Debugs, logs, and spans one API. Disabled logs skip argument evaluation entirely via optional chaining. ~3KB, zero dependencies.",
5
5
  "keywords": [
6
6
  "browser",
7
7
  "bun",
@@ -16,7 +16,8 @@
16
16
  "spans",
17
17
  "structured-logging",
18
18
  "tracing",
19
- "typescript"
19
+ "typescript",
20
+ "zero-dependency"
20
21
  ],
21
22
  "homepage": "https://beorn.codes/loggily/",
22
23
  "bugs": {