halua 2.0.2 → 4.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 +356 -7
- package/lib/AGENTS.md +143 -0
- package/lib/index.cjs +752 -432
- package/lib/index.d.ts +232 -157
- package/lib/index.js +739 -403
- package/package.json +35 -15
- package/lib/index.d.cts +0 -157
package/README.md
CHANGED
|
@@ -1,15 +1,364 @@
|
|
|
1
1
|
# Halua
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A powerful, extensible logging library for Node.js, browsers, and edge runtimes.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Halua gives you full control over log output through pluggable dispatchers (text, JSON, console), hierarchical child
|
|
6
|
+
loggers, fine-grained level filtering (including minor levels like `INFO+3`), and zero-config defaults that just work.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/halua)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Features
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
- Zero-config default logger (writes to `console` using appropriate methods)
|
|
14
|
+
- Four built-in dispatchers: `NewTextDispatcher`, `NewJSONDispatcher`, `NewConsoleDispatcher`, `NewConsoleColoredDispatcher`
|
|
15
|
+
- Compose any number of dispatchers per logger instance
|
|
16
|
+
- Child loggers that automatically append context (`logger.child("user", 42)`)
|
|
17
|
+
- Powerful level system: `TRACE` < `DEBUG` < `INFO` < `NOTICE` < `WARN` < `ERROR` < `FATAL` + minor levels (`INFO+5`)
|
|
18
|
+
- Per-dispatcher level overrides and exact-match mode
|
|
19
|
+
- Beautiful structured formatting for objects, arrays, Maps, Sets, Errors, etc.
|
|
20
|
+
- Safe by design — dispatcher errors never crash your application
|
|
21
|
+
- `.stamp(label, id?)` + `.stampEnd(id)` (or returned ender) for `performance.now`-based timing with automatic pretty
|
|
22
|
+
`took X.XXms` logging
|
|
23
|
+
- Tiny, fast, tree-shakeable ESM + CJS + TypeScript
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install halua
|
|
29
|
+
# or
|
|
30
|
+
pnpm add halua
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { halua } from "halua"
|
|
37
|
+
|
|
38
|
+
halua.info("Application started")
|
|
39
|
+
halua.warn("Disk space low", { available: "12%" })
|
|
40
|
+
halua.error("timeout") // strings accepted too (unknownToError normalizes to Error)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Default output (console):**
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
22/05/2026 21:55:50 INFO Application started
|
|
47
|
+
22/05/2026 21:55:50 WARN Disk space low { available: '12%' }
|
|
48
|
+
22/05/2026 21:55:50 ERROR Error: timeout
|
|
49
|
+
at ...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Dedicated Loggers & Dispatchers
|
|
53
|
+
|
|
54
|
+
Use the built-in dispatcher factories to create purpose-specific loggers:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { halua, NewTextDispatcher, NewJSONDispatcher, Level } from "halua"
|
|
58
|
+
|
|
59
|
+
// Text logger (human readable)
|
|
60
|
+
let textLogger = halua.create(NewTextDispatcher((line) => sendToLogServer(line)))
|
|
61
|
+
|
|
62
|
+
// JSON logger (for structured ingestion)
|
|
63
|
+
let jsonLogger = halua.create(NewJSONDispatcher((json) => writeToArchive(json)))
|
|
64
|
+
|
|
65
|
+
// Console logger (explicit)
|
|
66
|
+
let consoleLogger = halua.create(NewConsoleDispatcher(console))
|
|
67
|
+
|
|
68
|
+
// Colored console (ANSI in Node, %c CSS in browsers; colors: trace/debug=purple, info=blue, notice=orange, warn/error/fatal=red)
|
|
69
|
+
let colorLogger = halua.create(NewConsoleColoredDispatcher(console))
|
|
70
|
+
|
|
71
|
+
textLogger.info("user action", { id: 123, type: "click" })
|
|
72
|
+
// -> 22/05/2026 21:55:50 INFO user action { id: 123, type: "click" }
|
|
73
|
+
|
|
74
|
+
jsonLogger.info("structured", { success: true })
|
|
75
|
+
// -> {"timestamp":"2026-05-22T18:55:50.430Z","level":"INFO","args":["structured",{"success": true}]}
|
|
15
76
|
```
|
|
77
|
+
|
|
78
|
+
You can pass an array to use **multiple dispatchers at once**:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
let prodLogger = halua.create([NewTextDispatcher(sendToFile), NewJSONDispatcher(sendToElastic)], { level: Level.Info })
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Child Loggers (Context)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
let requestLogger = halua.child("requestId", "abc-123", "user", 42)
|
|
88
|
+
|
|
89
|
+
requestLogger.info("processing started")
|
|
90
|
+
// -> ... INFO processing started requestId abc-123 user 42
|
|
91
|
+
|
|
92
|
+
let stepLogger = requestLogger.child("step", "validate")
|
|
93
|
+
stepLogger.warn("slow validation")
|
|
94
|
+
// -> ... WARN slow validation requestId abc-123 user 42 step validate
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Call `.create({ withArgs: [] })` to clear context on a child.
|
|
98
|
+
|
|
99
|
+
## Level Control
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { Level } from "halua"
|
|
103
|
+
|
|
104
|
+
// Instance level (affects all dispatchers that don't override)
|
|
105
|
+
let logger = halua.create({ level: Level.Warn })
|
|
106
|
+
|
|
107
|
+
logger.debug("hidden")
|
|
108
|
+
logger.info("hidden")
|
|
109
|
+
logger.warn("visible")
|
|
110
|
+
logger.error("visible")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Per-Dispatcher Levels
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
let logger = halua.create([
|
|
117
|
+
NewTextDispatcher(sendToFile, { level: Level.Info }),
|
|
118
|
+
NewJSONDispatcher(sendToMetrics, { level: Level.Error }),
|
|
119
|
+
])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Minor / Custom Levels
|
|
123
|
+
|
|
124
|
+
Use the `LEVEL+N` syntax for fine-grained control (e.g. sampling, feature flags):
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
let logger = halua.create(NewTextDispatcher(out), { level: `${Level.Info}+2` })
|
|
128
|
+
|
|
129
|
+
logger.logTo("INFO+1", "sampled out")
|
|
130
|
+
logger.logTo("INFO+2", "important info") // logged
|
|
131
|
+
logger.logTo("INFO+3", "very important") // logged
|
|
132
|
+
logger.logTo("WARN", "always higher major level") // logged
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can also pass string levels directly: `{ level: "ERROR+7" }` or `logTo("DEBUG+10", ...)`.
|
|
136
|
+
|
|
137
|
+
## Sensitive Data Redaction
|
|
138
|
+
|
|
139
|
+
Pass `redactDataRegExp` (a `RegExp`) to `halua.create(options)` for the logger instance (applies to all its dispatchers) or to individual `New*Dispatcher(..., { redactDataRegExp })` (overrides the logger default).
|
|
140
|
+
|
|
141
|
+
- In strings (and strings inside arrays): all matches of the regexp are replaced by `"^_^"`
|
|
142
|
+
- In objects and Maps: if a _key_ matches the regexp, its value (any type) is replaced entirely by `"^_^"`
|
|
143
|
+
- Works for both text and structured (JSON) output, and for `errorMeta`
|
|
144
|
+
- Use the exported `DefaultRedactRegExp` for common PII (passwords, tokens, api keys, emails, SSNs, JWTs, credit cards, etc.) or provide your own.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { halua, NewJSONDispatcher, DefaultRedactRegExp } from "halua"
|
|
148
|
+
|
|
149
|
+
// logger-level default (affects dispatchers without their own setting)
|
|
150
|
+
let prodLogger = halua.create(
|
|
151
|
+
[
|
|
152
|
+
NewJSONDispatcher(sendToStore),
|
|
153
|
+
NewTextDispatcher(sendToFile, { level: Level.Warn }), // this one can override if needed
|
|
154
|
+
],
|
|
155
|
+
{ redactDataRegExp: DefaultRedactRegExp },
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
prodLogger.info("login", { user: "alice", password: "hunter2", apiKey: "sk_xxx" })
|
|
159
|
+
// args become: ["login", { user: "alice", password: "^_^", apiKey: "^_^" }]
|
|
160
|
+
|
|
161
|
+
prodLogger.info("token eyJhbGciOi...abc.123", "email: foo@bar.com")
|
|
162
|
+
// the string arg will have secrets replaced by ^_^
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `redact` helper is also exported for custom dispatchers or preprocessing.
|
|
166
|
+
|
|
167
|
+
## Dispatcher Options
|
|
168
|
+
|
|
169
|
+
All `New*Dispatcher` factories accept a second `options` argument:
|
|
170
|
+
|
|
171
|
+
| Option | Type | Default | Description |
|
|
172
|
+
| ------------------ | ------------------------ | ----------- | ---------------------------------------------------------------------------------------- |
|
|
173
|
+
| `level` | `LogLevel` | `undefined` | Minimum level this dispatcher accepts |
|
|
174
|
+
| `exact` | `LogLevel \| LogLevel[]` | `null` | Only log these exact levels (ignores normal hierarchy) |
|
|
175
|
+
| `printTimestamp` | `boolean` | `true` | Include timestamp in output |
|
|
176
|
+
| `printLevel` | `boolean` | `true` | Include level name in output |
|
|
177
|
+
| `spacing` | `boolean` | `true` | Pretty-print objects/arrays with tabs & newlines (Text & JSON only) |
|
|
178
|
+
| `redactDataRegExp` | `RegExp` | `undefined` | Redact sensitive data in strings/arrays and by key in objects/maps (see feature section) |
|
|
179
|
+
|
|
180
|
+
`NewConsoleDispatcher` and `NewConsoleColoredDispatcher` do not support `spacing` (they pass values directly to console methods).
|
|
181
|
+
|
|
182
|
+
## API Reference
|
|
183
|
+
|
|
184
|
+
### Main Export
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import {
|
|
188
|
+
halua,
|
|
189
|
+
Level,
|
|
190
|
+
NewTextDispatcher,
|
|
191
|
+
NewJSONDispatcher,
|
|
192
|
+
NewConsoleDispatcher,
|
|
193
|
+
NewConsoleColoredDispatcher,
|
|
194
|
+
} from "halua"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `halua` — default logger instance (preconfigured with `NewConsoleDispatcher`)
|
|
198
|
+
- `Level` — enum: `Trace | Debug | Info | Notice | Warn | Error | Fatal`
|
|
199
|
+
- `NewTextDispatcher(send: (line: string, errorMeta?: Record<string, any>) => void, options?)` → factory
|
|
200
|
+
- `NewJSONDispatcher(send: (json: string, errorMeta?: Record<string, any>) => void, options?)` → factory
|
|
201
|
+
- `NewConsoleDispatcher(console: {debug,info,warn,error}, options?)` → factory
|
|
202
|
+
- `NewConsoleColoredDispatcher(console: {debug,info,warn,error}, options?)` → factory (colors levels: TRACE/DEBUG=purple, INFO=blue, NOTICE=orange, WARN/ERROR/FATAL=red; uses ANSI in Node, %c in browsers)
|
|
203
|
+
|
|
204
|
+
### Advanced Exports (for custom dispatcher authors)
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { DispatcherBase, format, getType, toJSONValue, Dispatcher, HaluaLogger, ConsoleLike } from "halua"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- `DispatcherBase` — extendable base class implementing `dispatch(meta, args)` + timestamp/level prefixing; override via
|
|
211
|
+
`formatArg`
|
|
212
|
+
- `format(spec: {type, value, ...})` — the text pretty-printer (handles circulars, Errors, Maps, etc.)
|
|
213
|
+
- `getType(value)` — returns `ArgumentType` discriminant for any JS value
|
|
214
|
+
- `toJSONValue(value)` — converts any value to a JSON-legal tree (Errors → {name,message,stack[]}, etc.)
|
|
215
|
+
- `redact(value, regexp?)` — recursively redacts strings by content match and object/map values by key match (used internally by the redact feature)
|
|
216
|
+
- `DefaultRedactRegExp` — built-in regexp matching common sensitive keys and value patterns (password, token, email, ssn, jwt, cc, etc.)
|
|
217
|
+
- `Dispatcher` — interface for raw custom dispatchers (`dispatch(meta, args): void`)
|
|
218
|
+
- `HaluaLogger` — the logger instance interface
|
|
219
|
+
- `ConsoleLike` — minimal `{ debug, info, warn, error }` shape accepted by `NewConsoleDispatcher` / `NewConsoleColoredDispatcher`
|
|
220
|
+
|
|
221
|
+
### Logger Instance Methods
|
|
222
|
+
|
|
223
|
+
| Method | Description |
|
|
224
|
+
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| `.create(dispatcher?, options?)` | Create a new independent logger (inherits dispatchers/options when partial) |
|
|
226
|
+
| `.child(...args)` | Create child logger that appends context to every message |
|
|
227
|
+
| `.setDispatchers(dispatcher \| dispatchers[])` | Replace all dispatchers |
|
|
228
|
+
| `.appendDispatchers(...)` | Add more dispatchers to existing set |
|
|
229
|
+
| `.logTo(level, ...args)` | Log at a custom / minor level |
|
|
230
|
+
| `.trace / .debug / .info / .warn / .notice / .fatal(...args)` | Standard levels (varargs) |
|
|
231
|
+
| `.error(error, meta?)` | Log at ERROR level; first arg (unknown) is normalized to Error; optional `meta?: ErrorMeta` (generic on the logger instance) — when supplied, the normalized Error instance is auto-attached under `error` key and the augmented object becomes the second arg to dispatchers |
|
|
232
|
+
| `.assert(condition, error, meta?)` | Log at ERROR only on falsy condition; same error + optional `meta?: ErrorMeta` semantics as `.error` (auto-attaches normalized Error under `error` when meta supplied) |
|
|
233
|
+
| `.stamp(label, id?)` | Start high-res perf timer (`performance.now`); returns ender fn; optional id for `.stampEnd` |
|
|
234
|
+
| `.stampEnd(id)` | End named stamp started with same id on this logger; logs pretty `label took X.XXms` |
|
|
235
|
+
|
|
236
|
+
Every method returns a new `HaluaLogger` when using `.create` / `.child`, so they are fully chainable.
|
|
237
|
+
|
|
238
|
+
`setDispatchers` and `appendDispatchers` mutate the dispatcher list on the **live instance only**. They do not update the blueprint used by later `.create(...)` or `.child(...)` calls on that same logger (those continue to inherit the dispatchers that were supplied when the logger was originally built). If you need a fresh logger with the new set, call `halua.create(newDispatchers)` (or the mutated logger's `.create(newDispatchers)`).
|
|
239
|
+
|
|
240
|
+
## Error Handling
|
|
241
|
+
|
|
242
|
+
Halua never throws from logging calls. If a dispatcher fails, the error is reported via `console.error` (best-effort)
|
|
243
|
+
and logging continues for other dispatchers.
|
|
244
|
+
|
|
245
|
+
### Using `errorMeta` with error trackers (Sentry, Rollbar, etc.)
|
|
246
|
+
|
|
247
|
+
The special `.error(unknown, meta?)` and `.assert(condition, unknown, meta?)` methods accept an optional second `meta`
|
|
248
|
+
object. When you use a custom `send` callback with `NewTextDispatcher` (or `NewJSONDispatcher`), this `meta` is
|
|
249
|
+
delivered as the **second argument** to your send function.
|
|
250
|
+
|
|
251
|
+
Halua automatically appends the _normalized Error instance_ (the same one passed to the primary log args) under the
|
|
252
|
+
`error` key of the meta object. This makes it trivial to forward the live `Error` (including `.cause`, custom props,
|
|
253
|
+
and accurate stack) to error trackers instead of a string or plain-object snapshot.
|
|
254
|
+
|
|
255
|
+
This is ideal for attaching correlation IDs, issue keys, user context, or routing hints to your error reporting service
|
|
256
|
+
without polluting the normal log arguments.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import * as Sentry from "@sentry/node"
|
|
260
|
+
import { halua, NewTextDispatcher } from "halua"
|
|
261
|
+
|
|
262
|
+
// Human-readable logs via TextDispatcher, while still forwarding
|
|
263
|
+
// rich errorMeta (issueKey, etc.) to your error tracker.
|
|
264
|
+
let errorSink = NewTextDispatcher((line, errorMeta) => {
|
|
265
|
+
if (errorMeta?.issueKey) {
|
|
266
|
+
const err = errorMeta.error
|
|
267
|
+
// Destructure to omit the Error from `extra` (Sentry serializes it nicely on its own)
|
|
268
|
+
const { error: _err, ...context } = errorMeta
|
|
269
|
+
if (err instanceof Error) {
|
|
270
|
+
Sentry.captureException(err, {
|
|
271
|
+
level: "error",
|
|
272
|
+
tags: {
|
|
273
|
+
issueKey: errorMeta.issueKey,
|
|
274
|
+
component: errorMeta.component,
|
|
275
|
+
},
|
|
276
|
+
extra: context,
|
|
277
|
+
})
|
|
278
|
+
} else {
|
|
279
|
+
Sentry.captureMessage(line, { extra: context })
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
// Fallback: still surface the error even without extra context
|
|
283
|
+
Sentry.captureMessage(line, "error")
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
let logger = halua.create(errorSink, { level: "WARN" })
|
|
288
|
+
|
|
289
|
+
// Normal log — no meta attached (Note that .error will serialize passed string to Error)
|
|
290
|
+
logger.error("something odd happened")
|
|
291
|
+
|
|
292
|
+
// Critical path with traceable issue key — when you supply the second meta arg to .error or .assert,
|
|
293
|
+
// Halua auto-appends the normalized Error under `errorMeta.error` (in addition to your fields).
|
|
294
|
+
// The formatted line stays clean; your send fn receives the live Error + context for captureException.
|
|
295
|
+
logger.error(new Error("Payment declined"), {
|
|
296
|
+
issueKey: "PAY-48291",
|
|
297
|
+
userId: 8472,
|
|
298
|
+
component: "checkout",
|
|
299
|
+
requestId: "req_abc123",
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The user-supplied portion of `meta` is never mixed into the formatted `args` (exception: ConsoleDispatcher) — it (plus the auto-attached `error`) is always available as a clean second parameter to your send function.
|
|
304
|
+
When meta is absent, the second argument is `undefined`.
|
|
305
|
+
|
|
306
|
+
## Advanced / Custom Dispatchers
|
|
307
|
+
|
|
308
|
+
For simple file (or any sink) logging the easiest approach is to use a built-in factory with your own send function:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { halua, NewTextDispatcher } from "halua"
|
|
312
|
+
import fs from "node:fs"
|
|
313
|
+
|
|
314
|
+
let logPath = "app.log"
|
|
315
|
+
|
|
316
|
+
let fileLogger = halua.create(
|
|
317
|
+
NewTextDispatcher((line) => {
|
|
318
|
+
fs.appendFileSync(logPath, line + "\n")
|
|
319
|
+
}),
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
If you need full control (custom dispatch, different prefixing, rotation, binary framing, remote calls, etc.) extend `DispatcherBase` and use the exported `format` + `getType` (or `toJSONValue`) exactly as the built-ins do:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { halua, DispatcherBase, format, getType, toJSONValue } from "halua"
|
|
327
|
+
import fs from "node:fs"
|
|
328
|
+
|
|
329
|
+
const NewFileDispatcher = (filePath: string) => {
|
|
330
|
+
return () =>
|
|
331
|
+
new (class FileDispatcher extends DispatcherBase {
|
|
332
|
+
constructor() {
|
|
333
|
+
super((line) => {
|
|
334
|
+
fs.appendFileSync(filePath, line + "\n")
|
|
335
|
+
})
|
|
336
|
+
this.formatArg = (v) => format({ type: getType(v), value: v }, true)
|
|
337
|
+
}
|
|
338
|
+
})()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let fileLogger = halua.create(NewFileDispatcher("app.log"), { level: "INFO" })
|
|
342
|
+
fileLogger.warn("something happened", { user: 42 })
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The `Dispatcher` interface (`dispatch(meta, args)`) + `DispatcherBase` + `format`/`getType`/`toJSONValue` are the public
|
|
346
|
+
extension surface. See `src/main/dispatchers/text-dispatcher.ts`, `json-dispatcher.ts` for reference implementations.
|
|
347
|
+
|
|
348
|
+
**Semver note for custom dispatchers**: `Dispatcher`, `dispatch`, `DispatcherBase`, and the formatter trio are stable
|
|
349
|
+
within a major version. Changes that would break existing custom `Dispatcher` implementations are released only as
|
|
350
|
+
majors and recorded in `docs/dr.md`.
|
|
351
|
+
|
|
352
|
+
For most use cases the three built-in dispatchers are sufficient.
|
|
353
|
+
|
|
354
|
+
## TypeScript
|
|
355
|
+
|
|
356
|
+
Full TypeScript support included. All types are exported.
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
MIT © [inshinrei](https://github.com/inshinrei)
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
**See also:** [Tour of Halua](https://github.com/inshinrei/halua/blob/main/docs/tour-of-halua.md) for a narrative deep dive and decision records in `docs/dr.md`.
|
package/lib/AGENTS.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
> Guidelines for AI coding agents working in projects that depend on the `halua` logging library (when the package is
|
|
4
|
+
> installed from npm).
|
|
5
|
+
|
|
6
|
+
This lightweight version of the contributor guide is automatically placed into `lib/AGENTS.md` during build and shipped
|
|
7
|
+
inside the published npm package. It contains only the information relevant to **consumers** of the library.
|
|
8
|
+
|
|
9
|
+
## Logging Policy (mandatory)
|
|
10
|
+
|
|
11
|
+
All application code that uses halua **must** route its logs through a halua logger. Never call `console.log`,
|
|
12
|
+
`console.info`, `console.warn`, `console.error`, `console.debug` etc. directly for business or debug logging.
|
|
13
|
+
|
|
14
|
+
- Import the ready-to-use instance: `import { halua } from "halua"`
|
|
15
|
+
- Or create purpose-built loggers:
|
|
16
|
+
`import { halua, NewTextDispatcher, NewJSONDispatcher, NewConsoleDispatcher, NewConsoleColoredDispatcher, Level } from "halua"`
|
|
17
|
+
- Use `.error(err)` **only** for `Error` instances (or subclasses). For plain messages use a regular level or
|
|
18
|
+
`halua.error("message string")` — the implementation normalizes unknown values.
|
|
19
|
+
- Always prefer structured data as subsequent arguments: `logger.info("user action", { userId, action })`
|
|
20
|
+
|
|
21
|
+
Example (correct usage):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { halua } from "halua"
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await doSomething()
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err instanceof Error) {
|
|
30
|
+
halua.error(err, { userId: 42, route: "/checkout" }) // meta goes to error trackers via custom dispatcher
|
|
31
|
+
} else {
|
|
32
|
+
halua.warn("non-error failure", err)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Public API & Common Patterns (for AI agents)
|
|
38
|
+
|
|
39
|
+
### Core exports
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {
|
|
43
|
+
halua, // default Console-backed logger instance
|
|
44
|
+
Level, // Trace | Debug | Info | Notice | Warn | Error | Fatal
|
|
45
|
+
NewTextDispatcher,
|
|
46
|
+
NewJSONDispatcher,
|
|
47
|
+
NewConsoleDispatcher,
|
|
48
|
+
NewConsoleColoredDispatcher,
|
|
49
|
+
// advanced (for custom dispatchers)
|
|
50
|
+
DispatcherBase,
|
|
51
|
+
Dispatcher,
|
|
52
|
+
HaluaLogger,
|
|
53
|
+
ConsoleLike,
|
|
54
|
+
format,
|
|
55
|
+
getType,
|
|
56
|
+
toJSONValue,
|
|
57
|
+
redact,
|
|
58
|
+
DefaultRedactRegExp,
|
|
59
|
+
} from "halua"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Creating dedicated loggers
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
let fileLogger = halua.create(
|
|
66
|
+
NewTextDispatcher((line, errorMeta) => fs.appendFileSync("app.log", line + "\n")),
|
|
67
|
+
{ level: Level.Info, redactDataRegExp: DefaultRedactRegExp },
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
let jsonLogger = halua.create(NewJSONDispatcher(sendToElastic))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You can pass an array for multiple dispatchers on the same logger.
|
|
74
|
+
|
|
75
|
+
### Child contexts (recommended for request/trace scoping)
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
let reqLogger = halua.child("requestId", reqId, "user", userId)
|
|
79
|
+
reqLogger.info("starting work")
|
|
80
|
+
// output will contain the context pairs automatically
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Minor levels & sampling
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
logger.logTo("INFO+3", "very important event")
|
|
87
|
+
halua.create(dispatcher, { level: "DEBUG+2" })
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Redaction of secrets
|
|
91
|
+
|
|
92
|
+
Use `DefaultRedactRegExp` (covers password, token, apiKey, email, ssn, jwt, credit-card patterns, etc.) or supply your
|
|
93
|
+
own RegExp. Works for both text and JSON paths and for `errorMeta`.
|
|
94
|
+
|
|
95
|
+
### Timing with stamps
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
let end = logger.stamp("database query")
|
|
99
|
+
await db.query(...)
|
|
100
|
+
end() // logs: ... database query took 12.34ms
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Error handling contract
|
|
104
|
+
|
|
105
|
+
Halua **never** throws from any logging call. Dispatcher failures are caught and reported via a best-effort
|
|
106
|
+
`console.error` (the original error is not lost). Your send functions can safely throw; they will be isolated.
|
|
107
|
+
|
|
108
|
+
### Custom dispatchers (advanced)
|
|
109
|
+
|
|
110
|
+
For most cases the built-in factories + your own `send` callback are sufficient.
|
|
111
|
+
|
|
112
|
+
When you truly need different formatting, framing, rotation, remote transport, etc., extend `DispatcherBase`:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { halua, DispatcherBase, format, getType } from "halua"
|
|
116
|
+
import fs from "node:fs"
|
|
117
|
+
|
|
118
|
+
const NewFileDispatcher = (path: string) => () =>
|
|
119
|
+
new (class extends DispatcherBase {
|
|
120
|
+
constructor() {
|
|
121
|
+
super((line) => fs.appendFileSync(path, line + "\n"))
|
|
122
|
+
this.formatArg = (v) => format({ type: getType(v), value: v }, true)
|
|
123
|
+
}
|
|
124
|
+
})()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
See the implementations of `NewTextDispatcher` / `NewJSONDispatcher` (inside the package under `lib/` or
|
|
128
|
+
`src/main/dispatchers/`) for the exact pattern.
|
|
129
|
+
|
|
130
|
+
`Dispatcher`, `DispatcherBase`, `format`, `getType`, and `toJSONValue` are the stable public surface for extension
|
|
131
|
+
authors. Breaking changes to them are only done in major releases.
|
|
132
|
+
|
|
133
|
+
## When in doubt
|
|
134
|
+
|
|
135
|
+
- Start with the default `halua` or `halua.create(New*Dispatcher(yourSendFn))`.
|
|
136
|
+
- Use child loggers for context instead of manually prefixing strings.
|
|
137
|
+
- Let halua do the formatting — feed it rich JS values (Errors, Maps, Dates, circular structures are all handled).
|
|
138
|
+
- Read the package README.md (shipped alongside this file) for the complete reference.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
Follow the logging policy strictly. When the public API or recommended usage patterns change, the source of truth for
|
|
143
|
+
this file (`agents-for-module.md` in the repo) must be updated together with README.md and docs/tour-of-halua.md.
|