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 +132 -5
- package/dist/index.cjs +383 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -2
- package/dist/index.d.ts +208 -2
- package/dist/index.mjs +368 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,372 @@
|
|
|
1
|
+
import { mkdirSync, openSync, writeSync, fstatSync, closeSync, existsSync, unlinkSync, renameSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/levels.ts
|
|
5
|
+
var Level = /* @__PURE__ */ ((Level2) => {
|
|
6
|
+
Level2[Level2["TRACE"] = 5] = "TRACE";
|
|
7
|
+
Level2[Level2["DEBUG"] = 10] = "DEBUG";
|
|
8
|
+
Level2[Level2["INFO"] = 20] = "INFO";
|
|
9
|
+
Level2[Level2["WARN"] = 30] = "WARN";
|
|
10
|
+
Level2[Level2["ERROR"] = 40] = "ERROR";
|
|
11
|
+
Level2[Level2["FATAL"] = 50] = "FATAL";
|
|
12
|
+
return Level2;
|
|
13
|
+
})(Level || {});
|
|
14
|
+
var NAME_TO_LEVEL = {
|
|
15
|
+
TRACE: 5 /* TRACE */,
|
|
16
|
+
DEBUG: 10 /* DEBUG */,
|
|
17
|
+
INFO: 20 /* INFO */,
|
|
18
|
+
WARN: 30 /* WARN */,
|
|
19
|
+
ERROR: 40 /* ERROR */,
|
|
20
|
+
FATAL: 50 /* FATAL */
|
|
21
|
+
};
|
|
22
|
+
function levelName(level) {
|
|
23
|
+
return Level[level];
|
|
24
|
+
}
|
|
25
|
+
function parseLevel(level) {
|
|
26
|
+
if (typeof level === "string") {
|
|
27
|
+
const parsed = NAME_TO_LEVEL[level.toUpperCase()];
|
|
28
|
+
if (parsed === void 0) {
|
|
29
|
+
throw new Error(`Unknown log level: ${level}`);
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
if (Level[level] === void 0) {
|
|
34
|
+
throw new Error(`Unknown log level: ${String(level)}`);
|
|
35
|
+
}
|
|
36
|
+
return level;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/records.ts
|
|
40
|
+
function utcTimestamp() {
|
|
41
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
42
|
+
}
|
|
43
|
+
function createRecord(params) {
|
|
44
|
+
return {
|
|
45
|
+
timestamp: utcTimestamp(),
|
|
46
|
+
level: levelName(params.level),
|
|
47
|
+
logger: params.logger,
|
|
48
|
+
message: params.message,
|
|
49
|
+
meta: params.meta
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/formatter.ts
|
|
54
|
+
var JSONFormatter = class {
|
|
55
|
+
format(record) {
|
|
56
|
+
return JSON.stringify(record);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/context-plugin.ts
|
|
61
|
+
var ContextPlugin = class {
|
|
62
|
+
context;
|
|
63
|
+
constructor(context) {
|
|
64
|
+
this.context = context;
|
|
65
|
+
}
|
|
66
|
+
beforeLog(record) {
|
|
67
|
+
return { ...record, meta: { ...this.context, ...record.meta } };
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/redact-plugin.ts
|
|
72
|
+
var DEFAULT_REDACTED_KEYS = ["password", "token", "secret", "api_key", "authorization"];
|
|
73
|
+
var RedactPlugin = class {
|
|
74
|
+
keys;
|
|
75
|
+
replacement;
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
this.keys = new Set((options.keys ?? DEFAULT_REDACTED_KEYS).map((key) => key.toLowerCase()));
|
|
78
|
+
this.replacement = options.replacement ?? "***";
|
|
79
|
+
}
|
|
80
|
+
beforeLog(record) {
|
|
81
|
+
const meta = {};
|
|
82
|
+
for (const [key, value] of Object.entries(record.meta)) {
|
|
83
|
+
meta[key] = this.keys.has(key.toLowerCase()) ? this.replacement : value;
|
|
84
|
+
}
|
|
85
|
+
return { ...record, meta };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/sampling-plugin.ts
|
|
90
|
+
var SamplingPlugin = class {
|
|
91
|
+
rate;
|
|
92
|
+
rng;
|
|
93
|
+
constructor(rate, options = {}) {
|
|
94
|
+
if (rate < 0 || rate > 1) {
|
|
95
|
+
throw new Error(`rate must be between 0 and 1, got ${String(rate)}`);
|
|
96
|
+
}
|
|
97
|
+
this.rate = rate;
|
|
98
|
+
this.rng = options.rng ?? Math.random;
|
|
99
|
+
}
|
|
100
|
+
beforeLog(record) {
|
|
101
|
+
return this.rng() < this.rate ? record : null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/transport.ts
|
|
106
|
+
var Transport = class {
|
|
107
|
+
formatter;
|
|
108
|
+
constructor(formatter = new JSONFormatter()) {
|
|
109
|
+
this.formatter = formatter;
|
|
110
|
+
}
|
|
111
|
+
format(record) {
|
|
112
|
+
return this.formatter.format(record);
|
|
113
|
+
}
|
|
114
|
+
/** Flush/release resources on shutdown. No-op unless a transport overrides it. */
|
|
115
|
+
close() {
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var CollectingTransport = class extends Transport {
|
|
119
|
+
formatted = [];
|
|
120
|
+
records = [];
|
|
121
|
+
closed = false;
|
|
122
|
+
write(formatted, record) {
|
|
123
|
+
this.formatted.push(formatted);
|
|
124
|
+
this.records.push(record);
|
|
125
|
+
}
|
|
126
|
+
close() {
|
|
127
|
+
this.closed = true;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/console-transport.ts
|
|
132
|
+
var COLORS = {
|
|
133
|
+
[5 /* TRACE */]: "\x1B[90m",
|
|
134
|
+
// gray
|
|
135
|
+
[10 /* DEBUG */]: "\x1B[36m",
|
|
136
|
+
// cyan
|
|
137
|
+
[20 /* INFO */]: "\x1B[32m",
|
|
138
|
+
// green
|
|
139
|
+
[30 /* WARN */]: "\x1B[33m",
|
|
140
|
+
// yellow
|
|
141
|
+
[40 /* ERROR */]: "\x1B[31m",
|
|
142
|
+
// red
|
|
143
|
+
[50 /* FATAL */]: "\x1B[35m"
|
|
144
|
+
// magenta
|
|
145
|
+
};
|
|
146
|
+
var RESET = "\x1B[0m";
|
|
147
|
+
function defaultColorize() {
|
|
148
|
+
const env = typeof process === "undefined" ? void 0 : process.env;
|
|
149
|
+
return env?.NO_COLOR === void 0;
|
|
150
|
+
}
|
|
151
|
+
var ConsoleTransport = class extends Transport {
|
|
152
|
+
colorize;
|
|
153
|
+
out;
|
|
154
|
+
constructor(options = {}) {
|
|
155
|
+
super(options.formatter);
|
|
156
|
+
this.colorize = options.colorize ?? defaultColorize();
|
|
157
|
+
this.out = options.console ?? console;
|
|
158
|
+
}
|
|
159
|
+
write(formatted, record) {
|
|
160
|
+
const level = parseLevel(record.level);
|
|
161
|
+
const line = this.colorize ? this.applyColor(formatted, level) : formatted;
|
|
162
|
+
if (level >= 40 /* ERROR */) {
|
|
163
|
+
this.out.error(line);
|
|
164
|
+
} else {
|
|
165
|
+
this.out.log(line);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
applyColor(formatted, level) {
|
|
169
|
+
return `${COLORS[level]}${formatted}${RESET}`;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var FileTransport = class extends Transport {
|
|
173
|
+
path;
|
|
174
|
+
maxBytes;
|
|
175
|
+
backupCount;
|
|
176
|
+
fd;
|
|
177
|
+
constructor(path, options = {}) {
|
|
178
|
+
super(options.formatter);
|
|
179
|
+
this.path = path;
|
|
180
|
+
this.maxBytes = options.maxBytes ?? 10 * 1024 * 1024;
|
|
181
|
+
this.backupCount = options.backupCount ?? 5;
|
|
182
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
183
|
+
this.fd = openSync(this.path, "a");
|
|
184
|
+
}
|
|
185
|
+
write(formatted) {
|
|
186
|
+
writeSync(this.fd, formatted + "\n");
|
|
187
|
+
if (this.maxBytes > 0 && fstatSync(this.fd).size >= this.maxBytes) {
|
|
188
|
+
this.rotate();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
rotate() {
|
|
192
|
+
closeSync(this.fd);
|
|
193
|
+
if (this.backupCount > 0) {
|
|
194
|
+
for (let index = this.backupCount - 1; index > 0; index--) {
|
|
195
|
+
const src = `${this.path}.${String(index)}`;
|
|
196
|
+
const dst = `${this.path}.${String(index + 1)}`;
|
|
197
|
+
if (existsSync(src)) {
|
|
198
|
+
if (existsSync(dst)) {
|
|
199
|
+
unlinkSync(dst);
|
|
200
|
+
}
|
|
201
|
+
renameSync(src, dst);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const backup = `${this.path}.1`;
|
|
205
|
+
if (existsSync(backup)) {
|
|
206
|
+
unlinkSync(backup);
|
|
207
|
+
}
|
|
208
|
+
renameSync(this.path, backup);
|
|
209
|
+
} else {
|
|
210
|
+
unlinkSync(this.path);
|
|
211
|
+
}
|
|
212
|
+
this.fd = openSync(this.path, "a");
|
|
213
|
+
}
|
|
214
|
+
close() {
|
|
215
|
+
closeSync(this.fd);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/http-transport.ts
|
|
220
|
+
async function fetchSender(url, batch) {
|
|
221
|
+
const response = await fetch(url, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: { "Content-Type": "application/x-ndjson" },
|
|
224
|
+
body: batch.join("\n")
|
|
225
|
+
});
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`HTTPTransport: request to ${url} failed with status ${String(response.status)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
var HTTPTransport = class extends Transport {
|
|
231
|
+
url;
|
|
232
|
+
batchSize;
|
|
233
|
+
sender;
|
|
234
|
+
batch = [];
|
|
235
|
+
constructor(url, options = {}) {
|
|
236
|
+
super(options.formatter);
|
|
237
|
+
this.url = url;
|
|
238
|
+
this.batchSize = options.batchSize ?? 50;
|
|
239
|
+
this.sender = options.sender ?? fetchSender;
|
|
240
|
+
}
|
|
241
|
+
write(formatted) {
|
|
242
|
+
this.batch.push(formatted);
|
|
243
|
+
if (this.batch.length >= this.batchSize) {
|
|
244
|
+
this.flush();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Send the current batch now, even if it hasn't reached `batchSize`. */
|
|
248
|
+
flush() {
|
|
249
|
+
if (this.batch.length === 0) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const batch = this.batch;
|
|
253
|
+
this.batch = [];
|
|
254
|
+
const result = this.sender(this.url, batch);
|
|
255
|
+
if (result) {
|
|
256
|
+
result.catch((error) => {
|
|
257
|
+
console.error("HTTPTransport: failed to send log batch", error);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
close() {
|
|
262
|
+
this.flush();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/logger.ts
|
|
267
|
+
var Logger = class _Logger {
|
|
268
|
+
name;
|
|
269
|
+
transports;
|
|
270
|
+
plugins;
|
|
271
|
+
currentLevel;
|
|
272
|
+
baseMeta;
|
|
273
|
+
constructor(name, options = {}) {
|
|
274
|
+
this.name = name;
|
|
275
|
+
this.currentLevel = parseLevel(options.level ?? 20 /* INFO */);
|
|
276
|
+
this.transports = options.transports ? [...options.transports] : [];
|
|
277
|
+
this.plugins = options.plugins ? [...options.plugins] : [];
|
|
278
|
+
this.baseMeta = options.meta ? { ...options.meta } : {};
|
|
279
|
+
}
|
|
280
|
+
get level() {
|
|
281
|
+
return this.currentLevel;
|
|
282
|
+
}
|
|
283
|
+
setLevel(level) {
|
|
284
|
+
this.currentLevel = parseLevel(level);
|
|
285
|
+
}
|
|
286
|
+
/** Register a plugin. Returns `this` so calls can be chained. */
|
|
287
|
+
use(plugin) {
|
|
288
|
+
this.plugins.push(plugin);
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
/** Close every attached transport. Call on shutdown to flush buffered writes. */
|
|
292
|
+
close() {
|
|
293
|
+
for (const transport of this.transports) {
|
|
294
|
+
transport.close();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/** A logger scoped under this one, inheriting its level, transports, and plugins. */
|
|
298
|
+
child(name, meta = {}) {
|
|
299
|
+
return new _Logger(`${this.name}.${name}`, {
|
|
300
|
+
level: this.currentLevel,
|
|
301
|
+
transports: this.transports,
|
|
302
|
+
plugins: this.plugins,
|
|
303
|
+
meta: { ...this.baseMeta, ...meta }
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
notifyError(plugin, error, record) {
|
|
307
|
+
try {
|
|
308
|
+
plugin.onError?.(error, record);
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
dispatch(level, message, meta) {
|
|
313
|
+
if (level < this.currentLevel) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
let record = createRecord({
|
|
317
|
+
level,
|
|
318
|
+
logger: this.name,
|
|
319
|
+
message,
|
|
320
|
+
meta: { ...this.baseMeta, ...meta }
|
|
321
|
+
});
|
|
322
|
+
for (const plugin of this.plugins) {
|
|
323
|
+
let result;
|
|
324
|
+
try {
|
|
325
|
+
result = plugin.beforeLog ? plugin.beforeLog(record) : record;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
this.notifyError(plugin, error, record);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (result === null) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
record = result;
|
|
334
|
+
}
|
|
335
|
+
for (const transport of this.transports) {
|
|
336
|
+
transport.write(transport.format(record), record);
|
|
337
|
+
}
|
|
338
|
+
for (const plugin of this.plugins) {
|
|
339
|
+
try {
|
|
340
|
+
plugin.afterLog?.(record);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
this.notifyError(plugin, error, record);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return record;
|
|
346
|
+
}
|
|
347
|
+
trace(message, meta = {}) {
|
|
348
|
+
return this.dispatch(5 /* TRACE */, message, meta);
|
|
349
|
+
}
|
|
350
|
+
debug(message, meta = {}) {
|
|
351
|
+
return this.dispatch(10 /* DEBUG */, message, meta);
|
|
352
|
+
}
|
|
353
|
+
info(message, meta = {}) {
|
|
354
|
+
return this.dispatch(20 /* INFO */, message, meta);
|
|
355
|
+
}
|
|
356
|
+
warn(message, meta = {}) {
|
|
357
|
+
return this.dispatch(30 /* WARN */, message, meta);
|
|
358
|
+
}
|
|
359
|
+
error(message, meta = {}) {
|
|
360
|
+
return this.dispatch(40 /* ERROR */, message, meta);
|
|
361
|
+
}
|
|
362
|
+
fatal(message, meta = {}) {
|
|
363
|
+
return this.dispatch(50 /* FATAL */, message, meta);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
1
367
|
// src/index.ts
|
|
2
|
-
var VERSION = "0.1.
|
|
368
|
+
var VERSION = "0.1.2";
|
|
3
369
|
|
|
4
|
-
export { VERSION };
|
|
370
|
+
export { CollectingTransport, ConsoleTransport, ContextPlugin, DEFAULT_REDACTED_KEYS, FileTransport, HTTPTransport, JSONFormatter, Level, Logger, RedactPlugin, SamplingPlugin, Transport, VERSION, createRecord, levelName, parseLevel, utcTimestamp };
|
|
5
371
|
//# sourceMappingURL=index.mjs.map
|
|
6
372
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAEO,IAAM,OAAA,GAAU","file":"index.mjs","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"],"mappings":"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","file":"index.mjs","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"]}
|