@fend/firo 0.0.6 → 0.0.7

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
@@ -7,11 +7,11 @@
7
7
  [![Build](https://github.com/fend25/firo/actions/workflows/publish.yml/badge.svg)](https://github.com/fend25/firo/actions/workflows/publish.yml)
8
8
  [![Best logger ever](https://img.shields.io/badge/best_logger-ever-166FFF)](https://github.com/fend25/firo)
9
9
 
10
- **Spruce up your logs!**
10
+ **Spruce up your logs!**
11
11
 
12
12
  The logger for Node.js, Bun and Deno you've been looking for.
13
13
 
14
- Beautiful **dev** output - out of the box. Structured NDJSON for **prod**.
14
+ Beautiful **dev** output - out of the box. Fast, structured NDJSON for **prod**.
15
15
 
16
16
  Think of it as pino, but with brilliant DX.
17
17
 
@@ -93,36 +93,6 @@ log.info('Request handled', { status: 200 })
93
93
  // {"timestamp":"2024-01-15T14:32:01.204Z","level":"info","message":"Request handled","data":{"status":200}}
94
94
  ```
95
95
 
96
- ## Best practices
97
-
98
- ### AsyncLocalStorage (Traceability)
99
-
100
- The best way to use **firo** in web frameworks is to store a child logger in `AsyncLocalStorage`. This gives you automatic traceability (e.g. `requestId`) across your entire call stack without passing the logger as an argument.
101
-
102
- ```ts
103
- import { AsyncLocalStorage } from 'node:util'
104
- import { createFiro } from '@fend/firo'
105
-
106
- const logger = createFiro()
107
- const storage = new AsyncLocalStorage()
108
-
109
- // Middleware example
110
- function middleware(req, res, next) {
111
- const reqLog = logger.child({
112
- requestId: req.headers['x-request-id'] || 'gen-123',
113
- method: req.method
114
- })
115
- storage.run(reqLog, next)
116
- }
117
-
118
- // Deeply nested function
119
- function someService() {
120
- const log = storage.getStore() ?? logger
121
- log.info('Service action performed')
122
- // Output: [requestId:gen-123] [method:GET] Service action performed
123
- }
124
- ```
125
-
126
96
  ## Log levels
127
97
 
128
98
  Four levels, in order: `debug` → `info` → `warn` → `error`.
@@ -139,16 +109,31 @@ Debug lines are dimmed in dev mode to reduce visual noise.
139
109
  ### Filtering
140
110
 
141
111
  ```ts
142
- // Suppress debug in dev, keep everything in prod
143
- const log = createFiro({
144
- minLevelInDev: 'info',
145
- minLevelInProd: 'warn',
146
- })
147
-
148
- // Or a single threshold for both modes
149
112
  const log = createFiro({ minLevel: 'warn' })
150
113
  ```
151
114
 
115
+ ## Error signatures
116
+
117
+ `error()` accepts multiple call signatures:
118
+
119
+ ```ts
120
+ // Message only will be automatically wrapped in an Error object to intentionally capture and preserve the stack trace
121
+ // because stack trace with a couple of extra levels of indirection is definitely better than no stack trace at all
122
+ log.error('Something went wrong')
123
+
124
+ // Message + Error object
125
+ log.error('Query failed', new Error('timeout'))
126
+
127
+ // Error object only
128
+ log.error(new Error('Unhandled'))
129
+
130
+ // Error + extra data
131
+ log.error(new Error('DB down'), { query: 'SELECT ...', reqId: 123 })
132
+
133
+ // Anything — will be coerced to Error
134
+ log.error(someUnknownThing)
135
+ ```
136
+
152
137
  ## Context
153
138
 
154
139
  Attach persistent key/value pairs to a logger instance. They appear in every log line.
@@ -166,29 +151,47 @@ log.info('Started')
166
151
 
167
152
  ### Context options
168
153
 
154
+ Three ways to add context:
155
+
156
+ ```ts
157
+ // 1. Simple key-value — just the basics
158
+ log.addContext('service', 'auth')
159
+
160
+ // 2. Key + value with options — when you need control
161
+ log.addContext('traceId', { value: 'abc-123-xyz', hideIn: 'dev' })
162
+ log.addContext('region', { value: 'west', color: '38;5;214' })
163
+
164
+ // 3. Object form — everything in one object
165
+ log.addContext({ key: 'userId', value: 'u-789', omitKey: true })
166
+ log.addContext({ key: 'span', value: 'xyz', color: '38;2;255;100;0' })
167
+ ```
168
+
169
+ Available options (styles 2 and 3):
170
+
169
171
  ```ts
170
- // Hide the key, show only the value useful for IDs
172
+ // Hide the key, show only the value: [u-789] instead of [userId:u-789]
171
173
  log.addContext({ key: 'userId', value: 'u-789', omitKey: true })
172
- // renders as [u-789] instead of [userId:u-789]
173
174
 
174
175
  // Pin a specific color by palette index (0–29)
175
- log.addContext({ key: 'region', value: 'west', colorIndex: 3 })
176
+ log.addContext('region', { value: 'west', colorIndex: 3 })
176
177
 
177
178
  // Use any ANSI color — 256-color, truecolor, anything
178
- log.addContext({ key: 'trace', value: 'abc', color: '38;5;214' }) // 256-color orange
179
+ log.addContext('trace', { value: 'abc', color: '38;5;214' }) // 256-color orange
179
180
  log.addContext({ key: 'span', value: 'xyz', color: '38;2;255;100;0' }) // truecolor
180
- ```
181
181
 
182
- ### Remove context
182
+ // Hide in dev — useful for traceIds that clutter the terminal
183
+ log.addContext('traceId', { value: 'abc-123-xyz', hideIn: 'dev' })
183
184
 
184
- ```ts
185
- log.removeFromContext('env')
185
+ // Hide in prod — dev-only debugging context
186
+ log.addContext('debugTag', { value: 'perf-test', hideIn: 'prod' })
186
187
  ```
187
188
 
188
- ### Read context
189
+ ### Context API
189
190
 
190
191
  ```ts
191
- const ctx = log.getContext() // ContextItem[]
192
+ log.getContext() // ContextItem[]
193
+ log.hasInContext('key') // boolean
194
+ log.removeFromContext('env')
192
195
  ```
193
196
 
194
197
  ## Child loggers
@@ -234,90 +237,75 @@ log.error('Payment failed', err, {
234
237
  })
235
238
  ```
236
239
 
237
- ## Error signatures
240
+ ## Dev formatter options
238
241
 
239
- `error()` accepts multiple call signatures:
242
+ Fine-tune the dev formatter's timestamp format. For example, to remove seconds and milliseconds:
240
243
 
241
244
  ```ts
242
- // Message only will be automatically wrapped in an Error object to intentionally capture and preserve the stack trace
243
- // because stack trace with a couple of extra levels of indirection is definitely better than no stack trace at all
244
- log.error('Something went wrong')
245
+ import { createFiro } from '@fend/firo'
245
246
 
246
- // Message + Error object
247
- log.error('Query failed', new Error('timeout'))
247
+ const log = createFiro({
248
+ devFormatterConfig: {
249
+ timeOptions: {
250
+ hour: '2-digit',
251
+ minute: '2-digit',
252
+ second: undefined,
253
+ fractionalSecondDigits: undefined
254
+ }
255
+ }
256
+ })
257
+ ```
248
258
 
249
- // Error object only
250
- log.error(new Error('Unhandled'))
259
+ ## Color palette
251
260
 
252
- // Error + extra data
253
- log.error(new Error('DB down'), { query: 'SELECT ...', reqId: 123 })
261
+ 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.
254
262
 
255
- // Anything — will be coerced to Error
256
- log.error(someUnknownThing)
257
- ```
263
+ ![firo color palette](https://github.com/fend25/firo/blob/main/img/color_madness.png?raw=true)
258
264
 
259
- ## Custom transport
265
+ ### How it works
260
266
 
261
- Provide your own transport function to take full control of output:
267
+ By default, firo auto-assigns colors from all 30 palette colors using a hash of the context key. Similar keys like `user-1` and `user-2` land on different colors automatically.
268
+
269
+ You can also pin a specific color using `FIRO_COLORS` — a named palette with full IDE autocomplete:
262
270
 
263
271
  ```ts
264
- import type { TransportFn } from '@fend/firo'
272
+ import { createFiro, FIRO_COLORS } from '@fend/firo'
265
273
 
266
- const myTransport: TransportFn = (level, context, msg, data, opts) => {
267
- // level: 'debug' | 'info' | 'warn' | 'error'
268
- // context: ContextItemWithOptions[]
269
- // msg: string | Error | unknown
270
- // data: Error | unknown
271
- // opts: LogOptions | undefined
272
- }
274
+ const log = createFiro()
273
275
 
274
- const log = createFiro({ transport: myTransport })
276
+ log.addContext('region', { value: 'west', color: FIRO_COLORS.coral })
277
+ log.addContext('service', { value: 'auth', color: FIRO_COLORS.skyBlue })
278
+ log.addContext('env', { value: 'staging', color: FIRO_COLORS.lavender })
275
279
  ```
276
280
 
277
- ### FiroUtils
281
+ 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`.
278
282
 
279
- `FiroUtils` exposes helper functions useful for building custom transports:
283
+ ### Want even more variety?
280
284
 
281
- ```ts
282
- import { FiroUtils } from '@fend/firo'
285
+ You can also pass any raw ANSI code as a string — 256-color, truecolor, go wild:
283
286
 
284
- FiroUtils.wrapToError(value) // coerce unknown → Error
285
- FiroUtils.serializeError(err) // Error → plain object { message, stack, name, cause?, ... }
286
- FiroUtils.safeStringify(obj) // JSON.stringify with bigint support + fallback
287
- FiroUtils.jsonReplacer // replacer for JSON.stringify (handles bigint)
288
- FiroUtils.extractMessage(msg) // extract message string from string | Error | unknown
289
- FiroUtils.colorize(text, idx) // wrap text in ANSI color by palette index
290
- FiroUtils.colorizeLevel(level, t) // wrap text in level color (red/yellow/dim)
287
+ ```ts
288
+ log.addContext('trace', { value: 'abc', color: '38;5;214' }) // 256-color
289
+ log.addContext('span', { value: 'xyz', color: '38;2;255;105;180' }) // truecolor pink
291
290
  ```
292
291
 
293
- ## Dev transport options
292
+ ### Restrict to safe colors
294
293
 
295
- Fine-tune the dev transport's timestamp format. For example, to remove seconds and milliseconds:
294
+ If your terminal doesn't support 256 colors, you can restrict auto-hash to 10 basic terminal-safe colors:
296
295
 
297
296
  ```ts
298
- import { createFiro } from '@fend/firo'
299
-
300
- const log = createFiro({
301
- devTransportConfig: {
302
- timeOptions: {
303
- hour: '2-digit',
304
- minute: '2-digit',
305
- second: undefined,
306
- fractionalSecondDigits: undefined
307
- }
308
- }
309
- })
297
+ const log = createFiro({ useSafeColors: true })
310
298
  ```
311
299
 
312
- ## Prod transport options
300
+ ## Prod formatter options
313
301
 
314
- Configure the prod (JSON) transport's timestamp format:
302
+ Configure the prod (JSON) formatter's timestamp format:
315
303
 
316
304
  ```ts
317
305
  // Epoch ms (faster, same as pino)
318
306
  const log = createFiro({
319
307
  mode: 'prod',
320
- prodTransportConfig: { timestamp: 'epoch' }
308
+ prodFormatterConfig: { timestamp: 'epoch' }
321
309
  })
322
310
  // {"timestamp":1711100000000,"level":"info","message":"hello"}
323
311
 
@@ -328,7 +316,7 @@ const log = createFiro({ mode: 'prod' })
328
316
 
329
317
  ### Custom destination
330
318
 
331
- By default, prod transport writes to `process.stdout`. You can redirect output to any object with a `.write(string)` method:
319
+ By default, prod formatter writes to `process.stdout`. You can redirect output to any object with a `.write(string)` method:
332
320
 
333
321
  ```ts
334
322
  import { createFiro } from '@fend/firo'
@@ -337,63 +325,89 @@ import { createWriteStream } from 'node:fs'
337
325
  // Write to a file
338
326
  const log = createFiro({
339
327
  mode: 'prod',
340
- prodTransportConfig: { dest: createWriteStream('/var/log/app.log') }
328
+ prodFormatterConfig: { dest: createWriteStream('/var/log/app.log') }
341
329
  })
342
330
 
343
331
  // Use SonicBoom for async buffered writes (same as pino)
344
332
  import SonicBoom from 'sonic-boom'
345
333
  const log = createFiro({
346
334
  mode: 'prod',
347
- prodTransportConfig: { dest: new SonicBoom({ fd: 1 }) }
335
+ prodFormatterConfig: { dest: new SonicBoom({ fd: 1 }) }
348
336
  })
349
337
  ```
350
338
 
351
- ## Color palette
339
+ ## Custom formatter
352
340
 
353
- 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.
341
+ If for some reason all the options are not enough and you need to take full control of the output, you can provide your own formatter function.
354
342
 
355
- ![firo color palette](https://github.com/fend25/firo/blob/main/img/color_madness.png?raw=true)
343
+ ```ts
344
+ import type { FormatterFn } from '@fend/firo'
356
345
 
357
- ### How it works
346
+ const myFormatter: FormatterFn = (level, context, msg, data, opts) => {
347
+ // level: 'debug' | 'info' | 'warn' | 'error'
348
+ // context: ContextItemWithOptions[]
349
+ // msg: string | Error | unknown
350
+ // data: Error | unknown
351
+ // opts: LogOptions | undefined
352
+ }
358
353
 
359
- By default, firo auto-assigns colors from all 30 palette colors using a hash of the context key. Similar keys like `user-1` and `user-2` land on different colors automatically.
354
+ const log = createFiro({ formatter: myFormatter })
355
+ ```
360
356
 
361
- You can also pin a specific color using `FIRO_COLORS` a named palette with full IDE autocomplete:
357
+ You don't have to start from scratch all the helpers we use internally are yours too:
362
358
 
363
- ```ts
364
- import { createFiro, FIRO_COLORS } from '@fend/firo'
359
+ #### FiroUtils
365
360
 
366
- const log = createFiro()
361
+ `FiroUtils` exposes helper functions useful for building custom formatters:
367
362
 
368
- log.addContext('region', { value: 'west', color: FIRO_COLORS.coral })
369
- log.addContext('service', { value: 'auth', color: FIRO_COLORS.skyBlue })
370
- log.addContext('env', { value: 'staging', color: FIRO_COLORS.lavender })
363
+ ```ts
364
+ import { FiroUtils } from '@fend/firo'
365
+
366
+ FiroUtils.wrapToError(value) // coerce unknown → Error
367
+ FiroUtils.serializeError(err) // Error → plain object { message, stack, name, cause?, ... }
368
+ FiroUtils.safeStringify(obj) // JSON.stringify with bigint support + fallback
369
+ FiroUtils.jsonReplacer // replacer for JSON.stringify (handles bigint)
370
+ FiroUtils.extractMessage(msg) // extract message string from string | Error | unknown
371
+ FiroUtils.colorize(text, idx, c?) // wrap text in ANSI color by palette index or raw code
372
+ FiroUtils.colorizeLevel(level, t) // wrap text in level color (red/yellow/dim)
371
373
  ```
372
374
 
373
- 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`.
375
+ ## Best practices
374
376
 
375
- ### Want even more variety?
377
+ ### AsyncLocalStorage (Traceability)
376
378
 
377
- You can also pass any raw ANSI code as a string 256-color, truecolor, go wild:
379
+ The best way to use **firo** in web frameworks is to store a child logger in `AsyncLocalStorage`. This gives you automatic traceability (e.g. `requestId`) across your entire call stack without passing the logger as an argument.
378
380
 
379
381
  ```ts
380
- log.addContext('trace', { value: 'abc', color: '38;5;214' }) // 256-color
381
- log.addContext('span', { value: 'xyz', color: '38;2;255;105;180' }) // truecolor pink
382
- ```
382
+ import { AsyncLocalStorage } from 'node:util'
383
+ import { createFiro } from '@fend/firo'
383
384
 
384
- ### Restrict to safe colors
385
+ const logger = createFiro()
386
+ const storage = new AsyncLocalStorage()
385
387
 
386
- If your terminal doesn't support 256 colors, you can restrict auto-hash to 10 basic terminal-safe colors:
388
+ // Middleware traceId is essential in prod logs but noisy in dev terminal
389
+ function middleware(req, res, next) {
390
+ const reqLog = logger.child({
391
+ traceId: { value: req.headers['x-trace-id'] || crypto.randomUUID(), hideIn: 'dev' },
392
+ method: req.method
393
+ })
394
+ storage.run(reqLog, next)
395
+ }
387
396
 
388
- ```ts
389
- const log = createFiro({ useAllColors: false })
397
+ // Deeply nested function — no logger passing needed
398
+ function someService() {
399
+ const log = storage.getStore() ?? logger
400
+ log.info('Service action performed')
401
+ // dev: [method:GET] Service action performed
402
+ // prod: {"traceId":"a1b2c3","method":"GET","message":"Service action performed"}
403
+ }
390
404
  ```
391
405
 
392
406
  ## Why not pino?
393
407
 
394
- **Pino** is Italian for *Pine*. It's a great, sturdy tree, especially in production.
408
+ **Pino** is Italian for *Pine*. It's a great, sturdy tree, especially in production.
395
409
 
396
- But sometimes you need to **Spruce** up your development experience.
410
+ But sometimes you need to **Spruce** up your development experience.
397
411
 
398
412
  The problem with pino is development. Its default output is raw JSON — one giant line per log entry, completely unreadable. You reach for `pino-pretty`, and suddenly you're maintaining infrastructure just to see what your app is doing.
399
413
 
@@ -405,36 +419,35 @@ The problem with pino is development. Its default output is raw JSON — one gia
405
419
  - **Visual hierarchy:** Debug lines are dimmed; high-signal logs stay readable.
406
420
  - **Zero config:** Beautiful output from the first second.
407
421
 
408
- In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the difference.
422
+ In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the difference. And the speed tax? Smaller than you'd think.
409
423
 
410
424
  ## Performance
411
425
 
412
- Prod mode (NDJSON) with `timestamp: 'epoch'`, Apple M1, Node.js 25. Average time per 100K log lines (lower is better):
426
+ Firo vs [pino](https://github.com/pinojs/pino) head-to-head, both writing to stdout, same machine, same conditions.
427
+
428
+ | Scenario | pino ops/sec | firo ops/sec | pino ms | firo ms | diff |
429
+ | ------------------------------ | -----------: | -----------: | ------: | ------: | -------: |
430
+ | simple string | 941,986 | 812,970 | 106.2 | 123.0 | +15.82% |
431
+ | string + small obj | 749,782 | 673,332 | 133.4 | 148.5 | +11.32% |
432
+ | string + bigger obj | 582,000 | 523,643 | 171.8 | 191.0 | +11.18% |
433
+ | with 3 context items | 818,123 | 589,433 | 122.2 | 169.7 | +38.87% |
434
+ | child logger (2 ctx) | 807,551 | 592,472 | 123.8 | 168.8 | +36.35% |
435
+ | deep child (7 ctx) + rich data | 408,246 | 314,244 | 245.0 | 318.2 | +29.88% |
436
+ | error with Error obj | 389,665 | 458,247 | 256.6 | 218.2 | -14.96% |
437
+
438
+ <sub>Apple M1, Node.js 25, 10 runs × 100K logs per scenario.</sub>
439
+
440
+ Pino is backed by 10 years of relentless optimization: [SonicBoom](https://github.com/pinojs/sonic-boom) async writer, [fast-json-stringify](https://github.com/fastify/fast-json-stringify) with schema-compiled serialization, pre-serialized child context stored as raw JSON fragments, C++ worker threads. It is an obsessively optimized piece of engineering and fully deserves its reputation as the fastest logger in Node.js.
413
441
 
414
- | Scenario | ops/sec | ms |
415
- | ---------------------- | ------- | ------ |
416
- | simple string | 782,488 | 127.8 |
417
- | string + small obj | 656,512 | 152.3 |
418
- | string + bigger obj | 513,087 | 194.9 |
419
- | with 3 context items | 570,441 | 175.3 |
420
- | child logger (2 ctx) | 568,977 | 175.8 |
421
- | error with Error obj | 470,758 | 212.4 |
442
+ Firo uses the most vanilla tools imaginable — `JSON.stringify` and `process.stdout.write`, shipping since 2009. Zero dependencies. Zero tricks. ~30% behind pino on a realistic deep-child scenario with nested payloads. 15% ahead on error serialization.
422
443
 
423
- For comparison, here's [pino's own benchmark](https://github.com/pinojs/pino/blob/main/docs/benchmarks.md) (100K `"hello world"` logs):
444
+ For context, here's where the other loggers stand according to [pino's own benchmarks](https://github.com/pinojs/pino/blob/main/docs/benchmarks.md) (basic "hello world", same machine): winston 174ms, bunyan 228ms, bole 107ms. firo's 123ms puts it comfortably ahead of winston and bunyan, neck and neck with bole — and all of that with a DX that none of them can match.
424
445
 
425
- | Logger | ms |
426
- | ---------------- | ------ |
427
- | pino | 114.8 |
428
- | **firo** | **127.8** |
429
- | bole | 172.7 |
430
- | pino (NodeStream)| 159.2 |
431
- | debug | 220.5 |
432
- | winston | 270.2 |
433
- | bunyan | 377.4 |
446
+ So yes — if you're looking for a pino alternative with gorgeous DX, structured context, and beautiful dev output, firo is right there performance-wise. Almost a drop-in replacement.*
434
447
 
435
- Pino is faster thanks to [SonicBoom](https://github.com/pinojs/sonic-boom)an optimized async writer with buffering and [fast-json-stringify](https://github.com/fastify/fast-json-stringify) for schema-compiled serialization. firo uses plain `JSON.stringify` + `process.stdout.write` and lands within 8% of pino a difference of ~130 nanoseconds per log line.
448
+ <sub>* Okay, not exactly drop-inwe put the message first and the data second, like normal humans. `log.info("hello", data)` instead of `log.info(data, "hello")`. We'll let you decide which API sparks more joy.</sub>
436
449
 
437
- Run it yourself: `pnpm bench`
450
+ Run the benchmark yourself: `pnpm bench`
438
451
 
439
452
  ## API reference
440
453
 
@@ -457,14 +470,21 @@ Run it yourself: `pnpm bench`
457
470
 
458
471
  | Option | Type | Default | Description |
459
472
  |---|---|---|---|
460
- | `mode` | `'dev' \| 'prod'` | `'dev'` | Selects the built-in transport |
461
- | `minLevel` | `LogLevel` | `'debug'` | Minimum level for both modes |
462
- | `minLevelInDev` | `LogLevel` | — | Overrides `minLevel` in dev mode |
463
- | `minLevelInProd` | `LogLevel` | — | Overrides `minLevel` in prod mode |
464
- | `transport` | `TransportFn` | — | Custom transport, overrides `mode` |
465
- | `devTransportConfig` | `DevTransportConfig` | | Options for the built-in dev transport |
466
- | `prodTransportConfig` | `ProdTransportConfig` | — | Options for the built-in JSON prod transport |
467
- | `useAllColors` | `boolean` | `true` | Use all 30 palette colors for auto-hash (set `false` for 10 safe colors) |
473
+ | `mode` | `'dev' \| 'prod'` | `'dev'` | Selects the built-in formatter |
474
+ | `minLevel` | `LogLevel` | `'debug'` | Minimum log level |
475
+ | `formatter` | `FormatterFn` | — | Custom formatter, overrides `mode` |
476
+ | `devFormatterConfig` | `DevFormatterConfig` | — | Options for the built-in dev formatter |
477
+ | `prodFormatterConfig` | `ProdFormatterConfig` | — | Options for the built-in JSON prod formatter |
478
+ | `useSafeColors` | `boolean` | `false` | Restrict auto-hash to 10 terminal-safe colors (set `true` for basic terminals) |
479
+
480
+ ### Context options
481
+
482
+ | Option | Type | Default | Description |
483
+ |---|---|---|---|
484
+ | `colorIndex` | `number` | auto | Color palette index (0–29) |
485
+ | `color` | `string` | — | Raw ANSI color code (e.g. `'38;5;214'`). Takes priority over `colorIndex` |
486
+ | `omitKey` | `boolean` | `false` | Hide the key, show only the value as `[value]` |
487
+ | `hideIn` | `'dev' \| 'prod'` | — | Hide this context item in dev or prod mode |
468
488
 
469
489
  ## License
470
490
 
package/dist/index.cjs CHANGED
@@ -32,9 +32,9 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  FIRO_COLORS: () => FIRO_COLORS,
34
34
  FiroUtils: () => utils_exports,
35
- createDevTransport: () => createDevTransport,
35
+ createDevFormatter: () => createDevFormatter,
36
36
  createFiro: () => createFiro,
37
- createProdTransport: () => createProdTransport
37
+ createProdFormatter: () => createProdFormatter
38
38
  });
39
39
  module.exports = __toCommonJS(index_exports);
40
40
 
@@ -95,12 +95,12 @@ var FIRO_COLORS = {
95
95
  };
96
96
  var COLORS_LIST = Object.values(FIRO_COLORS);
97
97
  var SAFE_COLORS_COUNT = 10;
98
- var getColorIndex = (str, useAllColors = false) => {
98
+ var getColorIndex = (str, useSafeColors = false) => {
99
99
  let hash = 0;
100
100
  for (let i = 0, len = str.length; i < len; i++) {
101
101
  hash = str.charCodeAt(i) + ((hash << 5) - hash);
102
102
  }
103
- const range = useAllColors ? COLORS_LIST.length : SAFE_COLORS_COUNT;
103
+ const range = useSafeColors ? SAFE_COLORS_COUNT : COLORS_LIST.length;
104
104
  return Math.abs(hash % range);
105
105
  };
106
106
  var colorize = (text, colorIndex, color) => {
@@ -152,10 +152,10 @@ var colorizeLevel = (level, text) => {
152
152
  }
153
153
  };
154
154
 
155
- // src/transport_dev.ts
155
+ // src/formatter_dev.ts
156
156
  var import_node_util2 = require("util");
157
157
  var import_node_process = __toESM(require("process"), 1);
158
- var createDevTransport = (config = {}) => {
158
+ var createDevFormatter = (config = {}) => {
159
159
  const locale = config.locale ?? void 0;
160
160
  const timeOpts = {
161
161
  hour12: false,
@@ -165,10 +165,10 @@ var createDevTransport = (config = {}) => {
165
165
  fractionalSecondDigits: 3,
166
166
  ...config.timeOptions || {}
167
167
  };
168
- const transport = (level, context, msg, data, opts) => {
168
+ const formatter = (level, context, msg, data, opts) => {
169
169
  const now = /* @__PURE__ */ new Date();
170
170
  const timestamp = now.toLocaleTimeString(locale, timeOpts);
171
- const contextStr = context.map((ctx) => {
171
+ const contextStr = context.filter((ctx) => ctx.hideIn !== "dev").map((ctx) => {
172
172
  const key = ctx.omitKey ? "" : `${ctx.key}:`;
173
173
  const content = `${key}${ctx.value}`;
174
174
  return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
@@ -195,10 +195,10 @@ var createDevTransport = (config = {}) => {
195
195
  if (level === "error") import_node_process.default.stderr.write(finalLine);
196
196
  else import_node_process.default.stdout.write(finalLine);
197
197
  };
198
- return transport;
198
+ return formatter;
199
199
  };
200
200
 
201
- // src/transport_prod.ts
201
+ // src/formatter_prod.ts
202
202
  var import_node_util3 = require("util");
203
203
  var import_node_process2 = __toESM(require("process"), 1);
204
204
  var buildRecord = (level, context, msg, getTimestamp, data) => {
@@ -207,6 +207,7 @@ var buildRecord = (level, context, msg, getTimestamp, data) => {
207
207
  level
208
208
  };
209
209
  for (let i = 0, len = context.length; i < len; i++) {
210
+ if (context[i].hideIn === "prod") continue;
210
211
  logRecord[context[i].key] = context[i].value;
211
212
  }
212
213
  logRecord.message = extractMessage(msg);
@@ -230,7 +231,7 @@ var buildRecord = (level, context, msg, getTimestamp, data) => {
230
231
  }
231
232
  return logRecord;
232
233
  };
233
- var createProdTransport = (config = {}) => {
234
+ var createProdFormatter = (config = {}) => {
234
235
  const getTimestamp = config.timestamp === "epoch" ? () => Date.now() : () => (/* @__PURE__ */ new Date()).toISOString();
235
236
  const dest = config.dest ?? import_node_process2.default.stdout;
236
237
  return (level, context, msg, data) => {
@@ -257,10 +258,10 @@ var createProdTransport = (config = {}) => {
257
258
 
258
259
  // src/index.ts
259
260
  var createFiro = (config = {}, parentContext = []) => {
260
- const useAllColors = config.useAllColors ?? true;
261
+ const useSafeColors = config.useSafeColors ?? false;
261
262
  const fill = (item) => ({
262
263
  ...item,
263
- colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
264
+ colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useSafeColors),
264
265
  color: item.color,
265
266
  omitKey: item.omitKey ?? false
266
267
  });
@@ -269,8 +270,8 @@ var createFiro = (config = {}, parentContext = []) => {
269
270
  return [...context2, ...invokeContext.map(fill)];
270
271
  };
271
272
  const context = [...parentContext.map(fill)];
272
- const transport = config.transport ?? (config.mode === "prod" ? createProdTransport(config.prodTransportConfig) : createDevTransport(config.devTransportConfig));
273
- const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
273
+ const formatter = config.formatter ?? (config.mode === "prod" ? createProdFormatter(config.prodFormatterConfig) : createDevFormatter(config.devFormatterConfig));
274
+ const minLevelName = config.minLevel;
274
275
  const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
275
276
  const getContext = () => context;
276
277
  const hasInContext = (key) => context.some((ctx) => ctx.key === key);
@@ -298,25 +299,25 @@ var createFiro = (config = {}, parentContext = []) => {
298
299
  const { value: extValue, ...opts } = value;
299
300
  return { key, value: extValue, ...opts };
300
301
  }
301
- return { key, value, colorIndex: getColorIndex(key, useAllColors) };
302
+ return { key, value, colorIndex: getColorIndex(key, useSafeColors) };
302
303
  });
303
- return createFiro({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
304
+ return createFiro({ formatter, minLevel: minLevelName, useSafeColors }, [...context, ...newItems]);
304
305
  };
305
306
  const debug = (msg, data, opts) => {
306
307
  if (minLevel > LOG_LEVELS.debug) return;
307
- transport("debug", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
308
+ formatter("debug", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
308
309
  };
309
310
  const info = (msg, data, opts) => {
310
311
  if (minLevel > LOG_LEVELS.info) return;
311
- transport("info", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
312
+ formatter("info", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
312
313
  };
313
314
  const warn = (msg, data, opts) => {
314
315
  if (minLevel > LOG_LEVELS.warn) return;
315
- transport("warn", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
316
+ formatter("warn", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
316
317
  };
317
318
  const error = (msgOrError, err, opts) => {
318
319
  if (minLevel > LOG_LEVELS.error) return;
319
- transport("error", appendContextWithInvokeContext(context, opts?.ctx), msgOrError, err, opts);
320
+ formatter("error", appendContextWithInvokeContext(context, opts?.ctx), msgOrError, err, opts);
320
321
  };
321
322
  const logInstance = ((msg, data, opts) => {
322
323
  info(msg, data, opts);
@@ -337,7 +338,7 @@ var createFiro = (config = {}, parentContext = []) => {
337
338
  0 && (module.exports = {
338
339
  FIRO_COLORS,
339
340
  FiroUtils,
340
- createDevTransport,
341
+ createDevFormatter,
341
342
  createFiro,
342
- createProdTransport
343
+ createProdFormatter
343
344
  });
package/dist/index.d.cts CHANGED
@@ -17,6 +17,8 @@ type ContextOptions = {
17
17
  color?: string;
18
18
  /** If true, the key name is hidden, and only the value is printed. */
19
19
  omitKey?: boolean;
20
+ /** Hide this context item in 'dev' or 'prod' mode. Useful for keeping traceIds out of dev output. */
21
+ hideIn?: 'dev' | 'prod';
20
22
  };
21
23
  /** A single key-value context entry. */
22
24
  type ContextItem = {
@@ -32,6 +34,7 @@ type ContextItemWithOptions = ContextItem & {
32
34
  colorIndex: number;
33
35
  omitKey: boolean;
34
36
  color?: string;
37
+ hideIn?: 'dev' | 'prod';
35
38
  };
36
39
  /** Options that can be passed to a single log call. */
37
40
  type LogOptions = {
@@ -41,7 +44,7 @@ type LogOptions = {
41
44
  ctx?: ContextItem[];
42
45
  };
43
46
  /** The signature of a function responsible for formatting and emitting log records. */
44
- type TransportFn = (level: LogLevel, context: ContextItemWithOptions[], message: string | Error | unknown, data?: Error | unknown, options?: LogOptions) => void;
47
+ type FormatterFn = (level: LogLevel, context: ContextItemWithOptions[], message: string | Error | unknown, data?: Error | unknown, options?: LogOptions) => void;
45
48
  /** Named color palette for context badges. Use with `color` option: `{ color: FIRO_COLORS.skyBlue }` */
46
49
  declare const FIRO_COLORS: {
47
50
  readonly cyan: "36";
@@ -76,7 +79,7 @@ declare const FIRO_COLORS: {
76
79
  readonly periwinkle: "38;5;147";
77
80
  };
78
81
  /** Hash a string to a stable color palette index. Similar strings land on different colors. */
79
- declare const getColorIndex: (str: string, useAllColors?: boolean) => number;
82
+ declare const getColorIndex: (str: string, useSafeColors?: boolean) => number;
80
83
  /** Wrap text in an ANSI color escape sequence by palette index or raw ANSI code. */
81
84
  declare const colorize: (text: string, colorIndex: number, color?: string) => string;
82
85
  /** JSON.stringify replacer that converts BigInt values to strings. */
@@ -87,7 +90,7 @@ declare const safeStringify: (obj: unknown) => string;
87
90
  declare const wrapToError: (obj: unknown) => Error;
88
91
  /** Serialize an error-like value to a plain object with `message`, `stack`, `name`, and recursively serialized `cause`. */
89
92
  declare const serializeError: (_err: unknown) => Record<string, unknown>;
90
- /** Extract a human-readable message string from any log input. Useful for building custom transports. */
93
+ /** Extract a human-readable message string from any log input. Useful for building custom formatters. */
91
94
  declare const extractMessage: (msg: string | Error | unknown) => string;
92
95
  /** Wrap text in an ANSI color based on log level: red for error, yellow for warn, dim for debug. */
93
96
  declare const colorizeLevel: (level: LogLevel, text: string) => string;
@@ -98,10 +101,10 @@ type utils_ContextItemWithOptions = ContextItemWithOptions;
98
101
  type utils_ContextOptions = ContextOptions;
99
102
  type utils_ContextValue = ContextValue;
100
103
  declare const utils_FIRO_COLORS: typeof FIRO_COLORS;
104
+ type utils_FormatterFn = FormatterFn;
101
105
  declare const utils_LOG_LEVELS: typeof LOG_LEVELS;
102
106
  type utils_LogLevel = LogLevel;
103
107
  type utils_LogOptions = LogOptions;
104
- type utils_TransportFn = TransportFn;
105
108
  declare const utils_colorize: typeof colorize;
106
109
  declare const utils_colorizeLevel: typeof colorizeLevel;
107
110
  declare const utils_extractMessage: typeof extractMessage;
@@ -111,29 +114,29 @@ declare const utils_safeStringify: typeof safeStringify;
111
114
  declare const utils_serializeError: typeof serializeError;
112
115
  declare const utils_wrapToError: typeof wrapToError;
113
116
  declare namespace utils {
114
- export { type utils_ContextExtension as ContextExtension, type utils_ContextItem as ContextItem, type utils_ContextItemWithOptions as ContextItemWithOptions, type utils_ContextOptions as ContextOptions, type utils_ContextValue as ContextValue, utils_FIRO_COLORS as FIRO_COLORS, utils_LOG_LEVELS as LOG_LEVELS, type utils_LogLevel as LogLevel, type utils_LogOptions as LogOptions, type utils_TransportFn as TransportFn, utils_colorize as colorize, utils_colorizeLevel as colorizeLevel, utils_extractMessage as extractMessage, utils_getColorIndex as getColorIndex, utils_jsonReplacer as jsonReplacer, utils_safeStringify as safeStringify, utils_serializeError as serializeError, utils_wrapToError as wrapToError };
117
+ export { type utils_ContextExtension as ContextExtension, type utils_ContextItem as ContextItem, type utils_ContextItemWithOptions as ContextItemWithOptions, type utils_ContextOptions as ContextOptions, type utils_ContextValue as ContextValue, utils_FIRO_COLORS as FIRO_COLORS, type utils_FormatterFn as FormatterFn, utils_LOG_LEVELS as LOG_LEVELS, type utils_LogLevel as LogLevel, type utils_LogOptions as LogOptions, utils_colorize as colorize, utils_colorizeLevel as colorizeLevel, utils_extractMessage as extractMessage, utils_getColorIndex as getColorIndex, utils_jsonReplacer as jsonReplacer, utils_safeStringify as safeStringify, utils_serializeError as serializeError, utils_wrapToError as wrapToError };
115
118
  }
116
119
 
117
120
  /**
118
- * Configuration options for the development transport.
121
+ * Configuration options for the development formatter.
119
122
  */
120
- type DevTransportConfig = {
123
+ type DevFormatterConfig = {
121
124
  /** The locale used for formatting the timestamp. Defaults to the system locale. */
122
125
  locale?: string;
123
126
  /** Standard Intl.DateTimeFormatOptions to customize the timestamp output. */
124
127
  timeOptions?: Intl.DateTimeFormatOptions;
125
128
  };
126
129
  /**
127
- * Creates a built-in transport optimized for local development.
130
+ * Creates a built-in formatter optimized for local development.
128
131
  * Emits colored, human-readable strings to stdout/stderr.
129
132
  *
130
- * @param config Optional configuration for the transport, like timestamp formats.
131
- * @returns A `TransportFn` that writes to the console.
133
+ * @param config Optional configuration for the formatter, like timestamp formats.
134
+ * @returns A `FormatterFn` that writes to the console.
132
135
  */
133
- declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
136
+ declare const createDevFormatter: (config?: DevFormatterConfig) => FormatterFn;
134
137
 
135
138
  type TimestampFormat = 'iso' | 'epoch';
136
- type ProdTransportConfig = {
139
+ type ProdFormatterConfig = {
137
140
  /** Timestamp format: 'iso' (default) for ISO 8601 string, 'epoch' for ms since Unix epoch. */
138
141
  timestamp?: TimestampFormat;
139
142
  /** Output destination. Any object with a `.write(string)` method. Defaults to `process.stdout`. */
@@ -142,33 +145,29 @@ type ProdTransportConfig = {
142
145
  };
143
146
  };
144
147
  /**
145
- * Creates a built-in transport optimized for production.
148
+ * Creates a built-in formatter optimized for production.
146
149
  * Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
147
150
  *
148
- * @returns A `TransportFn` that writes JSON to standard output.
151
+ * @returns A `FormatterFn` that writes JSON to standard output.
149
152
  */
150
- declare const createProdTransport: (config?: ProdTransportConfig) => TransportFn;
153
+ declare const createProdFormatter: (config?: ProdFormatterConfig) => FormatterFn;
151
154
 
152
155
  /**
153
156
  * Configuration options for creating a logger instance.
154
157
  */
155
158
  type LoggerConfig = {
156
- /** The minimum log level for both modes. Overridden by mode-specific thresholds. */
159
+ /** The minimum log level. */
157
160
  minLevel?: LogLevel;
158
- /** Minimum log level to emit in 'dev' mode. */
159
- minLevelInDev?: LogLevel;
160
- /** Minimum log level to emit in 'prod' mode. */
161
- minLevelInProd?: LogLevel;
162
- /** Specifies the built-in transport to use. Defaults to 'dev'. */
161
+ /** Selects the built-in formatter. Defaults to 'dev'. */
163
162
  mode?: 'dev' | 'prod';
164
- /** Provide a custom transport function to override the built-in behaviors. */
165
- transport?: TransportFn;
166
- /** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
167
- devTransportConfig?: DevTransportConfig;
168
- /** Options for the built-in prod transport (e.g. timestamp format). */
169
- prodTransportConfig?: ProdTransportConfig;
170
- /** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to true. Set to false to restrict to 10 terminal-safe colors. */
171
- useAllColors?: boolean;
163
+ /** Provide a custom formatter function to override the built-in behaviors. */
164
+ formatter?: FormatterFn;
165
+ /** Options for fine-tuning the built-in development formatter (e.g. timestamp format). */
166
+ devFormatterConfig?: DevFormatterConfig;
167
+ /** Options for the built-in prod formatter (e.g. timestamp format). */
168
+ prodFormatterConfig?: ProdFormatterConfig;
169
+ /** Restrict auto-assigned context badge colors to 10 terminal-safe colors. Defaults to false (all 30 palette colors are used). */
170
+ useSafeColors?: boolean;
172
171
  };
173
172
  /**
174
173
  * The logger instance returned by `createFiro`.
@@ -209,9 +208,9 @@ interface Firo {
209
208
  /**
210
209
  * Creates a new logger instance with the specified configuration.
211
210
  *
212
- * @param config Optional configuration for log levels, mode, and transports.
211
+ * @param config Optional configuration for log levels, format, and formatters.
213
212
  * @returns A fully configured `Firo` instance.
214
213
  */
215
214
  declare const createFiro: (config?: LoggerConfig, parentContext?: ContextItem[]) => Firo;
216
215
 
217
- export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, utils as FiroUtils, type LogLevel, type LogOptions, type LoggerConfig, type ProdTransportConfig, type TimestampFormat, type TransportFn, createDevTransport, createFiro, createProdTransport };
216
+ export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevFormatterConfig, FIRO_COLORS, type Firo, utils as FiroUtils, type FormatterFn, type LogLevel, type LogOptions, type LoggerConfig, type ProdFormatterConfig, type TimestampFormat, createDevFormatter, createFiro, createProdFormatter };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ type ContextOptions = {
17
17
  color?: string;
18
18
  /** If true, the key name is hidden, and only the value is printed. */
19
19
  omitKey?: boolean;
20
+ /** Hide this context item in 'dev' or 'prod' mode. Useful for keeping traceIds out of dev output. */
21
+ hideIn?: 'dev' | 'prod';
20
22
  };
21
23
  /** A single key-value context entry. */
22
24
  type ContextItem = {
@@ -32,6 +34,7 @@ type ContextItemWithOptions = ContextItem & {
32
34
  colorIndex: number;
33
35
  omitKey: boolean;
34
36
  color?: string;
37
+ hideIn?: 'dev' | 'prod';
35
38
  };
36
39
  /** Options that can be passed to a single log call. */
37
40
  type LogOptions = {
@@ -41,7 +44,7 @@ type LogOptions = {
41
44
  ctx?: ContextItem[];
42
45
  };
43
46
  /** The signature of a function responsible for formatting and emitting log records. */
44
- type TransportFn = (level: LogLevel, context: ContextItemWithOptions[], message: string | Error | unknown, data?: Error | unknown, options?: LogOptions) => void;
47
+ type FormatterFn = (level: LogLevel, context: ContextItemWithOptions[], message: string | Error | unknown, data?: Error | unknown, options?: LogOptions) => void;
45
48
  /** Named color palette for context badges. Use with `color` option: `{ color: FIRO_COLORS.skyBlue }` */
46
49
  declare const FIRO_COLORS: {
47
50
  readonly cyan: "36";
@@ -76,7 +79,7 @@ declare const FIRO_COLORS: {
76
79
  readonly periwinkle: "38;5;147";
77
80
  };
78
81
  /** Hash a string to a stable color palette index. Similar strings land on different colors. */
79
- declare const getColorIndex: (str: string, useAllColors?: boolean) => number;
82
+ declare const getColorIndex: (str: string, useSafeColors?: boolean) => number;
80
83
  /** Wrap text in an ANSI color escape sequence by palette index or raw ANSI code. */
81
84
  declare const colorize: (text: string, colorIndex: number, color?: string) => string;
82
85
  /** JSON.stringify replacer that converts BigInt values to strings. */
@@ -87,7 +90,7 @@ declare const safeStringify: (obj: unknown) => string;
87
90
  declare const wrapToError: (obj: unknown) => Error;
88
91
  /** Serialize an error-like value to a plain object with `message`, `stack`, `name`, and recursively serialized `cause`. */
89
92
  declare const serializeError: (_err: unknown) => Record<string, unknown>;
90
- /** Extract a human-readable message string from any log input. Useful for building custom transports. */
93
+ /** Extract a human-readable message string from any log input. Useful for building custom formatters. */
91
94
  declare const extractMessage: (msg: string | Error | unknown) => string;
92
95
  /** Wrap text in an ANSI color based on log level: red for error, yellow for warn, dim for debug. */
93
96
  declare const colorizeLevel: (level: LogLevel, text: string) => string;
@@ -98,10 +101,10 @@ type utils_ContextItemWithOptions = ContextItemWithOptions;
98
101
  type utils_ContextOptions = ContextOptions;
99
102
  type utils_ContextValue = ContextValue;
100
103
  declare const utils_FIRO_COLORS: typeof FIRO_COLORS;
104
+ type utils_FormatterFn = FormatterFn;
101
105
  declare const utils_LOG_LEVELS: typeof LOG_LEVELS;
102
106
  type utils_LogLevel = LogLevel;
103
107
  type utils_LogOptions = LogOptions;
104
- type utils_TransportFn = TransportFn;
105
108
  declare const utils_colorize: typeof colorize;
106
109
  declare const utils_colorizeLevel: typeof colorizeLevel;
107
110
  declare const utils_extractMessage: typeof extractMessage;
@@ -111,29 +114,29 @@ declare const utils_safeStringify: typeof safeStringify;
111
114
  declare const utils_serializeError: typeof serializeError;
112
115
  declare const utils_wrapToError: typeof wrapToError;
113
116
  declare namespace utils {
114
- export { type utils_ContextExtension as ContextExtension, type utils_ContextItem as ContextItem, type utils_ContextItemWithOptions as ContextItemWithOptions, type utils_ContextOptions as ContextOptions, type utils_ContextValue as ContextValue, utils_FIRO_COLORS as FIRO_COLORS, utils_LOG_LEVELS as LOG_LEVELS, type utils_LogLevel as LogLevel, type utils_LogOptions as LogOptions, type utils_TransportFn as TransportFn, utils_colorize as colorize, utils_colorizeLevel as colorizeLevel, utils_extractMessage as extractMessage, utils_getColorIndex as getColorIndex, utils_jsonReplacer as jsonReplacer, utils_safeStringify as safeStringify, utils_serializeError as serializeError, utils_wrapToError as wrapToError };
117
+ export { type utils_ContextExtension as ContextExtension, type utils_ContextItem as ContextItem, type utils_ContextItemWithOptions as ContextItemWithOptions, type utils_ContextOptions as ContextOptions, type utils_ContextValue as ContextValue, utils_FIRO_COLORS as FIRO_COLORS, type utils_FormatterFn as FormatterFn, utils_LOG_LEVELS as LOG_LEVELS, type utils_LogLevel as LogLevel, type utils_LogOptions as LogOptions, utils_colorize as colorize, utils_colorizeLevel as colorizeLevel, utils_extractMessage as extractMessage, utils_getColorIndex as getColorIndex, utils_jsonReplacer as jsonReplacer, utils_safeStringify as safeStringify, utils_serializeError as serializeError, utils_wrapToError as wrapToError };
115
118
  }
116
119
 
117
120
  /**
118
- * Configuration options for the development transport.
121
+ * Configuration options for the development formatter.
119
122
  */
120
- type DevTransportConfig = {
123
+ type DevFormatterConfig = {
121
124
  /** The locale used for formatting the timestamp. Defaults to the system locale. */
122
125
  locale?: string;
123
126
  /** Standard Intl.DateTimeFormatOptions to customize the timestamp output. */
124
127
  timeOptions?: Intl.DateTimeFormatOptions;
125
128
  };
126
129
  /**
127
- * Creates a built-in transport optimized for local development.
130
+ * Creates a built-in formatter optimized for local development.
128
131
  * Emits colored, human-readable strings to stdout/stderr.
129
132
  *
130
- * @param config Optional configuration for the transport, like timestamp formats.
131
- * @returns A `TransportFn` that writes to the console.
133
+ * @param config Optional configuration for the formatter, like timestamp formats.
134
+ * @returns A `FormatterFn` that writes to the console.
132
135
  */
133
- declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
136
+ declare const createDevFormatter: (config?: DevFormatterConfig) => FormatterFn;
134
137
 
135
138
  type TimestampFormat = 'iso' | 'epoch';
136
- type ProdTransportConfig = {
139
+ type ProdFormatterConfig = {
137
140
  /** Timestamp format: 'iso' (default) for ISO 8601 string, 'epoch' for ms since Unix epoch. */
138
141
  timestamp?: TimestampFormat;
139
142
  /** Output destination. Any object with a `.write(string)` method. Defaults to `process.stdout`. */
@@ -142,33 +145,29 @@ type ProdTransportConfig = {
142
145
  };
143
146
  };
144
147
  /**
145
- * Creates a built-in transport optimized for production.
148
+ * Creates a built-in formatter optimized for production.
146
149
  * Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
147
150
  *
148
- * @returns A `TransportFn` that writes JSON to standard output.
151
+ * @returns A `FormatterFn` that writes JSON to standard output.
149
152
  */
150
- declare const createProdTransport: (config?: ProdTransportConfig) => TransportFn;
153
+ declare const createProdFormatter: (config?: ProdFormatterConfig) => FormatterFn;
151
154
 
152
155
  /**
153
156
  * Configuration options for creating a logger instance.
154
157
  */
155
158
  type LoggerConfig = {
156
- /** The minimum log level for both modes. Overridden by mode-specific thresholds. */
159
+ /** The minimum log level. */
157
160
  minLevel?: LogLevel;
158
- /** Minimum log level to emit in 'dev' mode. */
159
- minLevelInDev?: LogLevel;
160
- /** Minimum log level to emit in 'prod' mode. */
161
- minLevelInProd?: LogLevel;
162
- /** Specifies the built-in transport to use. Defaults to 'dev'. */
161
+ /** Selects the built-in formatter. Defaults to 'dev'. */
163
162
  mode?: 'dev' | 'prod';
164
- /** Provide a custom transport function to override the built-in behaviors. */
165
- transport?: TransportFn;
166
- /** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
167
- devTransportConfig?: DevTransportConfig;
168
- /** Options for the built-in prod transport (e.g. timestamp format). */
169
- prodTransportConfig?: ProdTransportConfig;
170
- /** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to true. Set to false to restrict to 10 terminal-safe colors. */
171
- useAllColors?: boolean;
163
+ /** Provide a custom formatter function to override the built-in behaviors. */
164
+ formatter?: FormatterFn;
165
+ /** Options for fine-tuning the built-in development formatter (e.g. timestamp format). */
166
+ devFormatterConfig?: DevFormatterConfig;
167
+ /** Options for the built-in prod formatter (e.g. timestamp format). */
168
+ prodFormatterConfig?: ProdFormatterConfig;
169
+ /** Restrict auto-assigned context badge colors to 10 terminal-safe colors. Defaults to false (all 30 palette colors are used). */
170
+ useSafeColors?: boolean;
172
171
  };
173
172
  /**
174
173
  * The logger instance returned by `createFiro`.
@@ -209,9 +208,9 @@ interface Firo {
209
208
  /**
210
209
  * Creates a new logger instance with the specified configuration.
211
210
  *
212
- * @param config Optional configuration for log levels, mode, and transports.
211
+ * @param config Optional configuration for log levels, format, and formatters.
213
212
  * @returns A fully configured `Firo` instance.
214
213
  */
215
214
  declare const createFiro: (config?: LoggerConfig, parentContext?: ContextItem[]) => Firo;
216
215
 
217
- export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, utils as FiroUtils, type LogLevel, type LogOptions, type LoggerConfig, type ProdTransportConfig, type TimestampFormat, type TransportFn, createDevTransport, createFiro, createProdTransport };
216
+ export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevFormatterConfig, FIRO_COLORS, type Firo, utils as FiroUtils, type FormatterFn, type LogLevel, type LogOptions, type LoggerConfig, type ProdFormatterConfig, type TimestampFormat, createDevFormatter, createFiro, createProdFormatter };
package/dist/index.js CHANGED
@@ -61,12 +61,12 @@ var FIRO_COLORS = {
61
61
  };
62
62
  var COLORS_LIST = Object.values(FIRO_COLORS);
63
63
  var SAFE_COLORS_COUNT = 10;
64
- var getColorIndex = (str, useAllColors = false) => {
64
+ var getColorIndex = (str, useSafeColors = false) => {
65
65
  let hash = 0;
66
66
  for (let i = 0, len = str.length; i < len; i++) {
67
67
  hash = str.charCodeAt(i) + ((hash << 5) - hash);
68
68
  }
69
- const range = useAllColors ? COLORS_LIST.length : SAFE_COLORS_COUNT;
69
+ const range = useSafeColors ? SAFE_COLORS_COUNT : COLORS_LIST.length;
70
70
  return Math.abs(hash % range);
71
71
  };
72
72
  var colorize = (text, colorIndex, color) => {
@@ -118,10 +118,10 @@ var colorizeLevel = (level, text) => {
118
118
  }
119
119
  };
120
120
 
121
- // src/transport_dev.ts
121
+ // src/formatter_dev.ts
122
122
  import { inspect as inspect2 } from "util";
123
123
  import process from "process";
124
- var createDevTransport = (config = {}) => {
124
+ var createDevFormatter = (config = {}) => {
125
125
  const locale = config.locale ?? void 0;
126
126
  const timeOpts = {
127
127
  hour12: false,
@@ -131,10 +131,10 @@ var createDevTransport = (config = {}) => {
131
131
  fractionalSecondDigits: 3,
132
132
  ...config.timeOptions || {}
133
133
  };
134
- const transport = (level, context, msg, data, opts) => {
134
+ const formatter = (level, context, msg, data, opts) => {
135
135
  const now = /* @__PURE__ */ new Date();
136
136
  const timestamp = now.toLocaleTimeString(locale, timeOpts);
137
- const contextStr = context.map((ctx) => {
137
+ const contextStr = context.filter((ctx) => ctx.hideIn !== "dev").map((ctx) => {
138
138
  const key = ctx.omitKey ? "" : `${ctx.key}:`;
139
139
  const content = `${key}${ctx.value}`;
140
140
  return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
@@ -161,10 +161,10 @@ var createDevTransport = (config = {}) => {
161
161
  if (level === "error") process.stderr.write(finalLine);
162
162
  else process.stdout.write(finalLine);
163
163
  };
164
- return transport;
164
+ return formatter;
165
165
  };
166
166
 
167
- // src/transport_prod.ts
167
+ // src/formatter_prod.ts
168
168
  import { inspect as inspect3 } from "util";
169
169
  import process2 from "process";
170
170
  var buildRecord = (level, context, msg, getTimestamp, data) => {
@@ -173,6 +173,7 @@ var buildRecord = (level, context, msg, getTimestamp, data) => {
173
173
  level
174
174
  };
175
175
  for (let i = 0, len = context.length; i < len; i++) {
176
+ if (context[i].hideIn === "prod") continue;
176
177
  logRecord[context[i].key] = context[i].value;
177
178
  }
178
179
  logRecord.message = extractMessage(msg);
@@ -196,7 +197,7 @@ var buildRecord = (level, context, msg, getTimestamp, data) => {
196
197
  }
197
198
  return logRecord;
198
199
  };
199
- var createProdTransport = (config = {}) => {
200
+ var createProdFormatter = (config = {}) => {
200
201
  const getTimestamp = config.timestamp === "epoch" ? () => Date.now() : () => (/* @__PURE__ */ new Date()).toISOString();
201
202
  const dest = config.dest ?? process2.stdout;
202
203
  return (level, context, msg, data) => {
@@ -223,10 +224,10 @@ var createProdTransport = (config = {}) => {
223
224
 
224
225
  // src/index.ts
225
226
  var createFiro = (config = {}, parentContext = []) => {
226
- const useAllColors = config.useAllColors ?? true;
227
+ const useSafeColors = config.useSafeColors ?? false;
227
228
  const fill = (item) => ({
228
229
  ...item,
229
- colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
230
+ colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useSafeColors),
230
231
  color: item.color,
231
232
  omitKey: item.omitKey ?? false
232
233
  });
@@ -235,8 +236,8 @@ var createFiro = (config = {}, parentContext = []) => {
235
236
  return [...context2, ...invokeContext.map(fill)];
236
237
  };
237
238
  const context = [...parentContext.map(fill)];
238
- const transport = config.transport ?? (config.mode === "prod" ? createProdTransport(config.prodTransportConfig) : createDevTransport(config.devTransportConfig));
239
- const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
239
+ const formatter = config.formatter ?? (config.mode === "prod" ? createProdFormatter(config.prodFormatterConfig) : createDevFormatter(config.devFormatterConfig));
240
+ const minLevelName = config.minLevel;
240
241
  const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
241
242
  const getContext = () => context;
242
243
  const hasInContext = (key) => context.some((ctx) => ctx.key === key);
@@ -264,25 +265,25 @@ var createFiro = (config = {}, parentContext = []) => {
264
265
  const { value: extValue, ...opts } = value;
265
266
  return { key, value: extValue, ...opts };
266
267
  }
267
- return { key, value, colorIndex: getColorIndex(key, useAllColors) };
268
+ return { key, value, colorIndex: getColorIndex(key, useSafeColors) };
268
269
  });
269
- return createFiro({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
270
+ return createFiro({ formatter, minLevel: minLevelName, useSafeColors }, [...context, ...newItems]);
270
271
  };
271
272
  const debug = (msg, data, opts) => {
272
273
  if (minLevel > LOG_LEVELS.debug) return;
273
- transport("debug", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
274
+ formatter("debug", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
274
275
  };
275
276
  const info = (msg, data, opts) => {
276
277
  if (minLevel > LOG_LEVELS.info) return;
277
- transport("info", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
278
+ formatter("info", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
278
279
  };
279
280
  const warn = (msg, data, opts) => {
280
281
  if (minLevel > LOG_LEVELS.warn) return;
281
- transport("warn", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
282
+ formatter("warn", appendContextWithInvokeContext(context, opts?.ctx), msg, data, opts);
282
283
  };
283
284
  const error = (msgOrError, err, opts) => {
284
285
  if (minLevel > LOG_LEVELS.error) return;
285
- transport("error", appendContextWithInvokeContext(context, opts?.ctx), msgOrError, err, opts);
286
+ formatter("error", appendContextWithInvokeContext(context, opts?.ctx), msgOrError, err, opts);
286
287
  };
287
288
  const logInstance = ((msg, data, opts) => {
288
289
  info(msg, data, opts);
@@ -302,7 +303,7 @@ var createFiro = (config = {}, parentContext = []) => {
302
303
  export {
303
304
  FIRO_COLORS,
304
305
  utils_exports as FiroUtils,
305
- createDevTransport,
306
+ createDevFormatter,
306
307
  createFiro,
307
- createProdTransport
308
+ createProdFormatter
308
309
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fend/firo",
3
- "version": "0.0.6",
4
- "description": "Elegant logger for Node.js, Bun and Deno with brilliant DX.",
3
+ "version": "0.0.7",
4
+ "description": "Elegant logger for Node.js, Bun and Deno with brilliant DX and pino-grade speed",
5
5
  "keywords": [
6
6
  "firo",
7
7
  "logger",
@@ -43,6 +43,7 @@
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.5.0",
45
45
  "bumpp": "^11.0.1",
46
+ "pino": "^10.3.1",
46
47
  "tsup": "^8.5.1",
47
48
  "tsx": "^4.0.0",
48
49
  "typescript": "^5.9.3"