loggily 0.0.1 → 0.3.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/.github/workflows/docs.yml +58 -0
- package/.github/workflows/release.yml +31 -0
- package/.github/workflows/test.yml +20 -0
- package/CHANGELOG.md +45 -0
- package/CLAUDE.md +299 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +21 -0
- package/README.md +102 -3
- package/benchmarks/overhead.ts +267 -0
- package/bun.lock +479 -0
- package/docs/api-reference.md +400 -0
- package/docs/benchmarks.md +106 -0
- package/docs/comparison.md +315 -0
- package/docs/conditional-logging-research.md +159 -0
- package/docs/guide.md +205 -0
- package/docs/migration-from-debug.md +310 -0
- package/docs/migration-from-pino.md +178 -0
- package/docs/migration-from-winston.md +179 -0
- package/docs/site/.vitepress/config.ts +67 -0
- package/docs/site/api/configuration.md +94 -0
- package/docs/site/api/index.md +61 -0
- package/docs/site/api/logger.md +99 -0
- package/docs/site/api/worker.md +120 -0
- package/docs/site/api/writers.md +69 -0
- package/docs/site/guide/getting-started.md +143 -0
- package/docs/site/guide/journey.md +203 -0
- package/docs/site/guide/migration-from-debug.md +24 -0
- package/docs/site/guide/spans.md +139 -0
- package/docs/site/guide/why.md +55 -0
- package/docs/site/guide/workers.md +113 -0
- package/docs/site/guide/zero-overhead.md +87 -0
- package/docs/site/index.md +54 -0
- package/package.json +56 -8
- package/src/colors.ts +27 -0
- package/src/context.ts +155 -0
- package/src/core.ts +804 -0
- package/src/file-writer.ts +104 -0
- package/src/index.browser.ts +64 -0
- package/src/index.ts +10 -1
- package/src/tracing.ts +142 -0
- package/src/worker.ts +687 -0
- package/tests/features.test.ts +552 -0
- package/tests/logger.test.ts +944 -0
- package/tests/tracing.test.ts +618 -0
- package/tests/universal.test.ts +107 -0
- package/tests/worker.test.ts +590 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineConfig } from "vitepress"
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
title: "loggily",
|
|
5
|
+
description: "Clarity without the clutter. Ergonomic unified logs, spans, and debugs for modern TypeScript.",
|
|
6
|
+
base: "/loggily/",
|
|
7
|
+
|
|
8
|
+
themeConfig: {
|
|
9
|
+
siteTitle: "loggily",
|
|
10
|
+
|
|
11
|
+
nav: [
|
|
12
|
+
{ text: "Guide", link: "/guide/journey" },
|
|
13
|
+
{ text: "API", link: "/api/" },
|
|
14
|
+
{ text: "GitHub", link: "https://github.com/beorn/loggily" },
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
sidebar: {
|
|
18
|
+
"/guide/": [
|
|
19
|
+
{
|
|
20
|
+
text: "Introduction",
|
|
21
|
+
items: [
|
|
22
|
+
{ text: "The Journey", link: "/guide/journey" },
|
|
23
|
+
{ text: "Getting Started", link: "/guide/getting-started" },
|
|
24
|
+
{ text: "Why loggily?", link: "/guide/why" },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
text: "Features",
|
|
29
|
+
items: [
|
|
30
|
+
{ text: "Zero-Overhead Logging", link: "/guide/zero-overhead" },
|
|
31
|
+
{ text: "Spans", link: "/guide/spans" },
|
|
32
|
+
{ text: "Worker Threads", link: "/guide/workers" },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
text: "Migration",
|
|
37
|
+
items: [{ text: "From debug", link: "/guide/migration-from-debug" }],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
"/api/": [
|
|
41
|
+
{
|
|
42
|
+
text: "API Reference",
|
|
43
|
+
items: [
|
|
44
|
+
{ text: "Overview", link: "/api/" },
|
|
45
|
+
{ text: "Logger", link: "/api/logger" },
|
|
46
|
+
{ text: "Configuration", link: "/api/configuration" },
|
|
47
|
+
{ text: "Writers", link: "/api/writers" },
|
|
48
|
+
{ text: "Worker Thread", link: "/api/worker" },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
socialLinks: [{ icon: "github", link: "https://github.com/beorn/loggily" }],
|
|
55
|
+
|
|
56
|
+
outline: { level: [2, 3] },
|
|
57
|
+
|
|
58
|
+
search: {
|
|
59
|
+
provider: "local",
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
footer: {
|
|
63
|
+
message: "Released under the MIT License.",
|
|
64
|
+
copyright: "Copyright © 2026 Bjørn Stabell",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## Log Level
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
setLogLevel(level: LogLevel): void
|
|
7
|
+
getLogLevel(): LogLevel
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Default: `"info"`. Override with `LOG_LEVEL` env var.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
setLogLevel("debug") // debug, info, warn, error
|
|
14
|
+
setLogLevel("error") // error only
|
|
15
|
+
setLogLevel("silent") // nothing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Log Format
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
setLogFormat(format: LogFormat): void
|
|
22
|
+
getLogFormat(): LogFormat
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Default: `"console"`. Override with `LOG_FORMAT` env var. Also auto-enabled by `NODE_ENV=production`.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
setLogFormat("json") // {"time":"...","level":"info","name":"myapp","msg":"..."}
|
|
29
|
+
setLogFormat("console") // 14:32:15 INFO myapp message
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Span Control
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
enableSpans(): void
|
|
36
|
+
disableSpans(): void
|
|
37
|
+
spansAreEnabled(): boolean
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Trace Filter
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
setTraceFilter(namespaces: string[] | null): void
|
|
44
|
+
getTraceFilter(): string[] | null
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Only emit spans matching these namespace prefixes.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
setTraceFilter(["myapp:db"]) // Only db spans
|
|
51
|
+
setTraceFilter(null) // All spans
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Debug Filter
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
setDebugFilter(namespaces: string[] | null): void
|
|
58
|
+
getDebugFilter(): string[] | null
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Filter log output by namespace. Supports negative patterns.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
setDebugFilter(["myapp"]) // Only myapp and children
|
|
65
|
+
setDebugFilter(["myapp", "-myapp:sql"]) // Exclude sql
|
|
66
|
+
setDebugFilter(null) // All namespaces
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Auto-lowers log level to `debug` when set.
|
|
70
|
+
|
|
71
|
+
## Output Mode
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
setOutputMode(mode: OutputMode): void
|
|
75
|
+
getOutputMode(): OutputMode
|
|
76
|
+
setSuppressConsole(value: boolean): void
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Mode | Console | Writers |
|
|
80
|
+
| ---------------- | ------- | ------- |
|
|
81
|
+
| `"console"` | Yes | Yes |
|
|
82
|
+
| `"stderr"` | stderr | Yes |
|
|
83
|
+
| `"writers-only"` | No | Yes |
|
|
84
|
+
|
|
85
|
+
## Environment Variables
|
|
86
|
+
|
|
87
|
+
| Variable | Values | Default |
|
|
88
|
+
| -------------- | --------------------------------------- | --------- |
|
|
89
|
+
| `LOG_LEVEL` | trace, debug, info, warn, error, silent | `info` |
|
|
90
|
+
| `LOG_FORMAT` | console, json | `console` |
|
|
91
|
+
| `DEBUG` | `*`, namespace prefixes, `-prefix` | (none) |
|
|
92
|
+
| `TRACE` | `1`, `true`, namespace prefixes | (none) |
|
|
93
|
+
| `TRACE_FORMAT` | json | (none) |
|
|
94
|
+
| `NODE_ENV` | production | (none) |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
See the [full API reference](https://github.com/beorn/loggily/blob/main/docs/api-reference.md) for complete documentation.
|
|
4
|
+
|
|
5
|
+
## Exports from `loggily`
|
|
6
|
+
|
|
7
|
+
### Core
|
|
8
|
+
|
|
9
|
+
| Export | Description |
|
|
10
|
+
| -------------------------------------------------------- | ---------------------------------------- |
|
|
11
|
+
| `createLogger(name, props?)` | Create a conditional logger |
|
|
12
|
+
| `setLogLevel(level)` / `getLogLevel()` | Log level control |
|
|
13
|
+
| `setLogFormat(format)` / `getLogFormat()` | Output format (`"console"` or `"json"`) |
|
|
14
|
+
| `enableSpans()` / `disableSpans()` / `spansAreEnabled()` | Span output control |
|
|
15
|
+
| `setTraceFilter(ns)` / `getTraceFilter()` | Namespace-based span filtering |
|
|
16
|
+
| `setDebugFilter(ns)` / `getDebugFilter()` | Namespace-based log filtering |
|
|
17
|
+
| `setOutputMode(mode)` / `getOutputMode()` | Output destination |
|
|
18
|
+
| `setSuppressConsole(bool)` | Suppress console (writers still receive) |
|
|
19
|
+
|
|
20
|
+
### Writers
|
|
21
|
+
|
|
22
|
+
| Export | Description |
|
|
23
|
+
| ------------------------------- | --------------------------------- |
|
|
24
|
+
| `addWriter(fn)` | Subscribe to all formatted output |
|
|
25
|
+
| `createFileWriter(path, opts?)` | Buffered file writer |
|
|
26
|
+
|
|
27
|
+
### Testing
|
|
28
|
+
|
|
29
|
+
| Export | Description |
|
|
30
|
+
| ----------------------------------------------- | ------------------------------ |
|
|
31
|
+
| `startCollecting()` / `stopCollecting()` | Collect span data for analysis |
|
|
32
|
+
| `getCollectedSpans()` / `clearCollectedSpans()` | Access collected spans |
|
|
33
|
+
| `resetIds()` | Reset span/trace ID counters |
|
|
34
|
+
|
|
35
|
+
### Types
|
|
36
|
+
|
|
37
|
+
| Export | Description |
|
|
38
|
+
| ------------------- | ----------------------------------------- |
|
|
39
|
+
| `Logger` | Full logger interface |
|
|
40
|
+
| `SpanLogger` | Logger + Disposable + SpanData |
|
|
41
|
+
| `ConditionalLogger` | Logger with optional methods |
|
|
42
|
+
| `SpanData` | Span timing and attributes |
|
|
43
|
+
| `LogLevel` | `"trace" \| "debug" \| ... \| "silent"` |
|
|
44
|
+
| `LogFormat` | `"console" \| "json"` |
|
|
45
|
+
| `LazyMessage` | `string \| (() => string)` |
|
|
46
|
+
| `OutputMode` | `"console" \| "stderr" \| "writers-only"` |
|
|
47
|
+
| `FileWriter` | `{ write, flush, close }` |
|
|
48
|
+
|
|
49
|
+
## Exports from `loggily/worker`
|
|
50
|
+
|
|
51
|
+
| Export | Description |
|
|
52
|
+
| --------------------------------------------- | --------------------------------- |
|
|
53
|
+
| `createWorkerLogger(postMessage, ns, props?)` | Logger for worker threads |
|
|
54
|
+
| `createWorkerLogHandler(opts?)` | Main thread handler |
|
|
55
|
+
| `createWorkerConsoleHandler(opts?)` | Console message handler |
|
|
56
|
+
| `forwardConsole(postMessage, ns?)` | Forward console.\* from worker |
|
|
57
|
+
| `restoreConsole()` | Restore original console methods |
|
|
58
|
+
| `isWorkerMessage(msg)` | Type guard for any worker message |
|
|
59
|
+
| `isWorkerConsoleMessage(msg)` | Type guard for console messages |
|
|
60
|
+
| `isWorkerLogMessage(msg)` | Type guard for log messages |
|
|
61
|
+
| `isWorkerSpanMessage(msg)` | Type guard for span messages |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Logger
|
|
2
|
+
|
|
3
|
+
## createLogger
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
function createLogger(name: string, props?: Record<string, unknown>): ConditionalLogger
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Create a conditional logger. Disabled log levels return `undefined` -- use `?.` for zero-overhead.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const log = createLogger("myapp", { version: "2.1" })
|
|
13
|
+
log.info?.("started")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Logger Methods
|
|
17
|
+
|
|
18
|
+
### Logging
|
|
19
|
+
|
|
20
|
+
All accept `LazyMessage` (string or `() => string`) and optional data:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
log.trace?.("verbose", { detail: "..." })
|
|
24
|
+
log.debug?.("debugging", { state: "..." })
|
|
25
|
+
log.info?.("normal operation")
|
|
26
|
+
log.warn?.("recoverable issue")
|
|
27
|
+
log.error?.(new Error("failed"))
|
|
28
|
+
log.error?.("manual error", { code: "ETIMEOUT" })
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Child Creation
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Extend namespace, inherit props
|
|
35
|
+
const db = log.logger("db", { pool: "primary" })
|
|
36
|
+
// namespace: "myapp:db", props: { version: "2.1", pool: "primary" }
|
|
37
|
+
|
|
38
|
+
// Add context to every message (same namespace)
|
|
39
|
+
const req = log.child({ requestId: "abc" })
|
|
40
|
+
|
|
41
|
+
// Create timed span
|
|
42
|
+
{
|
|
43
|
+
using span = log.span("import")
|
|
44
|
+
span.spanData.count = 42
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Manual Span End
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const span = log.span("op")
|
|
52
|
+
try {
|
|
53
|
+
/* ... */
|
|
54
|
+
} finally {
|
|
55
|
+
span.end()
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## ConditionalLogger
|
|
60
|
+
|
|
61
|
+
The return type of `createLogger()`. Log methods are possibly `undefined`:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
interface ConditionalLogger {
|
|
65
|
+
readonly name: string
|
|
66
|
+
readonly props: Readonly<Record<string, unknown>>
|
|
67
|
+
trace?: (msg: LazyMessage, data?: Record<string, unknown>) => void
|
|
68
|
+
debug?: (msg: LazyMessage, data?: Record<string, unknown>) => void
|
|
69
|
+
info?: (msg: LazyMessage, data?: Record<string, unknown>) => void
|
|
70
|
+
warn?: (msg: LazyMessage, data?: Record<string, unknown>) => void
|
|
71
|
+
error?: (msg: LazyMessage | Error, data?: Record<string, unknown>) => void
|
|
72
|
+
logger(ns?: string, props?: Record<string, unknown>): Logger
|
|
73
|
+
span(ns?: string, props?: Record<string, unknown>): SpanLogger
|
|
74
|
+
child(context: Record<string, unknown>): Logger
|
|
75
|
+
end(): void
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## SpanLogger
|
|
80
|
+
|
|
81
|
+
Logger + timing + Disposable:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface SpanLogger extends Logger, Disposable {
|
|
85
|
+
readonly spanData: SpanData & { [key: string]: unknown }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### SpanData
|
|
90
|
+
|
|
91
|
+
| Property | Type | Writable | Description |
|
|
92
|
+
| ----------- | ---------------- | -------- | --------------------------- |
|
|
93
|
+
| `id` | `string` | No | `sp_1`, `sp_2`, ... |
|
|
94
|
+
| `traceId` | `string` | No | Shared across nested spans |
|
|
95
|
+
| `parentId` | `string \| null` | No | Parent span ID |
|
|
96
|
+
| `startTime` | `number` | No | Start timestamp |
|
|
97
|
+
| `endTime` | `number \| null` | No | End timestamp |
|
|
98
|
+
| `duration` | `number` | No | Live computed duration |
|
|
99
|
+
| `[custom]` | `unknown` | **Yes** | `span.spanData.key = value` |
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Worker Thread API
|
|
2
|
+
|
|
3
|
+
Import from `loggily/worker`.
|
|
4
|
+
|
|
5
|
+
## Worker Side
|
|
6
|
+
|
|
7
|
+
### createWorkerLogger
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
function createWorkerLogger(
|
|
11
|
+
postMessage: (msg: WorkerMessage) => void,
|
|
12
|
+
namespace: string,
|
|
13
|
+
props?: Record<string, unknown>,
|
|
14
|
+
options?: { parentSpanId?: string; traceId?: string },
|
|
15
|
+
): Logger
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create a logger that forwards all output to the main thread via `postMessage`.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const log = createWorkerLogger(postMessage, "myapp:worker")
|
|
22
|
+
log.info?.("processing", { file: "data.csv" })
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
using span = log.span("parse")
|
|
26
|
+
span.spanData.lines = 100
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### forwardConsole
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
function forwardConsole(postMessage: (msg: WorkerConsoleMessage) => void, namespace?: string): void
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Monkey-patch `console.*` to forward output via `postMessage`.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
forwardConsole(postMessage, "myapp:worker")
|
|
40
|
+
console.log("this is forwarded")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### restoreConsole
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
function restoreConsole(): void
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Restore original `console.*` methods.
|
|
50
|
+
|
|
51
|
+
## Main Thread Side
|
|
52
|
+
|
|
53
|
+
### createWorkerLogHandler
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
function createWorkerLogHandler(options?: { enableSpans?: boolean }): (message: WorkerMessage) => void
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Handle all worker messages (logs, spans, console). Creates loggers per namespace automatically.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const handle = createWorkerLogHandler({ enableSpans: true })
|
|
63
|
+
worker.onmessage = (e) => handle(e.data)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### createWorkerConsoleHandler
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
function createWorkerConsoleHandler(options?: {
|
|
70
|
+
defaultNamespace?: string
|
|
71
|
+
logger?: Logger
|
|
72
|
+
}): (message: WorkerConsoleMessage) => void
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Handle only console forwarding messages.
|
|
76
|
+
|
|
77
|
+
## Type Guards
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
isWorkerMessage(msg: unknown): msg is WorkerMessage
|
|
81
|
+
isWorkerConsoleMessage(msg: unknown): msg is WorkerConsoleMessage
|
|
82
|
+
isWorkerLogMessage(msg: unknown): msg is WorkerLogMessage
|
|
83
|
+
isWorkerSpanMessage(msg: unknown): msg is WorkerSpanMessage
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Message Types
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface WorkerConsoleMessage {
|
|
90
|
+
type: "console"
|
|
91
|
+
level: "log" | "debug" | "info" | "warn" | "error" | "trace"
|
|
92
|
+
namespace?: string
|
|
93
|
+
args: unknown[]
|
|
94
|
+
timestamp: number
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface WorkerLogMessage {
|
|
98
|
+
type: "log"
|
|
99
|
+
level: "trace" | "debug" | "info" | "warn" | "error"
|
|
100
|
+
namespace: string
|
|
101
|
+
message: string
|
|
102
|
+
data?: Record<string, unknown>
|
|
103
|
+
timestamp: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface WorkerSpanMessage {
|
|
107
|
+
type: "span"
|
|
108
|
+
event: "start" | "end"
|
|
109
|
+
namespace: string
|
|
110
|
+
spanId: string
|
|
111
|
+
traceId: string
|
|
112
|
+
parentId: string | null
|
|
113
|
+
startTime: number
|
|
114
|
+
endTime?: number
|
|
115
|
+
duration?: number
|
|
116
|
+
props: Record<string, unknown>
|
|
117
|
+
spanData: Record<string, unknown>
|
|
118
|
+
timestamp: number
|
|
119
|
+
}
|
|
120
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Writers
|
|
2
|
+
|
|
3
|
+
## addWriter
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
function addWriter(writer: (formatted: string, level: string) => void): () => void
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Subscribe to all formatted log output. Returns an unsubscribe function.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const lines: string[] = []
|
|
13
|
+
const unsub = addWriter((formatted, level) => {
|
|
14
|
+
lines.push(formatted)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Later:
|
|
18
|
+
unsub()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Writers receive output regardless of `setOutputMode()` or `setSuppressConsole()` settings.
|
|
22
|
+
|
|
23
|
+
## createFileWriter
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
function createFileWriter(path: string, options?: FileWriterOptions): FileWriter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Create a buffered file writer that flushes automatically.
|
|
30
|
+
|
|
31
|
+
### Options
|
|
32
|
+
|
|
33
|
+
| Option | Type | Default | Description |
|
|
34
|
+
| --------------- | -------- | ------- | -------------------------------------- |
|
|
35
|
+
| `bufferSize` | `number` | 4096 | Flush when buffer exceeds this (bytes) |
|
|
36
|
+
| `flushInterval` | `number` | 100 | Flush every N milliseconds |
|
|
37
|
+
|
|
38
|
+
### FileWriter Methods
|
|
39
|
+
|
|
40
|
+
| Method | Description |
|
|
41
|
+
| ------------- | ------------------------------------- |
|
|
42
|
+
| `write(line)` | Append line to buffer (adds `\n`) |
|
|
43
|
+
| `flush()` | Write buffer to disk immediately |
|
|
44
|
+
| `close()` | Flush remaining buffer and close file |
|
|
45
|
+
|
|
46
|
+
### Example
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { createFileWriter, addWriter } from "loggily"
|
|
50
|
+
|
|
51
|
+
const writer = createFileWriter("/tmp/app.log", {
|
|
52
|
+
bufferSize: 8192,
|
|
53
|
+
flushInterval: 200,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const unsub = addWriter((formatted) => writer.write(formatted))
|
|
57
|
+
|
|
58
|
+
// On shutdown:
|
|
59
|
+
unsub()
|
|
60
|
+
writer.close()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Safety
|
|
64
|
+
|
|
65
|
+
- The flush interval timer is `unref()`'d so it won't keep the process alive
|
|
66
|
+
- A `process.on("exit")` handler flushes remaining buffer on shutdown
|
|
67
|
+
- `close()` removes the exit handler and clears the interval
|
|
68
|
+
- Multiple `close()` calls are safe (idempotent)
|
|
69
|
+
- `write()` after `close()` is silently ignored
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add loggily # or: npm install loggily
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Create a Logger
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { createLogger } from "loggily"
|
|
13
|
+
|
|
14
|
+
const log = createLogger("myapp")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The string argument is the **namespace** -- it appears in every log message and is used for filtering.
|
|
18
|
+
|
|
19
|
+
## Log Messages
|
|
20
|
+
|
|
21
|
+
Every log method accepts a message string and optional structured data:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
log.info?.("server started", { port: 3000 })
|
|
25
|
+
log.debug?.("cache hit", { key: "user:42", ttl: 300 })
|
|
26
|
+
log.warn?.("rate limited", { remaining: 0, resetIn: 60 })
|
|
27
|
+
log.error?.(new Error("connection lost"))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Notice the `?.` -- this is intentional. When a log level is disabled, the method returns `undefined`, and optional chaining skips the entire call including argument evaluation. This is the core performance feature of loggily.
|
|
31
|
+
|
|
32
|
+
## Log Levels
|
|
33
|
+
|
|
34
|
+
From most to least verbose:
|
|
35
|
+
|
|
36
|
+
| Level | Purpose | Default |
|
|
37
|
+
| -------- | --------------------- | ------- |
|
|
38
|
+
| `trace` | Hot path debugging | Off |
|
|
39
|
+
| `debug` | Development debugging | Off |
|
|
40
|
+
| `info` | Normal operation | **On** |
|
|
41
|
+
| `warn` | Recoverable issues | On |
|
|
42
|
+
| `error` | Failures | On |
|
|
43
|
+
| `silent` | Disable all output | -- |
|
|
44
|
+
|
|
45
|
+
Control via environment variable or programmatically:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
LOG_LEVEL=debug bun run app # Enable debug and above
|
|
49
|
+
LOG_LEVEL=error bun run app # Only errors
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { setLogLevel } from "loggily"
|
|
54
|
+
setLogLevel("debug")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Child Loggers
|
|
58
|
+
|
|
59
|
+
Build a namespace hierarchy with `.logger()`:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const log = createLogger("myapp")
|
|
63
|
+
const db = log.logger("db") // namespace: "myapp:db"
|
|
64
|
+
const cache = log.logger("cache") // namespace: "myapp:cache"
|
|
65
|
+
|
|
66
|
+
db.info?.("connected", { host: "localhost" })
|
|
67
|
+
// 14:32:15 INFO myapp:db connected {host: "localhost"}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Props are inherited by children:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const log = createLogger("myapp", { version: "2.1" })
|
|
74
|
+
const db = log.logger("db")
|
|
75
|
+
// db.props includes { version: "2.1" }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Context Loggers
|
|
79
|
+
|
|
80
|
+
Add structured context that appears in every message:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const reqLog = log.child({ requestId: "abc-123" })
|
|
84
|
+
reqLog.info?.("handling request")
|
|
85
|
+
// 14:32:15 INFO myapp handling request {requestId: "abc-123"}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Spans
|
|
89
|
+
|
|
90
|
+
Time any operation with `using`:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
{
|
|
94
|
+
using span = log.span("import", { file: "data.csv" })
|
|
95
|
+
span.info?.("parsing")
|
|
96
|
+
const rows = await importFile()
|
|
97
|
+
span.spanData.rowCount = rows.length
|
|
98
|
+
}
|
|
99
|
+
// SPAN myapp:import (1234ms) {rowCount: 500, file: "data.csv"}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Spans are disabled by default. Enable with:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
TRACE=1 bun run app # All spans
|
|
106
|
+
TRACE=myapp:import bun run app # Specific namespace
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [Spans](/guide/spans) for the full guide.
|
|
110
|
+
|
|
111
|
+
## Namespace Filtering
|
|
112
|
+
|
|
113
|
+
Filter output by namespace, just like the `debug` package:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
DEBUG=myapp bun run app # Only myapp and children
|
|
117
|
+
DEBUG='myapp,-myapp:noisy' bun run app # Exclude noisy sub-namespace
|
|
118
|
+
DEBUG='*' bun run app # Everything
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Output Format
|
|
122
|
+
|
|
123
|
+
Pretty console in development, JSON in production:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Development (default)
|
|
127
|
+
bun run app
|
|
128
|
+
# 14:32:15 INFO myapp server started {port: 3000}
|
|
129
|
+
|
|
130
|
+
# Production (automatic)
|
|
131
|
+
NODE_ENV=production bun run app
|
|
132
|
+
# {"time":"2026-01-15T14:32:15.123Z","level":"info","name":"myapp","msg":"server started","port":3000}
|
|
133
|
+
|
|
134
|
+
# Explicit
|
|
135
|
+
LOG_FORMAT=json bun run app
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Next Steps
|
|
139
|
+
|
|
140
|
+
- [Zero-Overhead Logging](/guide/zero-overhead) -- How optional chaining works and benchmarks
|
|
141
|
+
- [Spans](/guide/spans) -- Timing, nesting, trace IDs
|
|
142
|
+
- [Worker Threads](/guide/workers) -- Forward logs from workers
|
|
143
|
+
- [API Reference](/api/) -- Complete API documentation
|