@void.01/advanced-logger 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,308 +1,327 @@
1
- # advanced-logger
2
-
3
- Self-contained TypeScript port of the VOID `core.logging` package, for Playwright projects.
4
- Source code and issues: [github.com/Aryan-Vashishth/advanced-logger](https://github.com/Aryan-Vashishth/advanced-logger).
5
- No runtime dependencies other than `@playwright/test` (optional, only needed for the `@void.01/advanced-logger/playwright` entry).
6
-
7
- ## Installation
8
-
9
- Published on npm as [`@void.01/advanced-logger`](https://www.npmjs.com/package/@void.01/advanced-logger). No registry login or token is needed, because the package is public.
10
-
11
- ```bash
12
- npm install @void.01/advanced-logger
13
- ```
14
-
15
- Pin an exact version if you want reproducible installs:
16
-
17
- ```bash
18
- npm install @void.01/advanced-logger@0.4.0
19
- ```
20
-
21
- Requirements:
22
-
23
- - TypeScript types are bundled (`dist/*.d.ts`). The compiled output targets ES2022, and it is tested on Node 24.
24
- - For the Playwright entry, `@playwright/test` >= 1.40.0 must be installed in your project (it is an optional peer dependency).
25
-
26
- Check the installed version with `npm ls @void.01/advanced-logger`, and update with `npm update @void.01/advanced-logger`.
27
-
28
- ### Entry points
29
-
30
- | Import | Use |
31
- |---|---|
32
- | `@void.01/advanced-logger` | `info`, `warn`, `error`, `debug`, `fields`, `configure`, ... for page objects and helpers |
33
- | `@void.01/advanced-logger/playwright` | Drop-in `test` / `expect` that tag each test, print a banner and attach the log on failure |
34
-
35
- ### Quick start in a Playwright project
36
-
37
- 1. Install the package (above).
38
- 2. Create `global-setup.ts` so all workers share one run id:
39
-
40
- ```ts
41
- import { initRun } from '@void.01/advanced-logger';
42
- export default async () => { initRun(); };
43
- ```
44
-
45
- 3. Reference it in `playwright.config.ts`: `globalSetup: './global-setup.ts'`.
46
- If you skip this, each worker prints a one-time `VOID_RUN_ID is not set` warning to stderr. Its files then carry their own run id, and log retention is turned off in that worker, so no worker can delete another's live files.
47
- 4. In specs, import `test` and `expect` from `@void.01/advanced-logger/playwright` instead of `@playwright/test`.
48
- 5. Run your tests. Logs are written under `./logs/<date>/` (change with `VOID_LOG_DIR`).
49
-
50
- If the caller chain (`[CHAIN]` in `full-trace`) comes out empty, set the project root explicitly. It defaults to the working directory:
51
-
52
- ```ts
53
- import { configure } from '@void.01/advanced-logger';
54
- configure({ projectRoots: [__dirname] });
55
- ```
56
-
57
- ### Uninstall or migrate from a git install
58
-
59
- ```bash
60
- npm uninstall @void.01/advanced-logger
61
- ```
62
-
63
- If you previously installed from the GitHub URL, remove that dependency entry first so the lockfile stops recording `git+ssh://` addresses, then install from npm as shown above.
64
-
65
- You can also copy `src/` into your own project (for example `src/logger/`) and import by relative path if you would rather not take a dependency.
66
-
67
- ## Usage
68
-
69
- ```ts
70
- // tests: import test/expect from the Playwright entry so every test is tagged, bannered and its log attached on failure
71
- import { test, expect } from '@void.01/advanced-logger/playwright';
72
- import { info, warn, error, fields } from '@void.01/advanced-logger';
73
-
74
- info.click('Submit');
75
- info.log('Request', fields('method', 'POST'));
76
- info.table([{ ID: 1, Name: 'Alice' }], 'Users');
77
- ```
78
-
79
- ```ts
80
- // playwright.config.ts: share one run id across workers
81
- globalSetup: './global-setup.ts' // export default async () => { initRun(); }
82
- ```
83
-
84
- ## Output
85
-
86
- | Target | Content |
87
- |---|---|
88
- | Console | Themed lines at or above the root level. Caller suffix shown only at `debug`. |
89
- | `logs/<date>/partial-trace/` | Console-visible lines, plain text |
90
- | `logs/<date>/debug-trace/` | Every level, plain text, caller suffix |
91
- | `logs/<date>/full-trace/` | Every level, plain text, `[CHAIN]` suffix |
92
- | `logs/<date>/jsonl/` | One JSON record per call (opt-in: `VOID_LOG_JSONL=1`) |
93
- | `logs/<date>/attachments/csv/` | One CSV file per logged table (on by default, see below) |
94
- | `logs/<date>/attachments/json/` | One JSON file per logged table |
95
-
96
- Files are written synchronously, one per Playwright worker (`...-w<index>.log`), so parallel workers never interleave and a crash loses nothing.
97
-
98
- ### Table attachments: CSV and JSON copies of every table
99
-
100
- Every table you log also gets a CSV and a JSON file, so you can open the data in a spreadsheet or read it from code. A table needs a title, which is shown above it and names its files:
101
-
102
- ```ts
103
- info.table(rows, 'Order lines');
104
- ```
105
-
106
- ```
107
- logs/2026-10-16/
108
- partial-trace/partial-trace-20261016-101500-123-pid4242-w1.log ← the parent log (also in debug-trace, full-trace)
109
- attachments/
110
- csv/20261016-101500-123-pid4242-w1/ ← one folder per parent log
111
- 001-users.csv
112
- 002-order-lines.csv
113
- json/20261016-101500-123-pid4242-w1/
114
- 001-users.json
115
- 002-order-lines.json
116
- ```
117
-
118
- - **One folder per parent log.** Each worker's trace files are named `<kind>-<runId>-<worker>.log`, and that worker's tables go in the folder `<runId>-<worker>` under both `csv/` and `json/`.
119
- - **File names are the number and the title.** `<NNN>-<title>`: `<NNN>` counts tables in that worker, in log order, so files sort the way they were logged. The title is lowercased and anything other than letters and digits becomes `-` (letters of any script are kept, so `Пользователи` stays readable). It is cut at 40 characters, and a title with no letters or digits becomes `table`. A number whose file already exists is skipped, never overwritten.
120
- - **The title is required.** In TypeScript, `info.table(rows)` without a title no longer compiles. From plain JavaScript (or older compiled code), it still works: the table renders without a title row, its files are named `untitled`, and a one-time warning asks for a title. `info.log(object)`, which renders a table with no title row, names its files `log`; the tables of a logged list are named `list-item-0`, `list-item-1` and so on.
121
- - **The logs point to the files.** After each table, debug-trace and full-trace get a line naming both files: `TABLE FILES │ attachments/csv/<runId>-<worker>/002-order-lines.csv | attachments/json/<runId>-<worker>/002-order-lines.json`. It is a DEBUG line, so the console and partial-trace show it only at `level: 'debug'`.
122
- - **Full values.** Cells are never cut short in the files, even when `tableCellLimit` shortens them on the console.
123
- - **Same redaction as the console.** Sensitive keys are masked at any depth, and with `redactText` so is sensitive text inside cells and the title.
124
- - **CSV:** UTF-8 with a byte-order mark (so Excel shows non-ASCII text correctly), a header row, CRLF line ends, and RFC 4180 quoting. Objects and arrays are written as JSON text. A text cell starting with `=`, `+`, `-` or `@` gets a leading `'`, so a spreadsheet does not run it as a formula (plain negative numbers are left alone).
125
- - **JSON:** `{ title, time, level, worker, runId, test, columns, rows }`. `test` is the full test path when the Playwright fixture is in use.
126
- - Written for tables at every level, like debug-trace. Empty tables and `table("text")` lines write nothing.
127
- - `maxRuns` deletes a run's attachment folders along with its logs. `maxLogFiles` does not count them.
128
- - Turn it off with `files: { tables: false }` or `VOID_LOG_TABLES=0`.
129
-
130
- Workers are identified by Playwright's `TEST_WORKER_INDEX`, one process per worker. Node `worker_threads` are not supported: a thread starts with a copy of its parent's environment, so it gets the same worker label (`main` outside Playwright) and appends to the same files as the code that started it, with nothing to tell their lines apart.
131
-
132
- ### Telling parallel console output apart: consoleWorker, consoleTest
133
-
134
- With several workers, console lines from different tests interleave. Two opt-in prefixes say where each line came from (both off by default, so the console looks the same as in 0.2.0):
135
-
136
- ```ts
137
- configure({ consoleWorker: true, consoleTest: true });
138
- // w1 │ logs in with a valid user │ 2026-10-13 16:25:06.083 │ INFO │ Click: Submit
139
- ```
140
-
141
- - `consoleWorker` adds the worker (`w<index>`, or `main` outside Playwright), the same prefix every trace-file line already has.
142
- - `consoleTest` adds the running test's own title, cut to 40 characters. It needs the Playwright fixture (`test` from `@void.01/advanced-logger/playwright`); lines logged outside a test have no test segment.
143
- - Both apply to the console only, including GitHub annotations. The trace files are already one file per worker with `TEST START` / `TEST END` around each test, and JSONL records already carry `worker` and the full `test` path.
144
-
145
- ## Configuration
146
-
147
- Environment: `VOID_LOG_LEVEL` (debug/info/warn/error/off), `VOID_LOG_DIR`, `VOID_LOG_ANSI`, `VOID_LOG_FG`, `VOID_LOG_BG`, `VOID_LOG_JSONL`, `VOID_LOG_TABLES`, `VOID_LOG_INFO_LOGS_CHAR_LIMIT`, `VOID_LOG_MAX_RUNS`, `VOID_LOG_MAX_FILES`, `VOID_LOG_RECENT_RUN_MINUTES`, `VOID_LOG_REDACT_TEXT`, `VOID_LOG_CONFIG`, `VOID_RUN_ID`, plus standard `NO_COLOR` / `FORCE_COLOR`. Boolean variables accept `1/true/yes/on` and `0/false/no/off`.
148
- Code: `configure({ level, infoLogsCharLimit, maxRuns, maxLogFiles, recentRunMinutes, consoleWorker, consoleTest, ansi, fg, bg, callerColor, tsFormat, segmentDivider, tableCellLimit, tableCellLimitEnabled, files, console, redactKeys, redactText, attach, ... })`.
149
-
150
- `configure()` checks what you pass the same way the config file is checked, but it never throws, since a logging option should not fail a test. An unknown option (such as a typo like `levl`) or a wrongly typed value prints one warning to stderr and is skipped; everything else still applies. This covers both forms: with a patch, `configure({...})`, a bad option is not applied; with a callback, `configure((cfg) => ...)`, a bad change is undone (the option goes back to its value from before the call, and an unknown one is removed). In a callback you may also assign an array to `suppressContains`, `suppressMethodPrefixes` or `includeOnlyPrefixes`; it is turned into a Set.
151
-
152
- The single theme (HIGH_CONTRAST) is on when ANSI is on: `enableAnsi()` / `disableAnsi()`.
153
-
154
- ### Config file
155
-
156
- Put a `logger.config.ts` (or `.js`, `.cjs`, `.json`) in the project root, which is `process.cwd()`. The first one found, in that order, is loaded once per process, the first time the logger is used. Every Playwright worker is its own process, so every worker reads it.
157
-
158
- ```ts
159
- // logger.config.ts
160
- import { defineConfig } from '@void.01/advanced-logger';
161
-
162
- export default defineConfig({
163
- level: 'debug',
164
- fg: true,
165
- bg: false,
166
- redactKeys: ['password', 'token', 'pin'],
167
- files: { jsonl: true },
168
- projectRoots: [__dirname],
169
- });
170
- ```
171
-
172
- The default export is either a plain object (the same shape `configure()` accepts) or a function `(config) => patch`. The function receives a copy of the built-in defaults and returns a patch. `defineConfig()` only adds typing, and its type is exactly `LogConfigPatch`.
173
-
174
- Precedence, lowest to highest:
175
-
176
- 1. Built-in defaults
177
- 2. Config file
178
- 3. Environment variables (`VOID_LOG_*`, `NO_COLOR`, `FORCE_COLOR`)
179
- 4. Explicit `configure()` calls in code
180
-
181
- Notes:
182
-
183
- - `configure()` inside Playwright's `globalSetup` does **not** reach the workers. `globalSetup` runs in the runner process, and each worker is a separate process with its own logger state. Only environment variables are inherited by workers (that is how `initRun()` shares the run id). A config file does reach them.
184
- - Set `VOID_LOG_CONFIG` to use a different path (absolute, or relative to the working directory). Unlike the default lookup, an explicit path that does not exist is an error.
185
- - No file at all is fine and changes nothing. A file that exists but throws, has the wrong shape, an unknown option name (for example a typo such as `levl`) or a wrongly typed value fails with an error that names the file. It keeps failing on every use until fixed.
186
- - TypeScript files are loaded with `require()`, so Playwright's own transpiler (or Node's type stripping) handles them. Use `.js`, `.cjs` or `.json` if your runner cannot load `.ts`. There is no runtime dependency.
187
- - An invalid `VOID_LOG_LEVEL` is ignored, so it never overrides the config file.
188
- - `NO_COLOR` and `FORCE_COLOR` count as environment variables, so they beat `ansi` in the file. Terminal auto-detection does not: the file beats it.
189
-
190
- ### Limiting how many logs are kept: maxRuns, maxLogFiles
191
-
192
- `maxRuns: 5` (or `VOID_LOG_MAX_RUNS=5`) keeps the 5 newest runs in each of `partial-trace`, `debug-trace`, `full-trace` and `jsonl`, counted across all date folders and including the current run. Older runs are deleted whole, never one worker's file at a time. A run's age is the modification time of its newest file. Date folders left empty are removed. Unset, `null` or `0` keeps everything (the default; an env value of `0` turns off a limit set in the config file).
193
-
194
- `maxLogFiles: 20` (or `VOID_LOG_MAX_FILES=20`) is the older setting and counts files instead: it keeps the 20 newest files per kind. Each run writes one file per worker, so with 4 workers it holds about 5 runs, and it can leave part of a run behind. Prefer `maxRuns`. If both are set, a file is deleted when either limit says so.
195
-
196
- Neither limit is a hard cap. These are never deleted:
197
-
198
- - The current run's files. Parallel workers share one run id, so a run with more workers than `maxLogFiles` exceeds it.
199
- - Any run with a file modified in the last `recentRunMinutes` (default 60; env `VOID_LOG_RECENT_RUN_MINUTES`). Another runner or CI shard writing to the same log folder at the same time may still be using it. Set it to `0` to protect only the current run, for example when nothing else ever writes to the folder.
200
- - Anything, in a Playwright worker without a shared run id (`initRun()` was not called). Each worker would see its siblings' live files as an older run, so retention is off in that worker and it prints a warning instead.
201
-
202
- Cleanup runs once per process, per kind, right after the first write, and is best effort: a failure never reaches your test. Only files named like this logger's output (`<kind>-*.log` / `.jsonl`) are touched, so other files in the log folder are safe.
203
-
204
- ### Redaction: redactKeys, redactText
205
-
206
- A key is sensitive when it contains one of `redactKeys` (case-insensitive substring). The defaults cover `password`, `passwd`, `secret`, `token`, `authorization`, `apikey` and `api_key`. Sensitive values print as `***` in two ways:
207
-
208
- - **Structured values**, always: in `fields(...)`, trees, rows and tables, at any depth. `fields('user', { name: 'ann', password: pw })` prints `{"name":"ann","password":"***"}`. Nested objects, arrays and Maps are all checked, a `toJSON()` result is checked too, and your object is never modified.
209
- - **Free text**, with `redactText: true` (the default; env `VOID_LOG_REDACT_TEXT`): any message, label text included. Masked forms are `key=value`, `key: value`, `"key":"value"` and `'key': 'value'` where the key contains a sensitive word, plus `Bearer <token>` and `Basic <credentials>`. The value ends at whitespace or `,` `;` `&` `"` `'` `}` `]`.
210
-
211
- ```
212
- info.log('GET /api?access_token=abc123&page=2') → GET /api?access_token=***&page=2
213
- info.log('Authorization: Bearer eyJhbGciOi...') → Authorization: Bearer ***
214
- ```
215
-
216
- Masking happens once, before anything is written, so the console, every trace file, JSONL and the Playwright attachment all get the masked text.
217
-
218
- Limits of free-text masking:
219
-
220
- - It is pattern-based, so it can miss a secret with no key next to it (`` `logged in as ann / hunter2` ``) and can mask a harmless value whose key contains a sensitive word (`tokens=3` prints `tokens=***`). Prefer passing secrets as fields, or `info.password(value)`, which prints one `*` per character.
221
- - A value with spaces is masked only up to the first space.
222
- - Set `redactText: false` to turn it off. Structured redaction stays on.
223
-
224
- ### Truncating long INFO logs: infoLogsCharLimit
225
-
226
- `infoLogsCharLimit: 200` (or `VOID_LOG_INFO_LOGS_CHAR_LIMIT=200`) caps INFO log messages at 200 characters. When one is longer, three lines are logged:
227
-
228
- ```
229
- INFO │ xxxxxxxx... (259 chars)
230
- WARN │ INFO log exceeded the 200 char threshold (259 chars). Refer to the debug logs to see the full text. To increase the limit, change infoLogsCharLimit in /path/to/logger.config.ts.
231
- DEBUG │ <the full 259 characters, no limit>
232
- ```
233
-
234
- - The WARN also says where to raise the limit: the config file's full path when the value came from the file, the env var name when it came from the environment, or the `configure()` call when it was set in code.
235
- - Leave it unset, `null` or `0` for no limit (the default). An env value of `0` turns off a limit set in the config file.
236
- - A message of exactly the limit is not truncated. The count in `(N chars)` is the original length.
237
- - Applies to INFO only, and not to `table()` output (use `tableCellLimit` for that). Action lines keep their label (`CLICK [>]`) in all three lines.
238
- - Only applies when INFO is visible at the root `level`. When it is not, nothing is truncated or warned about, and the debug and full traces already hold the full text.
239
- - The full text is in `debug-trace` and `full-trace` files (they capture every level), and on the console when `level` is `debug`. `partial-trace`, the console and the Playwright attachment get the truncated line plus the WARN.
240
-
241
- ### Colors: ansi, fg, bg, callerColor
242
-
243
- | Option | Env | Default | Controls |
244
- |---|---|---|---|
245
- | `ansi` | `VOID_LOG_ANSI` | auto-detected | Master switch. `false` means no escape codes at all, whatever the other flags say. |
246
- | `fg` | `VOID_LOG_FG` | `true` | Foreground colors: each action's own color, and the level color on plain `log` lines. Bold goes with it. |
247
- | `bg` | `VOID_LOG_BG` | `true` | The theme background. |
248
- | `callerColor` | none | `false` | Colors the caller suffix separately (console, debug level). |
249
-
250
- With `ansi: true`:
251
-
252
- | `fg` | `bg` | Console line |
253
- |---|---|---|
254
- | true | true | foreground + background (the default) |
255
- | true | false | foreground only |
256
- | false | true | background only |
257
- | false | false | no escape codes on the line body |
258
-
259
- - The background is a single color for every level. There is no per-level background; the level only picks the foreground, and only on plain `log` lines.
260
- - See [Action colors](#action-colors) below for which color each action uses.
261
- - `callerColor` is its own opt-in and is **not** gated by `fg`/`bg`. With `fg: false, bg: false, callerColor: true` the body is bare and only the caller suffix is colored. `ansi: false` silences it too.
262
- - Tables, trees, rows and every action follow the same flags, including the table title's bold. Plain-text file sinks (partial, debug and full traces, JSONL) never contain escape codes.
263
- - Runtime switches: `enableFg()` / `disableFg()` / `isFgEnabled()` and the `Bg` equivalents.
264
-
265
- ### Action colors
266
-
267
- Every action has its own foreground color, so a `click` line and an `input` line look different. Colors are grouped by what the action does, so the hue still tells you the kind of line at a glance:
268
-
269
- | Kind | Hue | Actions |
270
- |---|---|---|
271
- | Interaction | blues, from cyan-blue to indigo | `click`, `hover`, `checkbox`, `toggle`, `dropdown`, `input`, `text`, `password`, `clear`, `key`, `upload` |
272
- | Navigation | teals | `navigate`, `tab`, `frame`, `breadcrumb` |
273
- | Observation | pinks and magentas | `wait`, `search`, `result` |
274
- | Verification | gold | `verifying` |
275
- | Data | violets | `grid`, `table`, `row` |
276
- | Success | greens | `success`, `complete`, `resolved` |
277
- | Alert | reds and oranges (`skip` is muted) | `failed`, `error`, `timeout`, `validation`, `fallback`, `skip` |
278
-
279
- - Plain `log` lines keep their level's color: INFO white, WARN amber, ERROR red, DEBUG grey. `failed` uses the same red as ERROR.
280
- - Table headers are darker shades of the table's violet, one per row, so title, column names and data read as three layers: the title row is the darkest (`TABLE_TITLE_FG`), the column-name row a muted mid shade (`TABLE_COLUMNS_FG`), and borders and data rows keep the table color. A title longer than the table widens the table instead of being cut.
281
- - An action's color does not depend on the level: `warn.click()` and `info.click()` look the same, as before.
282
- - Every color has at least 4.5:1 contrast on a dark terminal background (`#1e1e1e`). Colors within one kind are at least 10 apart in CIEDE2000 color difference, and colors of different kinds at least 12, so neighbors stay distinguishable. A test enforces these limits.
283
- - The colors are 24-bit, so they need a truecolor terminal (VS Code, Windows Terminal, iTerm2 and most modern terminals). Each color is exported as `ACTION_FG`.
284
-
285
- ## Development
286
-
287
- From a clone of this repository:
288
-
289
- | Command | What it does |
290
- |---|---|
291
- | `npm test` | Unit tests (vitest), then the Playwright e2e specs |
292
- | `npm run sweep` | Manual check of everything at once: prints every console style (each action's color, levels, tables, redaction, truncation, the color toggles, two workers in parallel), writes every file kind to `./logs` (or `VOID_LOG_DIR`) like any run, without deleting anything there, then lists this run's files with a preview of each and runs automatic checks on them (only this run's files, found by its run id) (no secrets or escape codes in any file, one CSV and JSON per table, names matching the trace files). It fails if a check fails; the console part is for you to look at. |
293
-
294
- ## More documentation
295
-
296
- [ARCHITECTURE.md](ARCHITECTURE.md) describes the internals end to end: file layout, the dispatch pipeline, config layering, sinks, caller resolution and the Playwright fixture. It ships inside the npm package.
297
-
298
- [CHANGELOG.md](CHANGELOG.md) lists what changed in each version, including any change to defaults or output. It also ships in the package.
299
-
300
- ## Differences from the Java logger
301
-
302
- - Custom themes, `setTheme`, `tsWidth/levelWidth/actionWidth` and `traceArrow` are dropped (the width and arrow settings were never used by the renderer).
303
- - Caller frames from the logger's own folder are excluded by path. Java excluded frames by method-name prefix (`log`, `info`, ...), which also hides user methods such as `login()`; that is not reproduced.
304
- - No file rotation or gzip archives.
305
- - Added: secret redaction in fields/tables, JSONL sink, custom sinks (`addSink`), in-memory capture, Playwright fixture with log attachment, per-worker files, run-based retention, worker and test prefixes on the console.
1
+ # advanced-logger
2
+
3
+ Self-contained TypeScript port of the VOID `core.logging` package, for Playwright projects.
4
+ Source code and issues: [github.com/Aryan-Vashishth/advanced-logger](https://github.com/Aryan-Vashishth/advanced-logger).
5
+ No runtime dependencies other than `@playwright/test` (optional, only needed for the `@void.01/advanced-logger/playwright` entry).
6
+
7
+ ## Installation
8
+
9
+ Published on npm as [`@void.01/advanced-logger`](https://www.npmjs.com/package/@void.01/advanced-logger). No registry login or token is needed, because the package is public.
10
+
11
+ ```bash
12
+ npm install @void.01/advanced-logger
13
+ ```
14
+
15
+ Pin an exact version if you want reproducible installs:
16
+
17
+ ```bash
18
+ npm install @void.01/advanced-logger@0.4.0
19
+ ```
20
+
21
+ Requirements:
22
+
23
+ - TypeScript types are bundled (`dist/*.d.ts`). The compiled output targets ES2022, and it is tested on Node 24.
24
+ - For the Playwright entry, `@playwright/test` >= 1.40.0 must be installed in your project (it is an optional peer dependency).
25
+
26
+ Check the installed version with `npm ls @void.01/advanced-logger`, and update with `npm update @void.01/advanced-logger`.
27
+
28
+ ### Entry points
29
+
30
+ | Import | Use |
31
+ |---|---|
32
+ | `@void.01/advanced-logger` | `info`, `warn`, `error`, `debug`, `fields`, `configure`, ... for page objects and helpers |
33
+ | `@void.01/advanced-logger/playwright` | Drop-in `test` / `expect` that tag each test, print a banner and attach the log on failure |
34
+
35
+ ### Quick start in a Playwright project
36
+
37
+ 1. Install the package (above).
38
+ 2. Create `global-setup.ts` so all workers share one run id:
39
+
40
+ ```ts
41
+ import { initRun } from '@void.01/advanced-logger';
42
+ export default async () => { initRun(); };
43
+ ```
44
+
45
+ 3. Reference it in `playwright.config.ts`: `globalSetup: './global-setup.ts'`.
46
+ If you skip this, each worker prints a one-time `VOID_RUN_ID is not set` warning to stderr. Its files then carry their own run id, and log retention is turned off in that worker, so no worker can delete another's live files.
47
+ 4. In specs, import `test` and `expect` from `@void.01/advanced-logger/playwright` instead of `@playwright/test`.
48
+ 5. Run your tests. Logs are written under `./logs/<date>/` (change with `VOID_LOG_DIR`).
49
+
50
+ If the caller chain (`[CHAIN]` in `full-trace`) comes out empty, set the project root explicitly. It defaults to the working directory:
51
+
52
+ ```ts
53
+ import { configure } from '@void.01/advanced-logger';
54
+ configure({ projectRoots: [__dirname] });
55
+ ```
56
+
57
+ ### Uninstall or migrate from a git install
58
+
59
+ ```bash
60
+ npm uninstall @void.01/advanced-logger
61
+ ```
62
+
63
+ If you previously installed from the GitHub URL, remove that dependency entry first so the lockfile stops recording `git+ssh://` addresses, then install from npm as shown above.
64
+
65
+ You can also copy `src/` into your own project (for example `src/logger/`) and import by relative path if you would rather not take a dependency.
66
+
67
+ ## Usage
68
+
69
+ ```ts
70
+ // tests: import test/expect from the Playwright entry so every test is tagged, bannered and its log attached on failure
71
+ import { test, expect } from '@void.01/advanced-logger/playwright';
72
+ import { info, warn, error, fields } from '@void.01/advanced-logger';
73
+
74
+ info.click('Submit');
75
+ info.log('Request', fields('method', 'POST'));
76
+ info.table([{ ID: 1, Name: 'Alice' }], 'Users');
77
+ info.success('Order placed', { id: 42, total: '9.99' }); // heading + key/value tree
78
+ info.result('Search hits', [{ ID: 1, Name: 'Alice' }]); // heading + table
79
+ ```
80
+
81
+ Every action (`click`, `navigate`, `wait`, `verifying`, `success`, `resolved`, `failed`, `grid`, ...) takes one of:
82
+
83
+ - a single message: `info.click('Submit')`
84
+ - a heading plus fields, logged as a key/value tree: `info.failed('Save', { status: 500 })` or `info.failed('Save', 'status', 500)`
85
+ - a heading plus an array of rows, logged as the heading line and then a table, whose CSV/JSON files are named after the heading: `info.grid('Products', rows)`
86
+
87
+ Every line of a tree, and the heading line above a table, carries the action's label and color. `password(text, label?)` is the exception: it only masks a value.
88
+
89
+ ```ts
90
+ // playwright.config.ts: share one run id across workers
91
+ globalSetup: './global-setup.ts' // export default async () => { initRun(); }
92
+ ```
93
+
94
+ ## Output
95
+
96
+ | Target | Content |
97
+ |---|---|
98
+ | Console | Themed lines at or above the root level. Caller suffix shown only at `debug`. |
99
+ | `logs/<date>/partial-trace/` | Console-visible lines, plain text |
100
+ | `logs/<date>/debug-trace/` | Every level, plain text, caller suffix |
101
+ | `logs/<date>/full-trace/` | Every level, plain text, `[CHAIN]` suffix |
102
+ | `logs/<date>/jsonl/` | One JSON record per call (opt-in: `VOID_LOG_JSONL=1`) |
103
+ | `logs/<date>/attachments/csv/` | One CSV file per logged table (on by default, see below) |
104
+ | `logs/<date>/attachments/json/` | One JSON file per logged table |
105
+
106
+ Files are written synchronously, one per Playwright worker (`...-w<index>.log`), so parallel workers never interleave and a crash loses nothing.
107
+
108
+ ### Table attachments: CSV and JSON copies of every table
109
+
110
+ Every table you log also gets a CSV and a JSON file, so you can open the data in a spreadsheet or read it from code. A table needs a title, which is shown above it and names its files:
111
+
112
+ ```ts
113
+ info.table(rows, 'Order lines');
114
+ ```
115
+
116
+ ```
117
+ logs/2026-10-16/
118
+ partial-trace/PT-20261016-101500-123-pid4242-w1.log ← the parent log (also DT-... in debug-trace, FT-... in full-trace)
119
+ attachments/
120
+ csv/20261016-101500-123-pid4242-w1/ ← one folder per parent log
121
+ 001-users.csv
122
+ 002-order-lines.csv
123
+ json/20261016-101500-123-pid4242-w1/
124
+ 001-users.json
125
+ 002-order-lines.json
126
+ ```
127
+
128
+ - **One folder per parent log.** Each worker's trace files are named `<PT|DT|FT>-<runId>-<worker>.log` (`JL-...jsonl` for JSONL), and that worker's tables go in the folder `<runId>-<worker>` under both `csv/` and `json/`.
129
+ - **File names are the number and the title.** `<NNN>-<title>`: `<NNN>` counts tables in that worker, in log order, so files sort the way they were logged. The title is lowercased and anything other than letters and digits becomes `-` (letters of any script are kept, so `Пользователи` stays readable). It is cut at 40 characters, and a title with no letters or digits becomes `table`. A number whose file already exists is skipped, never overwritten.
130
+ - **The title is required.** In TypeScript, `info.table(rows)` without a title no longer compiles. From plain JavaScript (or older compiled code), it still works: the table renders without a title row, its files are named `untitled`, and a one-time warning asks for a title. `info.log(object)`, which renders a table with no title row, names its files `log`; the tables of a logged list are named `list-item-0`, `list-item-1` and so on.
131
+ - **The logs point to the files.** After each table, debug-trace and full-trace get a line naming both files: `TABLE FILES │ attachments/csv/<runId>-<worker>/002-order-lines.csv | attachments/json/<runId>-<worker>/002-order-lines.json`. It is a DEBUG line, so the console and partial-trace show it only at `level: 'debug'`.
132
+ - **Full values.** Cells are never cut short in the files, even when `tableCellLimit` shortens them on the console.
133
+ - **Same redaction as the console.** Sensitive keys are masked at any depth, and with `redactText` so is sensitive text inside cells and the title.
134
+ - **CSV:** UTF-8 with a byte-order mark (so Excel shows non-ASCII text correctly), a header row, CRLF line ends, and RFC 4180 quoting. Objects and arrays are written as JSON text. A text cell starting with `=`, `+`, `-` or `@` gets a leading `'`, so a spreadsheet does not run it as a formula (plain negative numbers are left alone).
135
+ - **JSON:** `{ title, time, level, worker, runId, test, columns, rows }`. `test` is the full test path when the Playwright fixture is in use.
136
+ - Written for tables at every level, like debug-trace. Empty tables and `table("text")` lines write nothing.
137
+ - `maxRuns` deletes a run's attachment folders along with its logs. `maxLogFiles` does not count them.
138
+ - Turn it off with `files: { tables: false }` or `VOID_LOG_TABLES=0`.
139
+
140
+ Workers are identified by Playwright's `TEST_WORKER_INDEX`, one process per worker. Node `worker_threads` are not supported: a thread starts with a copy of its parent's environment, so it gets the same worker label (`main` outside Playwright) and appends to the same files as the code that started it, with nothing to tell their lines apart.
141
+
142
+ ### Telling parallel console output apart: consoleWorker, consoleTest
143
+
144
+ With several workers, console lines from different tests interleave. Two opt-in prefixes say where each line came from (both off by default, so the console looks the same as in 0.2.0):
145
+
146
+ ```ts
147
+ configure({ consoleWorker: true, consoleTest: true });
148
+ // w1 │ logs in with a valid user │ 2026-10-13 16:25:06.083 │ INFO │ Click: Submit
149
+ ```
150
+
151
+ - `consoleWorker` adds the worker (`w<index>`, or `main` outside Playwright), the same prefix every trace-file line already has.
152
+ - `consoleTest` adds the running test's own title, cut to 40 characters. It needs the Playwright fixture (`test` from `@void.01/advanced-logger/playwright`); lines logged outside a test have no test segment.
153
+ - Both apply to the console only, including GitHub annotations. The trace files are already one file per worker with `TEST START` / `TEST END` around each test, and JSONL records already carry `worker` and the full `test` path.
154
+
155
+ ## Configuration
156
+
157
+ Environment: `VOID_LOG_LEVEL` (debug/info/warn/error/off), `VOID_LOG_DIR`, `VOID_LOG_ANSI`, `VOID_LOG_FG`, `VOID_LOG_BG`, `VOID_LOG_JSONL`, `VOID_LOG_TABLES`, `VOID_LOG_INFO_LOGS_CHAR_LIMIT`, `VOID_LOG_MAX_RUNS`, `VOID_LOG_MAX_FILES`, `VOID_LOG_RECENT_RUN_MINUTES`, `VOID_LOG_REDACT_TEXT`, `VOID_LOG_CONFIG`, `VOID_RUN_ID`, plus standard `NO_COLOR` / `FORCE_COLOR`. Boolean variables accept `1/true/yes/on` and `0/false/no/off`.
158
+ Code: `configure({ level, infoLogsCharLimit, maxRuns, maxLogFiles, recentRunMinutes, consoleWorker, consoleTest, ansi, fg, bg, callerColor, tsFormat, segmentDivider, tableCellLimit, tableCellLimitEnabled, files, console, redactKeys, redactText, attach, ... })`.
159
+
160
+ `configure()` checks what you pass the same way the config file is checked, but it never throws, since a logging option should not fail a test. An unknown option (such as a typo like `levl`) or a wrongly typed value prints one warning to stderr and is skipped; everything else still applies. This covers both forms: with a patch, `configure({...})`, a bad option is not applied; with a callback, `configure((cfg) => ...)`, a bad change is undone (the option goes back to its value from before the call, and an unknown one is removed). In a callback you may also assign an array to `suppressContains`, `suppressMethodPrefixes` or `includeOnlyPrefixes`; it is turned into a Set.
161
+
162
+ The single theme (HIGH_CONTRAST) is on when ANSI is on: `enableAnsi()` / `disableAnsi()`.
163
+
164
+ ### Config file
165
+
166
+ Put a `logger.config.ts` (or `.js`, `.cjs`, `.json`) in the project root, which is `process.cwd()`. The first one found, in that order, is loaded once per process, the first time the logger is used. Every Playwright worker is its own process, so every worker reads it.
167
+
168
+ ```ts
169
+ // logger.config.ts
170
+ import { defineConfig } from '@void.01/advanced-logger';
171
+
172
+ export default defineConfig({
173
+ level: 'debug',
174
+ fg: true,
175
+ bg: false,
176
+ redactKeys: ['password', 'token', 'pin'],
177
+ files: { jsonl: true },
178
+ projectRoots: [__dirname],
179
+ });
180
+ ```
181
+
182
+ The default export is either a plain object (the same shape `configure()` accepts) or a function `(config) => patch`. The function receives a copy of the built-in defaults and returns a patch. `defineConfig()` only adds typing, and its type is exactly `LogConfigPatch`.
183
+
184
+ Precedence, lowest to highest:
185
+
186
+ 1. Built-in defaults
187
+ 2. Config file
188
+ 3. Environment variables (`VOID_LOG_*`, `NO_COLOR`, `FORCE_COLOR`)
189
+ 4. Explicit `configure()` calls in code
190
+
191
+ Notes:
192
+
193
+ - `configure()` inside Playwright's `globalSetup` does **not** reach the workers. `globalSetup` runs in the runner process, and each worker is a separate process with its own logger state. Only environment variables are inherited by workers (that is how `initRun()` shares the run id). A config file does reach them.
194
+ - Set `VOID_LOG_CONFIG` to use a different path (absolute, or relative to the working directory). Unlike the default lookup, an explicit path that does not exist is an error.
195
+ - No file at all is fine and changes nothing. A file that exists but throws, has the wrong shape, an unknown option name (for example a typo such as `levl`) or a wrongly typed value fails with an error that names the file. It keeps failing on every use until fixed.
196
+ - TypeScript files are loaded with `require()`, so Playwright's own transpiler (or Node's type stripping) handles them. Use `.js`, `.cjs` or `.json` if your runner cannot load `.ts`. There is no runtime dependency.
197
+ - An invalid `VOID_LOG_LEVEL` is ignored, so it never overrides the config file.
198
+ - `NO_COLOR` and `FORCE_COLOR` count as environment variables, so they beat `ansi` in the file. Terminal auto-detection does not: the file beats it.
199
+
200
+ ### Limiting how many logs are kept: maxRuns, maxLogFiles
201
+
202
+ `maxRuns: 5` (or `VOID_LOG_MAX_RUNS=5`) keeps the 5 newest runs in each of `partial-trace`, `debug-trace`, `full-trace` and `jsonl`, counted across all date folders and including the current run. Older runs are deleted whole, never one worker's file at a time. A run's age is the modification time of its newest file. Date folders left empty are removed. Unset, `null` or `0` keeps everything (the default; an env value of `0` turns off a limit set in the config file).
203
+
204
+ `maxLogFiles: 20` (or `VOID_LOG_MAX_FILES=20`) is the older setting and counts files instead: it keeps the 20 newest files per kind. Each run writes one file per worker, so with 4 workers it holds about 5 runs, and it can leave part of a run behind. Prefer `maxRuns`. If both are set, a file is deleted when either limit says so.
205
+
206
+ Neither limit is a hard cap. These are never deleted:
207
+
208
+ - The current run's files. Parallel workers share one run id, so a run with more workers than `maxLogFiles` exceeds it.
209
+ - Any run with a file modified in the last `recentRunMinutes` (default 60; env `VOID_LOG_RECENT_RUN_MINUTES`). Another runner or CI shard writing to the same log folder at the same time may still be using it. Set it to `0` to protect only the current run, for example when nothing else ever writes to the folder.
210
+ - Anything, in a Playwright worker without a shared run id (`initRun()` was not called). Each worker would see its siblings' live files as an older run, so retention is off in that worker and it prints a warning instead.
211
+
212
+ Cleanup runs once per process, per kind, right after the first write, and is best effort: a failure never reaches your test. Only files named like this logger's output (`PT-`, `DT-`, `FT-`, `JL-`, or the pre-0.5.0 `<kind>-` names, ending `.log` / `.jsonl`) are touched, so other files in the log folder are safe.
213
+
214
+ ### Redaction: redactKeys, redactText
215
+
216
+ A key is sensitive when it contains one of `redactKeys` (case-insensitive substring). The defaults cover `password`, `passwd`, `secret`, `token`, `authorization`, `apikey` and `api_key`. Sensitive values print as `***` in two ways:
217
+
218
+ - **Structured values**, always: in `fields(...)`, trees, rows and tables, at any depth. `fields('user', { name: 'ann', password: pw })` prints `{"name":"ann","password":"***"}`. Nested objects, arrays and Maps are all checked, a `toJSON()` result is checked too, and your object is never modified.
219
+ - **Free text**, with `redactText: true` (the default; env `VOID_LOG_REDACT_TEXT`): any message, label text included. Masked forms are `key=value`, `key: value`, `"key":"value"` and `'key': 'value'` where the key contains a sensitive word, plus `Bearer <token>` and `Basic <credentials>`. The value ends at whitespace or `,` `;` `&` `"` `'` `}` `]`.
220
+
221
+ ```
222
+ info.log('GET /api?access_token=abc123&page=2') → GET /api?access_token=***&page=2
223
+ info.log('Authorization: Bearer eyJhbGciOi...') → Authorization: Bearer ***
224
+ ```
225
+
226
+ Masking happens once, before anything is written, so the console, every trace file, JSONL and the Playwright attachment all get the masked text.
227
+
228
+ Limits of free-text masking:
229
+
230
+ - It is pattern-based, so it can miss a secret with no key next to it (`` `logged in as ann / hunter2` ``) and can mask a harmless value whose key contains a sensitive word (`tokens=3` prints `tokens=***`). Prefer passing secrets as fields, or `info.password(value)`, which prints one `*` per character.
231
+ - A value with spaces is masked only up to the first space.
232
+ - Set `redactText: false` to turn it off. Structured redaction stays on.
233
+
234
+ ### Truncating long INFO logs: infoLogsCharLimit
235
+
236
+ `infoLogsCharLimit: 200` (or `VOID_LOG_INFO_LOGS_CHAR_LIMIT=200`) caps INFO log messages at 200 characters. When one is longer, the console, `full-trace`, JSONL and the Playwright attachment get three lines per log:
237
+
238
+ ```
239
+ INFO │ xxxxxxxx... (259 chars)
240
+ WARN │ INFO log exceeded the 200 char threshold (259 chars). Refer to the debug logs to see the full text. To increase the limit, change infoLogsCharLimit in /path/to/logger.config.ts.
241
+ DEBUG │ <the full 259 characters, no limit>
242
+ ```
243
+
244
+ `partial-trace` and `debug-trace` instead get one general WARN, before the first cut line in the file, and no per-log WARN:
245
+
246
+ ```
247
+ WARN │ Some INFO logs exceed the 200 char limit. Any INFO log longer than 200 chars is truncated on the console and in partial-trace. Refer to debug-trace for the full text. To increase the limit, ...
248
+ INFO │ xxxxxxxx... (259 chars) <- partial-trace
249
+ INFO │ <the full 259 characters> <- debug-trace: the log itself, untruncated, with no DEBUG copy
250
+ ```
251
+
252
+ - Both WARNs also say where to raise the limit: the config file's full path when the value came from the file, the env var name when it came from the environment, or the `configure()` call when it was set in code.
253
+ - Leave it unset, `null` or `0` for no limit (the default). An env value of `0` turns off a limit set in the config file.
254
+ - A message of exactly the limit is not truncated. The count in `(N chars)` is the original length.
255
+ - Applies to INFO only, and not to `table()` output (use `tableCellLimit` for that). Action lines keep their label (`CLICK [>]`) in all three lines.
256
+ - Only applies when INFO is visible at the root `level`. When it is not, nothing is truncated or warned about, and the debug and full traces already hold the full text.
257
+ - The full text is in `debug-trace` and `full-trace` files (they capture every level), and on the console when `level` is `debug`.
258
+ - The general WARN is written once per file. If the limit changes during a run, it is written again with the new number.
259
+
260
+ ### Colors: ansi, fg, bg, callerColor
261
+
262
+ | Option | Env | Default | Controls |
263
+ |---|---|---|---|
264
+ | `ansi` | `VOID_LOG_ANSI` | auto-detected | Master switch. `false` means no escape codes at all, whatever the other flags say. |
265
+ | `fg` | `VOID_LOG_FG` | `true` | Foreground colors: each action's own color, and the level color on plain `log` lines. Bold goes with it. |
266
+ | `bg` | `VOID_LOG_BG` | `true` | The theme background. |
267
+ | `callerColor` | none | `false` | Colors the caller suffix separately (console, debug level). |
268
+
269
+ With `ansi: true`:
270
+
271
+ | `fg` | `bg` | Console line |
272
+ |---|---|---|
273
+ | true | true | foreground + background (the default) |
274
+ | true | false | foreground only |
275
+ | false | true | background only |
276
+ | false | false | no escape codes on the line body |
277
+
278
+ - The background is a single color for every level. There is no per-level background; the level only picks the foreground, and only on plain `log` lines.
279
+ - See [Action colors](#action-colors) below for which color each action uses.
280
+ - `callerColor` is its own opt-in and is **not** gated by `fg`/`bg`. With `fg: false, bg: false, callerColor: true` the body is bare and only the caller suffix is colored. `ansi: false` silences it too.
281
+ - Tables, trees, rows and every action follow the same flags, including the table title's bold. Plain-text file sinks (partial, debug and full traces, JSONL) never contain escape codes.
282
+ - Runtime switches: `enableFg()` / `disableFg()` / `isFgEnabled()` and the `Bg` equivalents.
283
+
284
+ ### Action colors
285
+
286
+ Every action has its own foreground color, so a `click` line and an `input` line look different. Colors are grouped by what the action does, so the hue still tells you the kind of line at a glance:
287
+
288
+ | Kind | Hue | Actions |
289
+ |---|---|---|
290
+ | Interaction | blues, from cyan-blue to indigo | `click`, `hover`, `checkbox`, `toggle`, `dropdown`, `input`, `text`, `password`, `clear`, `key`, `upload` |
291
+ | Navigation | teals | `navigate`, `tab`, `frame`, `breadcrumb` |
292
+ | Observation | pinks and magentas | `wait`, `search`, `result` |
293
+ | Verification | gold | `verifying` |
294
+ | Data | violets | `grid`, `table`, `row` |
295
+ | Success | greens | `success`, `complete`, `resolved` |
296
+ | Alert | reds and oranges (`skip` is muted) | `failed`, `error`, `timeout`, `validation`, `fallback`, `skip` |
297
+
298
+ - Plain `log` lines keep their level's color: INFO white, WARN amber, ERROR red, DEBUG grey. `failed` uses the same red as ERROR.
299
+ - Table headers are darker shades of the table's violet, one per row, so title, column names and data read as three layers: the title row is the darkest (`TABLE_TITLE_FG`), the column-name row a muted mid shade (`TABLE_COLUMNS_FG`), and borders and data rows keep the table color. A title longer than the table widens the table instead of being cut.
300
+ - An action's color does not depend on the level: `warn.click()` and `info.click()` look the same, as before.
301
+ - Every color has at least 4.5:1 contrast on a dark terminal background (`#1e1e1e`). Colors within one kind are at least 10 apart in CIEDE2000 color difference, and colors of different kinds at least 12, so neighbors stay distinguishable. A test enforces these limits.
302
+ - The colors are 24-bit, so they need a truecolor terminal (VS Code, Windows Terminal, iTerm2 and most modern terminals). Each color is exported as `ACTION_FG`.
303
+
304
+ ## Development
305
+
306
+ From a clone of this repository:
307
+
308
+ | Command | What it does |
309
+ |---|---|
310
+ | `npm test` | Unit tests (vitest), then the Playwright e2e specs |
311
+ | `npm run sweep` | Manual check of everything at once: prints every console style (each action's color, levels, tables, redaction, truncation, the color toggles, two workers in parallel), writes every file kind to `./logs` (or `VOID_LOG_DIR`) like any run, without deleting anything there, then lists this run's files with a preview of each and runs automatic checks on them (only this run's files, found by its run id) (no secrets or escape codes in any file, one CSV and JSON per table, names matching the trace files). It fails if a check fails; the console part is for you to look at. |
312
+
313
+ ## More documentation
314
+
315
+ [ARCHITECTURE.md](ARCHITECTURE.md) describes the internals end to end: file layout, the dispatch pipeline, config layering, sinks, caller resolution and the Playwright fixture. It ships inside the npm package.
316
+
317
+ [CHANGELOG.md](CHANGELOG.md) lists what changed in each version, including any change to defaults or output. It also ships in the package.
318
+
319
+ ## Differences from the Java logger
320
+
321
+ - Custom themes, `setTheme`, `tsWidth/levelWidth/actionWidth` and `traceArrow` are dropped (the width and arrow settings were never used by the renderer).
322
+ - Caller frames from the logger's own folder are excluded by path. Java excluded frames by method-name prefix (`log`, `info`, ...), which also hides user methods such as `login()`; that is not reproduced.
323
+ - No file rotation or gzip archives.
324
+ - Added: secret redaction in fields/tables, JSONL sink, custom sinks (`addSink`), in-memory capture, Playwright fixture with log attachment, per-worker files, run-based retention, worker and test prefixes on the console.
306
325
 
307
326
  ## License
308
327