@fend/firo 0.0.1 → 0.0.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.
- package/README.md +30 -36
- package/dist/index.cjs +28 -59
- package/dist/index.d.cts +17 -25
- package/dist/index.d.ts +17 -25
- package/dist/index.js +28 -59
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,22 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
The logger for Node.js, Bun and Deno you've been looking for.
|
|
6
6
|
|
|
7
|
-
Beautiful **dev** output - out of the box.
|
|
7
|
+
Beautiful **dev** output - out of the box. Structured NDJSON for **prod**.
|
|
8
8
|
|
|
9
|
-
Think of it as pino, but with brilliant DX.
|
|
9
|
+
Think of it as pino, but with brilliant DX.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Demo
|
|
12
|
+
|
|
13
|
+
Beautiful colors in dev mode:
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
Structured NDJSON in production mode:
|
|
18
|
+
|
|
19
|
+

|
|
12
20
|
|
|
13
21
|
## Features
|
|
14
22
|
|
|
15
23
|
- **Dev mode** — colored, timestamped, human-readable output with context badges
|
|
16
|
-
- **Prod mode** — structured NDJSON, one record per line
|
|
17
|
-
- **Async mode** — non-blocking buffered output for high-load production
|
|
24
|
+
- **Prod mode** — structured NDJSON, one record per line
|
|
18
25
|
- **Context system** — attach key/value pairs that beautifully appear in every subsequent log line
|
|
19
26
|
- **Child loggers** — inherit parent context, fully isolated from each other
|
|
20
27
|
- **Per-call context** — attach extra fields to a single log call without mutating state
|
|
21
28
|
- **Severity Level filtering** — globally or per-mode thresholds to reduce noise
|
|
22
|
-
- **30 named colors** — `FIRO_COLORS` palette with
|
|
29
|
+
- **30 named colors** — `FIRO_COLORS` palette with great handpicked colors, plus raw ANSI/256-color/truecolor support
|
|
23
30
|
- **Zero dependencies** — small and fast, no bloat, no native addons. Works on Node.js, Bun and Deno.
|
|
24
31
|
|
|
25
32
|
## Install
|
|
@@ -29,13 +36,14 @@ Think of it as pino, but with brilliant DX. **firo** (from *Fir*) is the elegant
|
|
|
29
36
|
npm install @fend/firo
|
|
30
37
|
yarn add @fend/firo
|
|
31
38
|
pnpm add @fend/firo
|
|
32
|
-
npx jsr add @fend/firo
|
|
33
39
|
|
|
34
|
-
#
|
|
40
|
+
# for bun:
|
|
41
|
+
bun add @fend/firo
|
|
42
|
+
|
|
43
|
+
# for deno:
|
|
35
44
|
deno add jsr:@fend/firo
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
|
|
39
47
|
## Quick start
|
|
40
48
|
|
|
41
49
|
```ts
|
|
@@ -78,19 +86,6 @@ log.info('Request handled', { status: 200 })
|
|
|
78
86
|
// {"timestamp":"2024-01-15T14:32:01.204Z","level":"info","message":"Request handled","data":{"status":200}}
|
|
79
87
|
```
|
|
80
88
|
|
|
81
|
-
#### Async mode (Prod only)
|
|
82
|
-
|
|
83
|
-
For high-load applications, you can enable asynchronous buffered output. This avoids blocking the event loop when writing to `stdout`, which is critical for maintaining low latency.
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
const log = createLogger({
|
|
87
|
-
mode: 'prod',
|
|
88
|
-
async: true // Enables non-blocking buffered output
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
When `async` is enabled, logs are queued and flushed when the stream is ready (handling backpressure). We also ensure all buffered logs are flushed synchronously if the process exits or crashes.
|
|
93
|
-
|
|
94
89
|
## Best practices
|
|
95
90
|
|
|
96
91
|
### AsyncLocalStorage (Traceability)
|
|
@@ -166,15 +161,15 @@ log.info('Started')
|
|
|
166
161
|
|
|
167
162
|
```ts
|
|
168
163
|
// Hide the key, show only the value — useful for IDs
|
|
169
|
-
log.addContext({ key: 'userId', value: 'u-789',
|
|
164
|
+
log.addContext({ key: 'userId', value: 'u-789', omitKey: true })
|
|
170
165
|
// renders as [u-789] instead of [userId:u-789]
|
|
171
166
|
|
|
172
167
|
// Pin a specific color (0–9)
|
|
173
|
-
log.addContext({ key: 'region', value: '
|
|
168
|
+
log.addContext({ key: 'region', value: 'west', colorIndex: 3 })
|
|
174
169
|
|
|
175
170
|
// Use any ANSI color — 256-color, truecolor, anything
|
|
176
|
-
log.addContext({ key: 'trace', value: 'abc',
|
|
177
|
-
log.addContext({ key: 'span', value: 'xyz',
|
|
171
|
+
log.addContext({ key: 'trace', value: 'abc', color: '38;5;214' }) // 256-color orange
|
|
172
|
+
log.addContext({ key: 'span', value: 'xyz', color: '38;2;255;100;0' }) // truecolor
|
|
178
173
|
```
|
|
179
174
|
|
|
180
175
|
### Remove context
|
|
@@ -220,7 +215,7 @@ Add context to a single log call without touching the logger's state:
|
|
|
220
215
|
|
|
221
216
|
```ts
|
|
222
217
|
log.info('User action', payload, {
|
|
223
|
-
ctx: [{ key: 'userId', value: 42,
|
|
218
|
+
ctx: [{ key: 'userId', value: 42, omitKey: true }]
|
|
224
219
|
})
|
|
225
220
|
```
|
|
226
221
|
|
|
@@ -291,7 +286,7 @@ const log = createLogger({
|
|
|
291
286
|
|
|
292
287
|
Most loggers give you monochrome walls of text. firo gives you **30 handpicked colors** that make context badges instantly scannable — you stop reading and start seeing.
|
|
293
288
|
|
|
294
|
-

|
|
289
|
+

|
|
295
290
|
|
|
296
291
|
### How it works
|
|
297
292
|
|
|
@@ -304,9 +299,9 @@ import { createLogger, FIRO_COLORS } from '@fend/firo'
|
|
|
304
299
|
|
|
305
300
|
const log = createLogger()
|
|
306
301
|
|
|
307
|
-
log.addContext('region', '
|
|
308
|
-
log.addContext('service', 'auth',
|
|
309
|
-
log.addContext('env', 'staging',
|
|
302
|
+
log.addContext('region', { value: 'west', color: FIRO_COLORS.coral })
|
|
303
|
+
log.addContext('service', { value: 'auth', color: FIRO_COLORS.skyBlue })
|
|
304
|
+
log.addContext('env', { value: 'staging', color: FIRO_COLORS.lavender })
|
|
310
305
|
```
|
|
311
306
|
|
|
312
307
|
Available colors: `cyan`, `green`, `yellow`, `magenta`, `blue`, `brightCyan`, `brightGreen`, `brightYellow`, `brightMagenta`, `brightBlue`, `orange`, `pink`, `lilac`, `skyBlue`, `mint`, `salmon`, `lemon`, `lavender`, `sage`, `coral`, `teal`, `rose`, `pistachio`, `mauve`, `aqua`, `gold`, `thistle`, `seafoam`, `tangerine`, `periwinkle`.
|
|
@@ -316,8 +311,8 @@ Available colors: `cyan`, `green`, `yellow`, `magenta`, `blue`, `brightCyan`, `b
|
|
|
316
311
|
You can also pass any raw ANSI code as a string — 256-color, truecolor, go wild:
|
|
317
312
|
|
|
318
313
|
```ts
|
|
319
|
-
log.addContext('trace', 'abc',
|
|
320
|
-
log.addContext('span', 'xyz',
|
|
314
|
+
log.addContext('trace', { value: 'abc', color: '38;5;214' }) // 256-color
|
|
315
|
+
log.addContext('span', { value: 'xyz', color: '38;2;255;105;180' }) // truecolor pink
|
|
321
316
|
```
|
|
322
317
|
|
|
323
318
|
### Use all 30 colors for auto-hash
|
|
@@ -329,7 +324,7 @@ const log = createLogger({ useAllColors: true })
|
|
|
329
324
|
|
|
330
325
|
// Now every context key auto-gets one of 30 distinct colors
|
|
331
326
|
log.addContext('service', 'api')
|
|
332
|
-
log.addContext('region', '
|
|
327
|
+
log.addContext('region', 'west')
|
|
333
328
|
log.addContext('pod', 'web-3')
|
|
334
329
|
// Each badge is a different, beautiful color — no configuration needed
|
|
335
330
|
```
|
|
@@ -363,7 +358,7 @@ In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the
|
|
|
363
358
|
| `warn(msg, data?, opts?)` | Warning |
|
|
364
359
|
| `error(msg, err?, opts?)` | Error — also accepts `error(err)` |
|
|
365
360
|
| `child(ctx)` | Create a child logger with additional context |
|
|
366
|
-
| `addContext(key, value
|
|
361
|
+
| `addContext(key, value \| ext)` | Add a context entry |
|
|
367
362
|
| `addContext(item)` | Add a context entry (object form) |
|
|
368
363
|
| `removeFromContext(key)` | Remove a context entry by key |
|
|
369
364
|
| `getContext()` | Return the current context array |
|
|
@@ -378,7 +373,6 @@ In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the
|
|
|
378
373
|
| `minLevelInProd` | `LogLevel` | — | Overrides `minLevel` in prod mode |
|
|
379
374
|
| `transport` | `TransportFn` | — | Custom transport, overrides `mode` |
|
|
380
375
|
| `devTransportConfig` | `DevTransportConfig` | — | Options for the built-in dev transport |
|
|
381
|
-
| `async` | `boolean` | `false` | Enable non-blocking output (Prod mode only) |
|
|
382
376
|
| `useAllColors` | `boolean` | `false` | Use all 30 palette colors for auto-hash (instead of 10 safe) |
|
|
383
377
|
|
|
384
378
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -112,7 +112,6 @@ var colorizeLevel = (level, text) => {
|
|
|
112
112
|
// src/transports.ts
|
|
113
113
|
var import_node_util = require("util");
|
|
114
114
|
var import_node_process = __toESM(require("process"), 1);
|
|
115
|
-
var import_node_fs = __toESM(require("fs"), 1);
|
|
116
115
|
var createDevTransport = (config = {}) => {
|
|
117
116
|
const locale = config.locale ?? void 0;
|
|
118
117
|
const timeOpts = {
|
|
@@ -127,9 +126,9 @@ var createDevTransport = (config = {}) => {
|
|
|
127
126
|
const now = /* @__PURE__ */ new Date();
|
|
128
127
|
const timestamp = now.toLocaleTimeString(locale, timeOpts);
|
|
129
128
|
const contextStr = context.map((ctx) => {
|
|
130
|
-
const key = ctx.
|
|
129
|
+
const key = ctx.omitKey ? "" : `${ctx.key}:`;
|
|
131
130
|
const content = `${key}${ctx.value}`;
|
|
132
|
-
return colorize(`[${content}]`, ctx.
|
|
131
|
+
return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
|
|
133
132
|
}).join(" ");
|
|
134
133
|
if (level === "error" && data === void 0) {
|
|
135
134
|
const realError = wrapToError(msg);
|
|
@@ -207,40 +206,7 @@ var buildRecord = (level, context, msg, data) => {
|
|
|
207
206
|
}
|
|
208
207
|
return logRecord;
|
|
209
208
|
};
|
|
210
|
-
var createJsonTransport = (
|
|
211
|
-
const queue = [];
|
|
212
|
-
const maxQueueSize = config.maxQueueSize ?? 1e3;
|
|
213
|
-
let isDraining = false;
|
|
214
|
-
const flush = () => {
|
|
215
|
-
if (queue.length === 0 || isDraining) return;
|
|
216
|
-
while (queue.length > 0) {
|
|
217
|
-
const line = queue.shift();
|
|
218
|
-
const ok = import_node_process.default.stdout.write(line);
|
|
219
|
-
if (!ok) {
|
|
220
|
-
isDraining = true;
|
|
221
|
-
import_node_process.default.stdout.once("drain", () => {
|
|
222
|
-
isDraining = false;
|
|
223
|
-
flush();
|
|
224
|
-
});
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
const flushSync = () => {
|
|
230
|
-
while (queue.length > 0) {
|
|
231
|
-
const line = queue.shift();
|
|
232
|
-
if (line) {
|
|
233
|
-
try {
|
|
234
|
-
import_node_fs.default.writeSync(1, line);
|
|
235
|
-
} catch {
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
if (config.async) {
|
|
241
|
-
import_node_process.default.on("beforeExit", flushSync);
|
|
242
|
-
import_node_process.default.on("exit", flushSync);
|
|
243
|
-
}
|
|
209
|
+
var createJsonTransport = () => {
|
|
244
210
|
return (level, context, msg, data) => {
|
|
245
211
|
const record = buildRecord(level, context, msg, data);
|
|
246
212
|
let line;
|
|
@@ -259,15 +225,7 @@ var createJsonTransport = (config = {}) => {
|
|
|
259
225
|
}) + "\n";
|
|
260
226
|
}
|
|
261
227
|
}
|
|
262
|
-
|
|
263
|
-
import_node_process.default.stdout.write(line);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
queue.push(line);
|
|
267
|
-
if (queue.length > maxQueueSize) {
|
|
268
|
-
queue.shift();
|
|
269
|
-
}
|
|
270
|
-
flush();
|
|
228
|
+
import_node_process.default.stdout.write(line);
|
|
271
229
|
};
|
|
272
230
|
};
|
|
273
231
|
|
|
@@ -275,11 +233,9 @@ var createJsonTransport = (config = {}) => {
|
|
|
275
233
|
var fillContextItem = (item, useAllColors = false) => {
|
|
276
234
|
return {
|
|
277
235
|
...item,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
omitKey: item.options?.omitKey ?? false
|
|
282
|
-
}
|
|
236
|
+
colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
|
|
237
|
+
color: item.color,
|
|
238
|
+
omitKey: item.omitKey ?? false
|
|
283
239
|
};
|
|
284
240
|
};
|
|
285
241
|
var createLoggerInternal = (config, parentContext) => {
|
|
@@ -290,12 +246,23 @@ var createLoggerInternal = (config, parentContext) => {
|
|
|
290
246
|
return [...context2, ...invokeContext.map(fill)];
|
|
291
247
|
};
|
|
292
248
|
const context = [...parentContext.map(fill)];
|
|
293
|
-
const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport(
|
|
249
|
+
const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport() : createDevTransport(config.devTransportConfig));
|
|
294
250
|
const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
|
|
295
251
|
const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
|
|
296
252
|
const getContext = () => context;
|
|
297
|
-
const
|
|
298
|
-
|
|
253
|
+
const hasInContext = (key) => context.some((ctx) => ctx.key === key);
|
|
254
|
+
const addContext = (key, value) => {
|
|
255
|
+
let item;
|
|
256
|
+
if (typeof key === "string") {
|
|
257
|
+
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
258
|
+
const { value: extValue, ...opts } = value;
|
|
259
|
+
item = { key, value: extValue, ...opts };
|
|
260
|
+
} else {
|
|
261
|
+
item = { key, value };
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
item = key;
|
|
265
|
+
}
|
|
299
266
|
context.push(fill(item));
|
|
300
267
|
};
|
|
301
268
|
const removeKeyFromContext = (key) => {
|
|
@@ -303,11 +270,13 @@ var createLoggerInternal = (config, parentContext) => {
|
|
|
303
270
|
if (index !== -1) context.splice(index, 1);
|
|
304
271
|
};
|
|
305
272
|
const child = (ctx) => {
|
|
306
|
-
const newItems = Object.entries(ctx).map(([key, value]) =>
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
273
|
+
const newItems = Object.entries(ctx).map(([key, value]) => {
|
|
274
|
+
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
275
|
+
const { value: extValue, ...opts } = value;
|
|
276
|
+
return { key, value: extValue, ...opts };
|
|
277
|
+
}
|
|
278
|
+
return { key, value, colorIndex: getColorIndex(key, useAllColors) };
|
|
279
|
+
});
|
|
311
280
|
return createLoggerInternal({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
|
|
312
281
|
};
|
|
313
282
|
const debug = (msg, data, opts) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -15,11 +15,16 @@ type ContextOptions = {
|
|
|
15
15
|
type ContextItem = {
|
|
16
16
|
key: string;
|
|
17
17
|
value: ContextValue;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
} & Partial<ContextOptions>;
|
|
19
|
+
/** Configuration options for creating a logger child instance. */
|
|
20
|
+
type ContextExtension = {
|
|
21
|
+
value: ContextValue;
|
|
22
|
+
} & Partial<ContextOptions>;
|
|
20
23
|
/** A context entry where options have been fully resolved with defaults. */
|
|
21
24
|
type ContextItemWithOptions = ContextItem & {
|
|
22
|
-
|
|
25
|
+
colorIndex: number;
|
|
26
|
+
omitKey: boolean;
|
|
27
|
+
color?: string;
|
|
23
28
|
};
|
|
24
29
|
/** Options that can be passed to a single log call. */
|
|
25
30
|
type LogOptions = {
|
|
@@ -81,26 +86,13 @@ type DevTransportConfig = {
|
|
|
81
86
|
* @returns A `TransportFn` that writes to the console.
|
|
82
87
|
*/
|
|
83
88
|
declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
|
|
84
|
-
/**
|
|
85
|
-
* Configuration for the JSON transport.
|
|
86
|
-
*/
|
|
87
|
-
type JsonTransportConfig = {
|
|
88
|
-
/**
|
|
89
|
-
* Enable asynchronous/buffered output.
|
|
90
|
-
* When true, logs are queued and written when the stream is ready (handling backpressure).
|
|
91
|
-
*/
|
|
92
|
-
async?: boolean;
|
|
93
|
-
/** Maximum number of log lines to buffer when async is enabled. Defaults to 1000. */
|
|
94
|
-
maxQueueSize?: number;
|
|
95
|
-
};
|
|
96
89
|
/**
|
|
97
90
|
* Creates a built-in transport optimized for production.
|
|
98
91
|
* Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
|
|
99
92
|
*
|
|
100
|
-
* @param config Optional configuration for async and buffering behavior.
|
|
101
93
|
* @returns A `TransportFn` that writes JSON to standard output.
|
|
102
94
|
*/
|
|
103
|
-
declare const createJsonTransport: (
|
|
95
|
+
declare const createJsonTransport: () => TransportFn;
|
|
104
96
|
|
|
105
97
|
/**
|
|
106
98
|
* Configuration options for creating a logger instance.
|
|
@@ -118,8 +110,6 @@ type LoggerConfig = {
|
|
|
118
110
|
transport?: TransportFn;
|
|
119
111
|
/** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
|
|
120
112
|
devTransportConfig?: DevTransportConfig;
|
|
121
|
-
/** Enable asynchronous/buffered output for 'prod' mode to avoid event loop blocking. */
|
|
122
|
-
async?: boolean;
|
|
123
113
|
/** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to false (safe 10-color palette). */
|
|
124
114
|
useAllColors?: boolean;
|
|
125
115
|
};
|
|
@@ -127,7 +117,7 @@ type LoggerConfig = {
|
|
|
127
117
|
* The logger instance returned by `createLogger`.
|
|
128
118
|
* It is a callable object: calling `log(msg)` is shorthand for `log.info(msg)`.
|
|
129
119
|
*/
|
|
130
|
-
interface
|
|
120
|
+
interface Firo {
|
|
131
121
|
/** Shorthand for log.info() */
|
|
132
122
|
(msg: string, data?: unknown, opts?: LogOptions): void;
|
|
133
123
|
/** Log a debug message (dimmed in dev mode). */
|
|
@@ -144,23 +134,25 @@ interface ILogger {
|
|
|
144
134
|
* Create a scoped child logger that inherits the current logger's context.
|
|
145
135
|
* @param ctx An object containing key-value pairs to add to the child logger's context.
|
|
146
136
|
*/
|
|
147
|
-
child: (ctx: Record<string, ContextValue>) =>
|
|
137
|
+
child: (ctx: Record<string, ContextValue | ContextExtension>) => Firo;
|
|
148
138
|
/** Add a context entry by key and value. */
|
|
149
|
-
addContext(key: string, value: ContextValue
|
|
139
|
+
addContext(key: string, value: ContextValue | ContextExtension): void;
|
|
150
140
|
/** Add a context entry using the object form. */
|
|
151
141
|
addContext(item: ContextItem): void;
|
|
152
142
|
/** Remove a context entry by its key. */
|
|
153
143
|
removeFromContext(key: string): void;
|
|
154
144
|
/** Return the current context array attached to this logger instance. */
|
|
155
145
|
getContext(): ContextItem[];
|
|
146
|
+
/** Check if a context key exists in the current logger instance. */
|
|
147
|
+
hasInContext(key: string): boolean;
|
|
156
148
|
}
|
|
157
149
|
|
|
158
150
|
/**
|
|
159
151
|
* Creates a new logger instance with the specified configuration.
|
|
160
152
|
*
|
|
161
153
|
* @param config Optional configuration for log levels, mode, and transports.
|
|
162
|
-
* @returns A fully configured `
|
|
154
|
+
* @returns A fully configured `Firo` instance.
|
|
163
155
|
*/
|
|
164
|
-
declare const createLogger: (config?: LoggerConfig) =>
|
|
156
|
+
declare const createLogger: (config?: LoggerConfig) => Firo;
|
|
165
157
|
|
|
166
|
-
export { type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type
|
|
158
|
+
export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,11 +15,16 @@ type ContextOptions = {
|
|
|
15
15
|
type ContextItem = {
|
|
16
16
|
key: string;
|
|
17
17
|
value: ContextValue;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
} & Partial<ContextOptions>;
|
|
19
|
+
/** Configuration options for creating a logger child instance. */
|
|
20
|
+
type ContextExtension = {
|
|
21
|
+
value: ContextValue;
|
|
22
|
+
} & Partial<ContextOptions>;
|
|
20
23
|
/** A context entry where options have been fully resolved with defaults. */
|
|
21
24
|
type ContextItemWithOptions = ContextItem & {
|
|
22
|
-
|
|
25
|
+
colorIndex: number;
|
|
26
|
+
omitKey: boolean;
|
|
27
|
+
color?: string;
|
|
23
28
|
};
|
|
24
29
|
/** Options that can be passed to a single log call. */
|
|
25
30
|
type LogOptions = {
|
|
@@ -81,26 +86,13 @@ type DevTransportConfig = {
|
|
|
81
86
|
* @returns A `TransportFn` that writes to the console.
|
|
82
87
|
*/
|
|
83
88
|
declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
|
|
84
|
-
/**
|
|
85
|
-
* Configuration for the JSON transport.
|
|
86
|
-
*/
|
|
87
|
-
type JsonTransportConfig = {
|
|
88
|
-
/**
|
|
89
|
-
* Enable asynchronous/buffered output.
|
|
90
|
-
* When true, logs are queued and written when the stream is ready (handling backpressure).
|
|
91
|
-
*/
|
|
92
|
-
async?: boolean;
|
|
93
|
-
/** Maximum number of log lines to buffer when async is enabled. Defaults to 1000. */
|
|
94
|
-
maxQueueSize?: number;
|
|
95
|
-
};
|
|
96
89
|
/**
|
|
97
90
|
* Creates a built-in transport optimized for production.
|
|
98
91
|
* Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
|
|
99
92
|
*
|
|
100
|
-
* @param config Optional configuration for async and buffering behavior.
|
|
101
93
|
* @returns A `TransportFn` that writes JSON to standard output.
|
|
102
94
|
*/
|
|
103
|
-
declare const createJsonTransport: (
|
|
95
|
+
declare const createJsonTransport: () => TransportFn;
|
|
104
96
|
|
|
105
97
|
/**
|
|
106
98
|
* Configuration options for creating a logger instance.
|
|
@@ -118,8 +110,6 @@ type LoggerConfig = {
|
|
|
118
110
|
transport?: TransportFn;
|
|
119
111
|
/** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
|
|
120
112
|
devTransportConfig?: DevTransportConfig;
|
|
121
|
-
/** Enable asynchronous/buffered output for 'prod' mode to avoid event loop blocking. */
|
|
122
|
-
async?: boolean;
|
|
123
113
|
/** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to false (safe 10-color palette). */
|
|
124
114
|
useAllColors?: boolean;
|
|
125
115
|
};
|
|
@@ -127,7 +117,7 @@ type LoggerConfig = {
|
|
|
127
117
|
* The logger instance returned by `createLogger`.
|
|
128
118
|
* It is a callable object: calling `log(msg)` is shorthand for `log.info(msg)`.
|
|
129
119
|
*/
|
|
130
|
-
interface
|
|
120
|
+
interface Firo {
|
|
131
121
|
/** Shorthand for log.info() */
|
|
132
122
|
(msg: string, data?: unknown, opts?: LogOptions): void;
|
|
133
123
|
/** Log a debug message (dimmed in dev mode). */
|
|
@@ -144,23 +134,25 @@ interface ILogger {
|
|
|
144
134
|
* Create a scoped child logger that inherits the current logger's context.
|
|
145
135
|
* @param ctx An object containing key-value pairs to add to the child logger's context.
|
|
146
136
|
*/
|
|
147
|
-
child: (ctx: Record<string, ContextValue>) =>
|
|
137
|
+
child: (ctx: Record<string, ContextValue | ContextExtension>) => Firo;
|
|
148
138
|
/** Add a context entry by key and value. */
|
|
149
|
-
addContext(key: string, value: ContextValue
|
|
139
|
+
addContext(key: string, value: ContextValue | ContextExtension): void;
|
|
150
140
|
/** Add a context entry using the object form. */
|
|
151
141
|
addContext(item: ContextItem): void;
|
|
152
142
|
/** Remove a context entry by its key. */
|
|
153
143
|
removeFromContext(key: string): void;
|
|
154
144
|
/** Return the current context array attached to this logger instance. */
|
|
155
145
|
getContext(): ContextItem[];
|
|
146
|
+
/** Check if a context key exists in the current logger instance. */
|
|
147
|
+
hasInContext(key: string): boolean;
|
|
156
148
|
}
|
|
157
149
|
|
|
158
150
|
/**
|
|
159
151
|
* Creates a new logger instance with the specified configuration.
|
|
160
152
|
*
|
|
161
153
|
* @param config Optional configuration for log levels, mode, and transports.
|
|
162
|
-
* @returns A fully configured `
|
|
154
|
+
* @returns A fully configured `Firo` instance.
|
|
163
155
|
*/
|
|
164
|
-
declare const createLogger: (config?: LoggerConfig) =>
|
|
156
|
+
declare const createLogger: (config?: LoggerConfig) => Firo;
|
|
165
157
|
|
|
166
|
-
export { type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type
|
|
158
|
+
export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
|
package/dist/index.js
CHANGED
|
@@ -73,7 +73,6 @@ var colorizeLevel = (level, text) => {
|
|
|
73
73
|
// src/transports.ts
|
|
74
74
|
import { inspect } from "util";
|
|
75
75
|
import process from "process";
|
|
76
|
-
import fs from "fs";
|
|
77
76
|
var createDevTransport = (config = {}) => {
|
|
78
77
|
const locale = config.locale ?? void 0;
|
|
79
78
|
const timeOpts = {
|
|
@@ -88,9 +87,9 @@ var createDevTransport = (config = {}) => {
|
|
|
88
87
|
const now = /* @__PURE__ */ new Date();
|
|
89
88
|
const timestamp = now.toLocaleTimeString(locale, timeOpts);
|
|
90
89
|
const contextStr = context.map((ctx) => {
|
|
91
|
-
const key = ctx.
|
|
90
|
+
const key = ctx.omitKey ? "" : `${ctx.key}:`;
|
|
92
91
|
const content = `${key}${ctx.value}`;
|
|
93
|
-
return colorize(`[${content}]`, ctx.
|
|
92
|
+
return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
|
|
94
93
|
}).join(" ");
|
|
95
94
|
if (level === "error" && data === void 0) {
|
|
96
95
|
const realError = wrapToError(msg);
|
|
@@ -168,40 +167,7 @@ var buildRecord = (level, context, msg, data) => {
|
|
|
168
167
|
}
|
|
169
168
|
return logRecord;
|
|
170
169
|
};
|
|
171
|
-
var createJsonTransport = (
|
|
172
|
-
const queue = [];
|
|
173
|
-
const maxQueueSize = config.maxQueueSize ?? 1e3;
|
|
174
|
-
let isDraining = false;
|
|
175
|
-
const flush = () => {
|
|
176
|
-
if (queue.length === 0 || isDraining) return;
|
|
177
|
-
while (queue.length > 0) {
|
|
178
|
-
const line = queue.shift();
|
|
179
|
-
const ok = process.stdout.write(line);
|
|
180
|
-
if (!ok) {
|
|
181
|
-
isDraining = true;
|
|
182
|
-
process.stdout.once("drain", () => {
|
|
183
|
-
isDraining = false;
|
|
184
|
-
flush();
|
|
185
|
-
});
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
const flushSync = () => {
|
|
191
|
-
while (queue.length > 0) {
|
|
192
|
-
const line = queue.shift();
|
|
193
|
-
if (line) {
|
|
194
|
-
try {
|
|
195
|
-
fs.writeSync(1, line);
|
|
196
|
-
} catch {
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
if (config.async) {
|
|
202
|
-
process.on("beforeExit", flushSync);
|
|
203
|
-
process.on("exit", flushSync);
|
|
204
|
-
}
|
|
170
|
+
var createJsonTransport = () => {
|
|
205
171
|
return (level, context, msg, data) => {
|
|
206
172
|
const record = buildRecord(level, context, msg, data);
|
|
207
173
|
let line;
|
|
@@ -220,15 +186,7 @@ var createJsonTransport = (config = {}) => {
|
|
|
220
186
|
}) + "\n";
|
|
221
187
|
}
|
|
222
188
|
}
|
|
223
|
-
|
|
224
|
-
process.stdout.write(line);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
queue.push(line);
|
|
228
|
-
if (queue.length > maxQueueSize) {
|
|
229
|
-
queue.shift();
|
|
230
|
-
}
|
|
231
|
-
flush();
|
|
189
|
+
process.stdout.write(line);
|
|
232
190
|
};
|
|
233
191
|
};
|
|
234
192
|
|
|
@@ -236,11 +194,9 @@ var createJsonTransport = (config = {}) => {
|
|
|
236
194
|
var fillContextItem = (item, useAllColors = false) => {
|
|
237
195
|
return {
|
|
238
196
|
...item,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
omitKey: item.options?.omitKey ?? false
|
|
243
|
-
}
|
|
197
|
+
colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
|
|
198
|
+
color: item.color,
|
|
199
|
+
omitKey: item.omitKey ?? false
|
|
244
200
|
};
|
|
245
201
|
};
|
|
246
202
|
var createLoggerInternal = (config, parentContext) => {
|
|
@@ -251,12 +207,23 @@ var createLoggerInternal = (config, parentContext) => {
|
|
|
251
207
|
return [...context2, ...invokeContext.map(fill)];
|
|
252
208
|
};
|
|
253
209
|
const context = [...parentContext.map(fill)];
|
|
254
|
-
const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport(
|
|
210
|
+
const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport() : createDevTransport(config.devTransportConfig));
|
|
255
211
|
const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
|
|
256
212
|
const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
|
|
257
213
|
const getContext = () => context;
|
|
258
|
-
const
|
|
259
|
-
|
|
214
|
+
const hasInContext = (key) => context.some((ctx) => ctx.key === key);
|
|
215
|
+
const addContext = (key, value) => {
|
|
216
|
+
let item;
|
|
217
|
+
if (typeof key === "string") {
|
|
218
|
+
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
219
|
+
const { value: extValue, ...opts } = value;
|
|
220
|
+
item = { key, value: extValue, ...opts };
|
|
221
|
+
} else {
|
|
222
|
+
item = { key, value };
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
item = key;
|
|
226
|
+
}
|
|
260
227
|
context.push(fill(item));
|
|
261
228
|
};
|
|
262
229
|
const removeKeyFromContext = (key) => {
|
|
@@ -264,11 +231,13 @@ var createLoggerInternal = (config, parentContext) => {
|
|
|
264
231
|
if (index !== -1) context.splice(index, 1);
|
|
265
232
|
};
|
|
266
233
|
const child = (ctx) => {
|
|
267
|
-
const newItems = Object.entries(ctx).map(([key, value]) =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
234
|
+
const newItems = Object.entries(ctx).map(([key, value]) => {
|
|
235
|
+
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
236
|
+
const { value: extValue, ...opts } = value;
|
|
237
|
+
return { key, value: extValue, ...opts };
|
|
238
|
+
}
|
|
239
|
+
return { key, value, colorIndex: getColorIndex(key, useAllColors) };
|
|
240
|
+
});
|
|
272
241
|
return createLoggerInternal({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
|
|
273
242
|
};
|
|
274
243
|
const debug = (msg, data, opts) => {
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fend/firo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Elegant logger for Node.js, Bun and Deno with brilliant DX.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"logger",
|
|
7
|
-
"pino",
|
|
8
6
|
"firo",
|
|
7
|
+
"logger",
|
|
9
8
|
"fend",
|
|
10
9
|
"dx",
|
|
11
10
|
"structured-logging",
|
|
12
11
|
"ndjson",
|
|
13
12
|
"color",
|
|
13
|
+
"pino",
|
|
14
14
|
"zero-dependencies"
|
|
15
15
|
],
|
|
16
16
|
"author": "Alex Saft <fend25@gmail.com>",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup",
|
|
42
|
-
"publish:jsr": "pnpm dlx jsr publish",
|
|
43
42
|
"test": "node --import tsx --test test/*.test.ts",
|
|
43
|
+
"check": "tsc --noEmit && node --import tsx --test test/*.test.ts",
|
|
44
44
|
"typecheck": "tsc --noEmit",
|
|
45
45
|
"demo": "tsx demo.ts"
|
|
46
46
|
},
|