logquill 0.1.0 → 0.1.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 CHANGED
@@ -2,15 +2,30 @@
2
2
 
3
3
  [![CI](https://github.com/nikhilvdev/logquill-js/actions/workflows/ci.yml/badge.svg)](https://github.com/nikhilvdev/logquill-js/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/logquill.svg)](https://www.npmjs.com/package/logquill)
5
+ [![npm downloads](https://img.shields.io/npm/dm/logquill.svg)](https://www.npmjs.com/package/logquill)
5
6
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
6
7
 
7
8
  A logging framework for Node/TypeScript that shares one mental model and one
8
9
  JSON log shape with its Python sibling, [`logquill`](https://pypi.org/project/logquill/)
9
10
  (repo: `logquill-python`).
10
11
 
11
- > **Status:** Phase 0 (repo & build tooling) is scaffolded. The public
12
- > logging API — `Logger`, levels, transports, formatters, plugins lands in
13
- > the phases that follow.
12
+ Status: pre-release, under active development. The core `Logger`, level
13
+ filtering, the plugin pipeline (with `ContextPlugin`/`RedactPlugin`/
14
+ `SamplingPlugin` built in), `JSONFormatter`, and the built-in transports are
15
+ implemented; non-blocking async dispatch is not yet — see `CHANGELOG.md`
16
+ for what's landed so far.
17
+
18
+ ## Features
19
+
20
+ - **Structured by default** — every call carries a `meta` object, not just a message string
21
+ - **Cross-language record shape** — identical JSON shape and level names/weights as [`logquill` on PyPI](https://pypi.org/project/logquill/)
22
+ - **Pluggable formatters** — `JSONFormatter` out of the box; implement `format(record) -> string` for your own
23
+ - **Pluggable transports** — `ConsoleTransport` (colorized, respects `NO_COLOR`, isomorphic), `FileTransport` (rotation), `HTTPTransport` (batched, `fetch`-based); write your own by subclassing `Transport`; `CollectingTransport` ships as an in-memory sink, handy for tests
24
+ - **Plugin pipeline** — `ContextPlugin`, `RedactPlugin`, `SamplingPlugin` out of the box; `beforeLog`/`afterLog`/`onError` hooks; a throwing plugin can't crash logging
25
+ - **Child loggers** — `.child()` inherits level, transports, and plugins, and merges its own `meta` on top
26
+ - **Typed throughout** — TypeScript strict mode, no `any` in the public API
27
+ - **Dual package** — works via both `require()` (CJS) and `import` (ESM) from the same published package
28
+ - *(planned)* non-blocking async dispatch, `AsyncLocalStorage`-based context propagation — see `CHANGELOG.md`
14
29
 
15
30
  ## Install
16
31
 
@@ -21,14 +36,122 @@ npm install logquill
21
36
  ## Usage
22
37
 
23
38
  ```ts
24
- import { VERSION } from "logquill";
39
+ import { Level, Logger } from "logquill";
40
+
41
+ const logger = new Logger("app", { level: Level.INFO });
42
+
43
+ const record = logger.info("user signed up", { user_id: 42, plan: "pro" });
44
+ console.log(record);
45
+ // { timestamp: '2026-08-29T07:12:55.968Z', level: 'INFO', logger: 'app',
46
+ // message: 'user signed up', meta: { user_id: 42, plan: 'pro' } }
47
+
48
+ logger.debug("below threshold, dropped"); // -> null, filtered by level
49
+ logger.setLevel("debug");
50
+ logger.debug("now visible"); // -> a record
51
+ ```
52
+
53
+ Every log call returns the record (or `null` if filtered by level) —
54
+ `{"timestamp": ISO8601, "level": string, "logger": string, "message": string, "meta": object}`,
55
+ the same shape shared with [`logquill` on PyPI](https://pypi.org/project/logquill/).
56
+ Use `JSONFormatter` to serialize a record to the canonical JSON line:
57
+
58
+ ```ts
59
+ import { JSONFormatter } from "logquill";
60
+
61
+ console.log(new JSONFormatter().format(record));
62
+ // '{"timestamp":"2026-08-29T07:12:55.968Z","level":"INFO","logger":"app","message":"user signed up","meta":{"user_id":42,"plan":"pro"}}'
63
+ ```
64
+
65
+ `.child()` creates a logger scoped under this one, inheriting its level,
66
+ transports, and plugins, and merging its own `meta` on top:
67
+
68
+ ```ts
69
+ const dbLogger = logger.child("db", { component: "pool" });
70
+ dbLogger.warn("connection lost");
71
+ // logger: "app.db", meta: { component: "pool" }
72
+ ```
25
73
 
26
- console.log(VERSION);
74
+ `.use()` registers a plugin implementing `beforeLog`/`afterLog`/`onError`
75
+ hooks (all optional; a throwing hook is caught and routed to `onError`
76
+ rather than crashing logging):
77
+
78
+ ```ts
79
+ logger.use({
80
+ beforeLog(record) {
81
+ return { ...record, message: record.message.toUpperCase() };
82
+ },
83
+ });
27
84
  ```
28
85
 
29
86
  Works from both ESM (`import`) and CommonJS (`require`) — the package ships
30
87
  a dual build with full TypeScript types.
31
88
 
89
+ ## Transports
90
+
91
+ Attach transports to a `Logger` to actually write records somewhere. Each
92
+ record is dispatched to every attached transport synchronously
93
+ (non-blocking dispatch isn't implemented yet):
94
+
95
+ ```ts
96
+ import { ConsoleTransport, FileTransport, HTTPTransport, Logger } from "logquill";
97
+
98
+ const logger = new Logger("app", {
99
+ transports: [
100
+ new ConsoleTransport(), // console.log; ERROR/FATAL to console.error, colorized
101
+ new FileTransport("app.log", { maxBytes: 10 * 1024 * 1024, backupCount: 5 }),
102
+ new HTTPTransport("https://logs.example.com/ingest", { batchSize: 50 }),
103
+ ],
104
+ });
105
+
106
+ logger.info("user signed up", { user_id: 42 });
107
+ logger.close(); // flushes the HTTPTransport's pending batch, closes the FileTransport's fd
108
+ ```
109
+
110
+ `ConsoleTransport` writes via `console.log`/`console.error` (not
111
+ `process.stdout`/`stderr`), so it works unmodified in a browser bundle.
112
+ Colorizing defaults to on, unless the [`NO_COLOR`](https://no-color.org)
113
+ environment variable is set. `FileTransport` rotates the file once it
114
+ exceeds `maxBytes`, keeping `backupCount` numbered backups (`.1`, `.2`, …).
115
+ `HTTPTransport` batches formatted lines and POSTs them as
116
+ newline-delimited JSON once `batchSize` is reached, or on `.close()`/
117
+ `.flush()`; pass `sender` to swap in a fake for tests or a different
118
+ backend.
119
+
120
+ Write your own by subclassing `Transport` (implement `write(formatted,
121
+ record)`; `format()` and `close()` have defaults), or use
122
+ `CollectingTransport` — an in-memory sink included for tests:
123
+
124
+ ```ts
125
+ import { CollectingTransport } from "logquill";
126
+
127
+ const sink = new CollectingTransport();
128
+ const logger2 = new Logger("app", { transports: [sink] });
129
+ logger2.info("hello");
130
+ console.log(sink.formatted); // ['{"timestamp":...,"message":"hello",...}']
131
+ ```
132
+
133
+ ## Plugins
134
+
135
+ Plugins hook into the pipeline around each log call: `beforeLog(record)` can
136
+ transform a record or return `null` to drop it, `afterLog(record)` runs once
137
+ it's been dispatched to every transport, and `onError(error, record)` catches
138
+ anything a plugin's own hooks throw — a broken plugin can't take down logging.
139
+
140
+ ```ts
141
+ import { ContextPlugin, Logger, RedactPlugin, SamplingPlugin } from "logquill";
142
+
143
+ const logger = new Logger("app");
144
+ logger.use(new ContextPlugin({ service: "api", env: "prod" })); // merged into every record's meta
145
+ logger.use(new RedactPlugin({ keys: ["password", "token"] })); // replaces matching meta values
146
+ logger.use(new SamplingPlugin(0.1)); // keep ~10% of records that reach this point
147
+
148
+ logger.info("login attempt", { user_id: 42, password: "hunter2" });
149
+ // meta: { service: "api", env: "prod", user_id: 42, password: "***" }
150
+ // (unless this call was one of the ~90% sampling dropped, in which case it's null)
151
+ ```
152
+
153
+ Write your own by implementing `Plugin`; every hook is optional.
154
+
32
155
  ## Development
33
156
 
34
157
  ```sh
@@ -39,6 +162,10 @@ npm run typecheck # tsc --noEmit
39
162
  npm run coverage # vitest run --coverage
40
163
  ```
41
164
 
165
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the PR workflow, the
166
+ [Code of Conduct](CODE_OF_CONDUCT.md) for community standards, and
167
+ [SECURITY.md](.github/SECURITY.md) for how to report a vulnerability.
168
+
42
169
  ## License
43
170
 
44
171
  MIT
package/dist/index.cjs CHANGED
@@ -1,8 +1,390 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ // src/levels.ts
7
+ var Level = /* @__PURE__ */ ((Level2) => {
8
+ Level2[Level2["TRACE"] = 5] = "TRACE";
9
+ Level2[Level2["DEBUG"] = 10] = "DEBUG";
10
+ Level2[Level2["INFO"] = 20] = "INFO";
11
+ Level2[Level2["WARN"] = 30] = "WARN";
12
+ Level2[Level2["ERROR"] = 40] = "ERROR";
13
+ Level2[Level2["FATAL"] = 50] = "FATAL";
14
+ return Level2;
15
+ })(Level || {});
16
+ var NAME_TO_LEVEL = {
17
+ TRACE: 5 /* TRACE */,
18
+ DEBUG: 10 /* DEBUG */,
19
+ INFO: 20 /* INFO */,
20
+ WARN: 30 /* WARN */,
21
+ ERROR: 40 /* ERROR */,
22
+ FATAL: 50 /* FATAL */
23
+ };
24
+ function levelName(level) {
25
+ return Level[level];
26
+ }
27
+ function parseLevel(level) {
28
+ if (typeof level === "string") {
29
+ const parsed = NAME_TO_LEVEL[level.toUpperCase()];
30
+ if (parsed === void 0) {
31
+ throw new Error(`Unknown log level: ${level}`);
32
+ }
33
+ return parsed;
34
+ }
35
+ if (Level[level] === void 0) {
36
+ throw new Error(`Unknown log level: ${String(level)}`);
37
+ }
38
+ return level;
39
+ }
40
+
41
+ // src/records.ts
42
+ function utcTimestamp() {
43
+ return (/* @__PURE__ */ new Date()).toISOString();
44
+ }
45
+ function createRecord(params) {
46
+ return {
47
+ timestamp: utcTimestamp(),
48
+ level: levelName(params.level),
49
+ logger: params.logger,
50
+ message: params.message,
51
+ meta: params.meta
52
+ };
53
+ }
54
+
55
+ // src/formatter.ts
56
+ var JSONFormatter = class {
57
+ format(record) {
58
+ return JSON.stringify(record);
59
+ }
60
+ };
61
+
62
+ // src/context-plugin.ts
63
+ var ContextPlugin = class {
64
+ context;
65
+ constructor(context) {
66
+ this.context = context;
67
+ }
68
+ beforeLog(record) {
69
+ return { ...record, meta: { ...this.context, ...record.meta } };
70
+ }
71
+ };
72
+
73
+ // src/redact-plugin.ts
74
+ var DEFAULT_REDACTED_KEYS = ["password", "token", "secret", "api_key", "authorization"];
75
+ var RedactPlugin = class {
76
+ keys;
77
+ replacement;
78
+ constructor(options = {}) {
79
+ this.keys = new Set((options.keys ?? DEFAULT_REDACTED_KEYS).map((key) => key.toLowerCase()));
80
+ this.replacement = options.replacement ?? "***";
81
+ }
82
+ beforeLog(record) {
83
+ const meta = {};
84
+ for (const [key, value] of Object.entries(record.meta)) {
85
+ meta[key] = this.keys.has(key.toLowerCase()) ? this.replacement : value;
86
+ }
87
+ return { ...record, meta };
88
+ }
89
+ };
90
+
91
+ // src/sampling-plugin.ts
92
+ var SamplingPlugin = class {
93
+ rate;
94
+ rng;
95
+ constructor(rate, options = {}) {
96
+ if (rate < 0 || rate > 1) {
97
+ throw new Error(`rate must be between 0 and 1, got ${String(rate)}`);
98
+ }
99
+ this.rate = rate;
100
+ this.rng = options.rng ?? Math.random;
101
+ }
102
+ beforeLog(record) {
103
+ return this.rng() < this.rate ? record : null;
104
+ }
105
+ };
106
+
107
+ // src/transport.ts
108
+ var Transport = class {
109
+ formatter;
110
+ constructor(formatter = new JSONFormatter()) {
111
+ this.formatter = formatter;
112
+ }
113
+ format(record) {
114
+ return this.formatter.format(record);
115
+ }
116
+ /** Flush/release resources on shutdown. No-op unless a transport overrides it. */
117
+ close() {
118
+ }
119
+ };
120
+ var CollectingTransport = class extends Transport {
121
+ formatted = [];
122
+ records = [];
123
+ closed = false;
124
+ write(formatted, record) {
125
+ this.formatted.push(formatted);
126
+ this.records.push(record);
127
+ }
128
+ close() {
129
+ this.closed = true;
130
+ }
131
+ };
132
+
133
+ // src/console-transport.ts
134
+ var COLORS = {
135
+ [5 /* TRACE */]: "\x1B[90m",
136
+ // gray
137
+ [10 /* DEBUG */]: "\x1B[36m",
138
+ // cyan
139
+ [20 /* INFO */]: "\x1B[32m",
140
+ // green
141
+ [30 /* WARN */]: "\x1B[33m",
142
+ // yellow
143
+ [40 /* ERROR */]: "\x1B[31m",
144
+ // red
145
+ [50 /* FATAL */]: "\x1B[35m"
146
+ // magenta
147
+ };
148
+ var RESET = "\x1B[0m";
149
+ function defaultColorize() {
150
+ const env = typeof process === "undefined" ? void 0 : process.env;
151
+ return env?.NO_COLOR === void 0;
152
+ }
153
+ var ConsoleTransport = class extends Transport {
154
+ colorize;
155
+ out;
156
+ constructor(options = {}) {
157
+ super(options.formatter);
158
+ this.colorize = options.colorize ?? defaultColorize();
159
+ this.out = options.console ?? console;
160
+ }
161
+ write(formatted, record) {
162
+ const level = parseLevel(record.level);
163
+ const line = this.colorize ? this.applyColor(formatted, level) : formatted;
164
+ if (level >= 40 /* ERROR */) {
165
+ this.out.error(line);
166
+ } else {
167
+ this.out.log(line);
168
+ }
169
+ }
170
+ applyColor(formatted, level) {
171
+ return `${COLORS[level]}${formatted}${RESET}`;
172
+ }
173
+ };
174
+ var FileTransport = class extends Transport {
175
+ path;
176
+ maxBytes;
177
+ backupCount;
178
+ fd;
179
+ constructor(path$1, options = {}) {
180
+ super(options.formatter);
181
+ this.path = path$1;
182
+ this.maxBytes = options.maxBytes ?? 10 * 1024 * 1024;
183
+ this.backupCount = options.backupCount ?? 5;
184
+ fs.mkdirSync(path.dirname(path$1), { recursive: true });
185
+ this.fd = fs.openSync(this.path, "a");
186
+ }
187
+ write(formatted) {
188
+ fs.writeSync(this.fd, formatted + "\n");
189
+ if (this.maxBytes > 0 && fs.fstatSync(this.fd).size >= this.maxBytes) {
190
+ this.rotate();
191
+ }
192
+ }
193
+ rotate() {
194
+ fs.closeSync(this.fd);
195
+ if (this.backupCount > 0) {
196
+ for (let index = this.backupCount - 1; index > 0; index--) {
197
+ const src = `${this.path}.${String(index)}`;
198
+ const dst = `${this.path}.${String(index + 1)}`;
199
+ if (fs.existsSync(src)) {
200
+ if (fs.existsSync(dst)) {
201
+ fs.unlinkSync(dst);
202
+ }
203
+ fs.renameSync(src, dst);
204
+ }
205
+ }
206
+ const backup = `${this.path}.1`;
207
+ if (fs.existsSync(backup)) {
208
+ fs.unlinkSync(backup);
209
+ }
210
+ fs.renameSync(this.path, backup);
211
+ } else {
212
+ fs.unlinkSync(this.path);
213
+ }
214
+ this.fd = fs.openSync(this.path, "a");
215
+ }
216
+ close() {
217
+ fs.closeSync(this.fd);
218
+ }
219
+ };
220
+
221
+ // src/http-transport.ts
222
+ async function fetchSender(url, batch) {
223
+ const response = await fetch(url, {
224
+ method: "POST",
225
+ headers: { "Content-Type": "application/x-ndjson" },
226
+ body: batch.join("\n")
227
+ });
228
+ if (!response.ok) {
229
+ throw new Error(`HTTPTransport: request to ${url} failed with status ${String(response.status)}`);
230
+ }
231
+ }
232
+ var HTTPTransport = class extends Transport {
233
+ url;
234
+ batchSize;
235
+ sender;
236
+ batch = [];
237
+ constructor(url, options = {}) {
238
+ super(options.formatter);
239
+ this.url = url;
240
+ this.batchSize = options.batchSize ?? 50;
241
+ this.sender = options.sender ?? fetchSender;
242
+ }
243
+ write(formatted) {
244
+ this.batch.push(formatted);
245
+ if (this.batch.length >= this.batchSize) {
246
+ this.flush();
247
+ }
248
+ }
249
+ /** Send the current batch now, even if it hasn't reached `batchSize`. */
250
+ flush() {
251
+ if (this.batch.length === 0) {
252
+ return;
253
+ }
254
+ const batch = this.batch;
255
+ this.batch = [];
256
+ const result = this.sender(this.url, batch);
257
+ if (result) {
258
+ result.catch((error) => {
259
+ console.error("HTTPTransport: failed to send log batch", error);
260
+ });
261
+ }
262
+ }
263
+ close() {
264
+ this.flush();
265
+ }
266
+ };
267
+
268
+ // src/logger.ts
269
+ var Logger = class _Logger {
270
+ name;
271
+ transports;
272
+ plugins;
273
+ currentLevel;
274
+ baseMeta;
275
+ constructor(name, options = {}) {
276
+ this.name = name;
277
+ this.currentLevel = parseLevel(options.level ?? 20 /* INFO */);
278
+ this.transports = options.transports ? [...options.transports] : [];
279
+ this.plugins = options.plugins ? [...options.plugins] : [];
280
+ this.baseMeta = options.meta ? { ...options.meta } : {};
281
+ }
282
+ get level() {
283
+ return this.currentLevel;
284
+ }
285
+ setLevel(level) {
286
+ this.currentLevel = parseLevel(level);
287
+ }
288
+ /** Register a plugin. Returns `this` so calls can be chained. */
289
+ use(plugin) {
290
+ this.plugins.push(plugin);
291
+ return this;
292
+ }
293
+ /** Close every attached transport. Call on shutdown to flush buffered writes. */
294
+ close() {
295
+ for (const transport of this.transports) {
296
+ transport.close();
297
+ }
298
+ }
299
+ /** A logger scoped under this one, inheriting its level, transports, and plugins. */
300
+ child(name, meta = {}) {
301
+ return new _Logger(`${this.name}.${name}`, {
302
+ level: this.currentLevel,
303
+ transports: this.transports,
304
+ plugins: this.plugins,
305
+ meta: { ...this.baseMeta, ...meta }
306
+ });
307
+ }
308
+ notifyError(plugin, error, record) {
309
+ try {
310
+ plugin.onError?.(error, record);
311
+ } catch {
312
+ }
313
+ }
314
+ dispatch(level, message, meta) {
315
+ if (level < this.currentLevel) {
316
+ return null;
317
+ }
318
+ let record = createRecord({
319
+ level,
320
+ logger: this.name,
321
+ message,
322
+ meta: { ...this.baseMeta, ...meta }
323
+ });
324
+ for (const plugin of this.plugins) {
325
+ let result;
326
+ try {
327
+ result = plugin.beforeLog ? plugin.beforeLog(record) : record;
328
+ } catch (error) {
329
+ this.notifyError(plugin, error, record);
330
+ continue;
331
+ }
332
+ if (result === null) {
333
+ return null;
334
+ }
335
+ record = result;
336
+ }
337
+ for (const transport of this.transports) {
338
+ transport.write(transport.format(record), record);
339
+ }
340
+ for (const plugin of this.plugins) {
341
+ try {
342
+ plugin.afterLog?.(record);
343
+ } catch (error) {
344
+ this.notifyError(plugin, error, record);
345
+ }
346
+ }
347
+ return record;
348
+ }
349
+ trace(message, meta = {}) {
350
+ return this.dispatch(5 /* TRACE */, message, meta);
351
+ }
352
+ debug(message, meta = {}) {
353
+ return this.dispatch(10 /* DEBUG */, message, meta);
354
+ }
355
+ info(message, meta = {}) {
356
+ return this.dispatch(20 /* INFO */, message, meta);
357
+ }
358
+ warn(message, meta = {}) {
359
+ return this.dispatch(30 /* WARN */, message, meta);
360
+ }
361
+ error(message, meta = {}) {
362
+ return this.dispatch(40 /* ERROR */, message, meta);
363
+ }
364
+ fatal(message, meta = {}) {
365
+ return this.dispatch(50 /* FATAL */, message, meta);
366
+ }
367
+ };
368
+
3
369
  // src/index.ts
4
- var VERSION = "0.1.0";
370
+ var VERSION = "0.1.2";
5
371
 
372
+ exports.CollectingTransport = CollectingTransport;
373
+ exports.ConsoleTransport = ConsoleTransport;
374
+ exports.ContextPlugin = ContextPlugin;
375
+ exports.DEFAULT_REDACTED_KEYS = DEFAULT_REDACTED_KEYS;
376
+ exports.FileTransport = FileTransport;
377
+ exports.HTTPTransport = HTTPTransport;
378
+ exports.JSONFormatter = JSONFormatter;
379
+ exports.Level = Level;
380
+ exports.Logger = Logger;
381
+ exports.RedactPlugin = RedactPlugin;
382
+ exports.SamplingPlugin = SamplingPlugin;
383
+ exports.Transport = Transport;
6
384
  exports.VERSION = VERSION;
385
+ exports.createRecord = createRecord;
386
+ exports.levelName = levelName;
387
+ exports.parseLevel = parseLevel;
388
+ exports.utcTimestamp = utcTimestamp;
7
389
  //# sourceMappingURL=index.cjs.map
8
390
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAA,GAAU","file":"index.cjs","sourcesContent":["// Phase 0 scaffolding placeholder — Logger, levels, formatters, transports,\n// and plugins land in later phases.\nexport const VERSION = \"0.1.0\";\n"]}
1
+ {"version":3,"sources":["../src/levels.ts","../src/records.ts","../src/formatter.ts","../src/context-plugin.ts","../src/redact-plugin.ts","../src/sampling-plugin.ts","../src/transport.ts","../src/console-transport.ts","../src/file-transport.ts","../src/http-transport.ts","../src/logger.ts","../src/index.ts"],"names":["Level","path","mkdirSync","dirname","openSync","writeSync","fstatSync","closeSync","existsSync","unlinkSync","renameSync"],"mappings":"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","file":"index.cjs","sourcesContent":["/** Log levels, shared by name and numeric weight with the logquill-python contract. */\nexport enum Level {\n TRACE = 5,\n DEBUG = 10,\n INFO = 20,\n WARN = 30,\n ERROR = 40,\n FATAL = 50,\n}\n\nconst NAME_TO_LEVEL: Readonly<Record<string, Level>> = {\n TRACE: Level.TRACE,\n DEBUG: Level.DEBUG,\n INFO: Level.INFO,\n WARN: Level.WARN,\n ERROR: Level.ERROR,\n FATAL: Level.FATAL,\n};\n\n/** A level given as a `Level`, a level name (any case), or its numeric weight. */\nexport type LevelInput = Level | number | string;\n\n/** The level's name, e.g. `levelName(Level.INFO) === \"INFO\"`. */\nexport function levelName(level: Level): string {\n return Level[level];\n}\n\n/** Normalize a level given as a `Level`, level name, or numeric weight. Throws if unknown. */\nexport function parseLevel(level: LevelInput): Level {\n if (typeof level === \"string\") {\n const parsed = NAME_TO_LEVEL[level.toUpperCase()];\n if (parsed === undefined) {\n throw new Error(`Unknown log level: ${level}`);\n }\n return parsed;\n }\n if (Level[level] === undefined) {\n throw new Error(`Unknown log level: ${String(level)}`);\n }\n return level;\n}\n","import { levelName, type Level } from \"./levels.js\";\n\n/** The cross-language record shape shared with logquill-python. */\nexport interface LogRecord {\n timestamp: string;\n level: string;\n logger: string;\n message: string;\n meta: Record<string, unknown>;\n}\n\n/** ISO8601 UTC timestamp with millisecond precision, matching Python's `utc_timestamp()`. */\nexport function utcTimestamp(): string {\n return new Date().toISOString();\n}\n\nexport function createRecord(params: {\n level: Level;\n logger: string;\n message: string;\n meta: Record<string, unknown>;\n}): LogRecord {\n return {\n timestamp: utcTimestamp(),\n level: levelName(params.level),\n logger: params.logger,\n message: params.message,\n meta: params.meta,\n };\n}\n","import type { LogRecord } from \"./records.js\";\n\n/** `format(record) -> string`, per the transport contract shared with logquill-python. */\nexport interface Formatter {\n format(record: LogRecord): string;\n}\n\n/** Serializes a record to the canonical JSON line shape. */\nexport class JSONFormatter implements Formatter {\n format(record: LogRecord): string {\n return JSON.stringify(record);\n }\n}\n","import type { Plugin } from \"./plugin.js\";\nimport type { LogRecord } from \"./records.js\";\n\n/**\n * Injects fixed key/value pairs into every record's `meta`.\n * A value already present in a record's own `meta` wins over the fixed context.\n */\nexport class ContextPlugin implements Plugin {\n readonly context: Record<string, unknown>;\n\n constructor(context: Record<string, unknown>) {\n this.context = context;\n }\n\n beforeLog(record: LogRecord): LogRecord {\n return { ...record, meta: { ...this.context, ...record.meta } };\n }\n}\n","import type { Plugin } from \"./plugin.js\";\nimport type { LogRecord } from \"./records.js\";\n\nexport const DEFAULT_REDACTED_KEYS: readonly string[] = [\"password\", \"token\", \"secret\", \"api_key\", \"authorization\"];\n\nexport interface RedactPluginOptions {\n keys?: readonly string[];\n replacement?: string;\n}\n\n/** Replaces sensitive `meta` values, matched by key (case-insensitive), with a placeholder. */\nexport class RedactPlugin implements Plugin {\n private readonly keys: ReadonlySet<string>;\n readonly replacement: string;\n\n constructor(options: RedactPluginOptions = {}) {\n this.keys = new Set((options.keys ?? DEFAULT_REDACTED_KEYS).map((key) => key.toLowerCase()));\n this.replacement = options.replacement ?? \"***\";\n }\n\n beforeLog(record: LogRecord): LogRecord {\n const meta: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record.meta)) {\n meta[key] = this.keys.has(key.toLowerCase()) ? this.replacement : value;\n }\n return { ...record, meta };\n }\n}\n","import type { Plugin } from \"./plugin.js\";\nimport type { LogRecord } from \"./records.js\";\n\nexport interface SamplingPluginOptions {\n rng?: () => number;\n}\n\n/** Keeps roughly `rate` of records (0.0-1.0), dropping the rest. */\nexport class SamplingPlugin implements Plugin {\n readonly rate: number;\n private readonly rng: () => number;\n\n constructor(rate: number, options: SamplingPluginOptions = {}) {\n if (rate < 0 || rate > 1) {\n throw new Error(`rate must be between 0 and 1, got ${String(rate)}`);\n }\n this.rate = rate;\n this.rng = options.rng ?? Math.random;\n }\n\n beforeLog(record: LogRecord): LogRecord | null {\n return this.rng() < this.rate ? record : null;\n }\n}\n","import { JSONFormatter, type Formatter } from \"./formatter.js\";\nimport type { LogRecord } from \"./records.js\";\n\n/**\n * Sink for log records, per the cross-language transport contract:\n * `format(record) -> string`, `write(formatted, record)`, `close()` on shutdown.\n */\nexport abstract class Transport {\n formatter: Formatter;\n\n constructor(formatter: Formatter = new JSONFormatter()) {\n this.formatter = formatter;\n }\n\n format(record: LogRecord): string {\n return this.formatter.format(record);\n }\n\n abstract write(formatted: string, record: LogRecord): void;\n\n /** Flush/release resources on shutdown. No-op unless a transport overrides it. */\n close(): void {}\n}\n\n/** In-memory transport for tests: collects every (formatted, record) pair written to it. */\nexport class CollectingTransport extends Transport {\n readonly formatted: string[] = [];\n readonly records: LogRecord[] = [];\n closed = false;\n\n write(formatted: string, record: LogRecord): void {\n this.formatted.push(formatted);\n this.records.push(record);\n }\n\n override close(): void {\n this.closed = true;\n }\n}\n","import type { Formatter } from \"./formatter.js\";\nimport { Level, parseLevel } from \"./levels.js\";\nimport type { LogRecord } from \"./records.js\";\nimport { Transport } from \"./transport.js\";\n\nconst COLORS: Readonly<Record<Level, string>> = {\n [Level.TRACE]: \"\\x1b[90m\", // gray\n [Level.DEBUG]: \"\\x1b[36m\", // cyan\n [Level.INFO]: \"\\x1b[32m\", // green\n [Level.WARN]: \"\\x1b[33m\", // yellow\n [Level.ERROR]: \"\\x1b[31m\", // red\n [Level.FATAL]: \"\\x1b[35m\", // magenta\n};\nconst RESET = \"\\x1b[0m\";\n\n/** The subset of `console` this transport needs — swap in a fake for tests. */\nexport interface ConsoleLike {\n log(message: string): void;\n error(message: string): void;\n}\n\nexport interface ConsoleTransportOptions {\n formatter?: Formatter;\n colorize?: boolean;\n console?: ConsoleLike;\n}\n\nfunction defaultColorize(): boolean {\n // Isomorphic guard: `process` doesn't exist in a browser bundle.\n const env: NodeJS.ProcessEnv | undefined = typeof process === \"undefined\" ? undefined : process.env;\n return env?.NO_COLOR === undefined;\n}\n\n/**\n * Writes to `console.log`, routing ERROR/FATAL to `console.error`, colorized by\n * level. Uses the global `console` rather than Node's `process.stdout`/`stderr`\n * so this transport works unmodified in a browser bundle.\n */\nexport class ConsoleTransport extends Transport {\n colorize: boolean;\n private readonly out: ConsoleLike;\n\n constructor(options: ConsoleTransportOptions = {}) {\n super(options.formatter);\n this.colorize = options.colorize ?? defaultColorize();\n this.out = options.console ?? console;\n }\n\n write(formatted: string, record: LogRecord): void {\n const level = parseLevel(record.level);\n const line = this.colorize ? this.applyColor(formatted, level) : formatted;\n if (level >= Level.ERROR) {\n this.out.error(line);\n } else {\n this.out.log(line);\n }\n }\n\n private applyColor(formatted: string, level: Level): string {\n return `${COLORS[level]}${formatted}${RESET}`;\n }\n}\n","import { closeSync, existsSync, fstatSync, mkdirSync, openSync, renameSync, unlinkSync, writeSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { Formatter } from \"./formatter.js\";\nimport { Transport } from \"./transport.js\";\n\nexport interface FileTransportOptions {\n formatter?: Formatter;\n maxBytes?: number;\n backupCount?: number;\n}\n\n/** Appends formatted records to a file, rotating when it exceeds `maxBytes`. */\nexport class FileTransport extends Transport {\n readonly path: string;\n readonly maxBytes: number;\n readonly backupCount: number;\n private fd: number;\n\n constructor(path: string, options: FileTransportOptions = {}) {\n super(options.formatter);\n this.path = path;\n this.maxBytes = options.maxBytes ?? 10 * 1024 * 1024;\n this.backupCount = options.backupCount ?? 5;\n mkdirSync(dirname(path), { recursive: true });\n this.fd = openSync(this.path, \"a\");\n }\n\n write(formatted: string): void {\n writeSync(this.fd, formatted + \"\\n\");\n if (this.maxBytes > 0 && fstatSync(this.fd).size >= this.maxBytes) {\n this.rotate();\n }\n }\n\n private rotate(): void {\n closeSync(this.fd);\n\n if (this.backupCount > 0) {\n for (let index = this.backupCount - 1; index > 0; index--) {\n const src = `${this.path}.${String(index)}`;\n const dst = `${this.path}.${String(index + 1)}`;\n if (existsSync(src)) {\n if (existsSync(dst)) {\n unlinkSync(dst);\n }\n renameSync(src, dst);\n }\n }\n const backup = `${this.path}.1`;\n if (existsSync(backup)) {\n unlinkSync(backup);\n }\n renameSync(this.path, backup);\n } else {\n unlinkSync(this.path);\n }\n\n this.fd = openSync(this.path, \"a\");\n }\n\n override close(): void {\n closeSync(this.fd);\n }\n}\n","import type { Formatter } from \"./formatter.js\";\nimport { Transport } from \"./transport.js\";\n\n/** Sends one batch of formatted lines to `url`. Swap in a fake for tests. */\nexport type Sender = (url: string, batch: readonly string[]) => Promise<void> | void;\n\nasync function fetchSender(url: string, batch: readonly string[]): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-ndjson\" },\n body: batch.join(\"\\n\"),\n });\n if (!response.ok) {\n throw new Error(`HTTPTransport: request to ${url} failed with status ${String(response.status)}`);\n }\n}\n\nexport interface HTTPTransportOptions {\n formatter?: Formatter;\n batchSize?: number;\n sender?: Sender;\n}\n\n/**\n * Batches formatted records and POSTs them as newline-delimited JSON via `fetch`.\n * Pass `sender` to swap in a fake for tests, or a different backend.\n */\nexport class HTTPTransport extends Transport {\n readonly url: string;\n readonly batchSize: number;\n private readonly sender: Sender;\n private batch: string[] = [];\n\n constructor(url: string, options: HTTPTransportOptions = {}) {\n super(options.formatter);\n this.url = url;\n this.batchSize = options.batchSize ?? 50;\n this.sender = options.sender ?? fetchSender;\n }\n\n write(formatted: string): void {\n this.batch.push(formatted);\n if (this.batch.length >= this.batchSize) {\n this.flush();\n }\n }\n\n /** Send the current batch now, even if it hasn't reached `batchSize`. */\n flush(): void {\n if (this.batch.length === 0) {\n return;\n }\n const batch = this.batch;\n this.batch = [];\n\n const result = this.sender(this.url, batch);\n if (result) {\n result.catch((error: unknown) => {\n // write()/close() are non-blocking by contract, so a send failure can't\n // propagate to the caller — report it the same way a broken plugin would.\n console.error(\"HTTPTransport: failed to send log batch\", error);\n });\n }\n }\n\n override close(): void {\n this.flush();\n }\n}\n","import { Level, parseLevel, type LevelInput } from \"./levels.js\";\nimport type { Plugin } from \"./plugin.js\";\nimport { createRecord, type LogRecord } from \"./records.js\";\nimport type { Transport } from \"./transport.js\";\n\nexport interface LoggerOptions {\n level?: LevelInput;\n transports?: Transport[];\n plugins?: Plugin[];\n meta?: Record<string, unknown>;\n}\n\nexport class Logger {\n readonly name: string;\n readonly transports: Transport[];\n readonly plugins: Plugin[];\n private currentLevel: Level;\n private readonly baseMeta: Record<string, unknown>;\n\n constructor(name: string, options: LoggerOptions = {}) {\n this.name = name;\n this.currentLevel = parseLevel(options.level ?? Level.INFO);\n this.transports = options.transports ? [...options.transports] : [];\n this.plugins = options.plugins ? [...options.plugins] : [];\n this.baseMeta = options.meta ? { ...options.meta } : {};\n }\n\n get level(): Level {\n return this.currentLevel;\n }\n\n setLevel(level: LevelInput): void {\n this.currentLevel = parseLevel(level);\n }\n\n /** Register a plugin. Returns `this` so calls can be chained. */\n use(plugin: Plugin): this {\n this.plugins.push(plugin);\n return this;\n }\n\n /** Close every attached transport. Call on shutdown to flush buffered writes. */\n close(): void {\n for (const transport of this.transports) {\n transport.close();\n }\n }\n\n /** A logger scoped under this one, inheriting its level, transports, and plugins. */\n child(name: string, meta: Record<string, unknown> = {}): Logger {\n return new Logger(`${this.name}.${name}`, {\n level: this.currentLevel,\n transports: this.transports,\n plugins: this.plugins,\n meta: { ...this.baseMeta, ...meta },\n });\n }\n\n private notifyError(plugin: Plugin, error: unknown, record: LogRecord): void {\n // a broken error handler must not crash logging either\n try {\n plugin.onError?.(error, record);\n } catch {\n /* swallowed */\n }\n }\n\n private dispatch(level: Level, message: string, meta: Record<string, unknown>): LogRecord | null {\n if (level < this.currentLevel) {\n return null;\n }\n\n let record = createRecord({\n level,\n logger: this.name,\n message,\n meta: { ...this.baseMeta, ...meta },\n });\n\n for (const plugin of this.plugins) {\n let result: LogRecord | null;\n try {\n result = plugin.beforeLog ? plugin.beforeLog(record) : record;\n } catch (error) {\n this.notifyError(plugin, error, record);\n continue;\n }\n if (result === null) {\n return null;\n }\n record = result;\n }\n\n for (const transport of this.transports) {\n transport.write(transport.format(record), record);\n }\n\n for (const plugin of this.plugins) {\n try {\n plugin.afterLog?.(record);\n } catch (error) {\n this.notifyError(plugin, error, record);\n }\n }\n\n return record;\n }\n\n trace(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.TRACE, message, meta);\n }\n\n debug(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.DEBUG, message, meta);\n }\n\n info(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.INFO, message, meta);\n }\n\n warn(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.WARN, message, meta);\n }\n\n error(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.ERROR, message, meta);\n }\n\n fatal(message: string, meta: Record<string, unknown> = {}): LogRecord | null {\n return this.dispatch(Level.FATAL, message, meta);\n }\n}\n","export { Level, levelName, parseLevel } from \"./levels.js\";\nexport type { LevelInput } from \"./levels.js\";\n\nexport { createRecord, utcTimestamp } from \"./records.js\";\nexport type { LogRecord } from \"./records.js\";\n\nexport { JSONFormatter } from \"./formatter.js\";\nexport type { Formatter } from \"./formatter.js\";\n\nexport type { Plugin } from \"./plugin.js\";\n\nexport { ContextPlugin } from \"./context-plugin.js\";\n\nexport { DEFAULT_REDACTED_KEYS, RedactPlugin } from \"./redact-plugin.js\";\nexport type { RedactPluginOptions } from \"./redact-plugin.js\";\n\nexport { SamplingPlugin } from \"./sampling-plugin.js\";\nexport type { SamplingPluginOptions } from \"./sampling-plugin.js\";\n\nexport { CollectingTransport, Transport } from \"./transport.js\";\n\nexport { ConsoleTransport } from \"./console-transport.js\";\nexport type { ConsoleLike, ConsoleTransportOptions } from \"./console-transport.js\";\n\nexport { FileTransport } from \"./file-transport.js\";\nexport type { FileTransportOptions } from \"./file-transport.js\";\n\nexport { HTTPTransport } from \"./http-transport.js\";\nexport type { HTTPTransportOptions, Sender } from \"./http-transport.js\";\n\nexport { Logger } from \"./logger.js\";\nexport type { LoggerOptions } from \"./logger.js\";\n\nexport const VERSION = \"0.1.2\";\n"]}