@unhingged/logit 0.0.0-stage → 0.1.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/dist/node.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { SinkOptions, FormatName, Formatter, LogLevel, Logger, Sink, Enricher } from './index.js';
2
+ export { BatchingOptions, BatchingSink, CLEF_MEDIA_TYPE, CapturedError, ClefFormatterOptions, ConsoleSinkOptions, ContextFrame, ContextHandle, ContextStore, DEFAULT_DESTRUCTURING, DEFAULT_OUTPUT_TEMPLATE, DestructuringOptions, DestructuringPolicy, FetchLike, Filter, HoleToken, HttpSinkOptions, JsonFormatterOptions, LEVELS, LEVEL_CODES, LEVEL_NUMBERS, LEVEL_ORDER, LevelOrSwitch, LevelSwitch, LogContext, LogEvent, LogEventInit, LogMethod, LoggerConfiguration, LoggerOptions, MapSinkOptions, Matching, MemorySink, MessageTemplate, Operation, OperationOptions, PrettyFormatterOptions, RenderOptions, SERILOG_LEVEL_NAMES, SeqSinkOptions, StreamLike, StreamSinkOptions, TemplateToken, TextFormatterOptions, TextToken, TraceContext, ValueStyles, ansi, audit, batchingSink, byExcluding, byIncludingOnly, byTransforming, callbackSink, captureError, captureProperty, captureValue, clefFormatter, closeAndFlush, conditional, configure, consoleSink, createLogger, detectColors, diagnosticContext, formatDate, formatError, formatLevel, formatNumber, fromLogContext, httpSink, isLevel, isoTimestamp, jsonFormatter, jsonReplacer, levelAtLeast, log, mapSink, memorySink, parseLevel, parseTemplate, prettyFormatter, rateLimited, renderTemplate, renderValue, resolveFormatter, resolveLevel, resolveStyles, restricted, sampled, selfLog, seqSink, streamSink, templateHash, textFormatter, toJson, withComputed, withEnvironment, withProperties, withProperty, withTraceContext } from './index.js';
3
+
4
+ declare function withMachineName(): Enricher;
5
+ declare function withProcessId(): Enricher;
6
+ /** `MachineName`, `ProcessId` and `Environment` in one go. */
7
+ declare function withProcessInfo(): Enricher;
8
+ type RollingInterval = 'none' | 'minute' | 'hour' | 'day' | 'month';
9
+ interface FileSinkOptions extends SinkOptions {
10
+ /** `logs/app.log`. With a rolling interval the date goes before the extension: `logs/app-20261008.log`. */
11
+ path: string;
12
+ format?: FormatName | Formatter;
13
+ outputTemplate?: string;
14
+ utc?: boolean;
15
+ /** Start a new file every minute / hour / day / month. Default `'none'`. */
16
+ rolling?: RollingInterval;
17
+ /** Start a new file when the current one exceeds this many bytes (`_001`, `_002`… suffixes). */
18
+ maxBytes?: number;
19
+ /** Keep only this many files for the path; older ones are deleted on roll. */
20
+ retainedFileCount?: number;
21
+ /** Also create the directory. Default true. */
22
+ mkdir?: boolean;
23
+ }
24
+ /**
25
+ * Appends one line per event to a file with synchronous writes — nothing is
26
+ * lost when the process dies, at the cost of a syscall per event (fine for
27
+ * the usual thousands of events a second; put a batching sink in front for
28
+ * more). Rolls by time, by size, or both, and prunes old files.
29
+ */
30
+ declare function fileSink(options: FileSinkOptions): Sink;
31
+ interface InterceptConsoleOptions {
32
+ /** Map console methods to levels. Default: log/info → info, debug → debug, warn → warn, error/trace → error. */
33
+ levels?: Partial<Record<'log' | 'info' | 'debug' | 'warn' | 'error' | 'trace', LogLevel | string>>;
34
+ /** `SourceContext` for the captured lines. Default `'console'`. */
35
+ source?: string;
36
+ }
37
+ /**
38
+ * Routes `console.log` and friends through a logger, so a framework's own
39
+ * output (Next.js, a dependency's warnings) comes out in the same format and
40
+ * goes to the same sinks. Returns a function that restores the console.
41
+ */
42
+ declare function interceptConsole(logger: Logger, options?: InterceptConsoleOptions): () => void;
43
+ interface CloseOnExitOptions {
44
+ /** Signals to handle. Default `['SIGINT', 'SIGTERM']`. Pass `[]` to only hook `beforeExit`. */
45
+ signals?: NodeJS.Signals[];
46
+ /** Exit the process after closing, with 128 + the signal number. Default true. */
47
+ exit?: boolean;
48
+ /** Also log uncaught exceptions and unhandled rejections at `fatal` before exiting. Default true. */
49
+ crashes?: boolean;
50
+ }
51
+ /**
52
+ * Flushes and closes the logger when the process is about to end — on
53
+ * `beforeExit`, on SIGINT / SIGTERM, and (by default) after logging a crash.
54
+ * Register once, at startup. Returns a function that removes the handlers.
55
+ */
56
+ declare function closeOnExit(logger: Logger, options?: CloseOnExitOptions): () => void;
57
+
58
+ export { type CloseOnExitOptions, Enricher, type FileSinkOptions, FormatName, Formatter, type InterceptConsoleOptions, LogLevel, Logger, type RollingInterval, Sink, SinkOptions, closeOnExit, fileSink, interceptConsole, withMachineName, withProcessId, withProcessInfo };
package/dist/node.js ADDED
@@ -0,0 +1,341 @@
1
+ import {
2
+ CLEF_MEDIA_TYPE,
3
+ DEFAULT_DESTRUCTURING,
4
+ DEFAULT_OUTPUT_TEMPLATE,
5
+ LEVELS,
6
+ LEVEL_CODES,
7
+ LEVEL_NUMBERS,
8
+ LEVEL_ORDER,
9
+ LevelSwitch,
10
+ LogContext,
11
+ LogEvent,
12
+ Logger,
13
+ LoggerConfiguration,
14
+ Matching,
15
+ Operation,
16
+ SERILOG_LEVEL_NAMES,
17
+ ansi,
18
+ audit,
19
+ batchingSink,
20
+ byExcluding,
21
+ byIncludingOnly,
22
+ byTransforming,
23
+ callbackSink,
24
+ captureError,
25
+ captureProperty,
26
+ captureValue,
27
+ clefFormatter,
28
+ closeAndFlush,
29
+ conditional,
30
+ configure,
31
+ consoleSink,
32
+ createLogger,
33
+ detectColors,
34
+ diagnosticContext,
35
+ formatDate,
36
+ formatError,
37
+ formatLevel,
38
+ formatNumber,
39
+ fromLogContext,
40
+ httpSink,
41
+ isLevel,
42
+ isoTimestamp,
43
+ jsonFormatter,
44
+ jsonReplacer,
45
+ levelAtLeast,
46
+ log,
47
+ mapSink,
48
+ memorySink,
49
+ parseLevel,
50
+ parseTemplate,
51
+ prettyFormatter,
52
+ rateLimited,
53
+ renderTemplate,
54
+ renderValue,
55
+ resolveFormatter,
56
+ resolveLevel,
57
+ resolveStyles,
58
+ restricted,
59
+ sampled,
60
+ selfLog,
61
+ seqSink,
62
+ streamSink,
63
+ templateHash,
64
+ textFormatter,
65
+ toJson,
66
+ withComputed,
67
+ withEnvironment,
68
+ withProperties,
69
+ withProperty,
70
+ withTraceContext
71
+ } from "./chunk-GW7WQELF.js";
72
+
73
+ // src/node.ts
74
+ import { AsyncLocalStorage } from "async_hooks";
75
+ import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, renameSync, statSync, unlinkSync, writeSync } from "fs";
76
+ import { hostname } from "os";
77
+ import { basename, dirname, extname, join, resolve } from "path";
78
+ import { format as utilFormat } from "util";
79
+ if (!LogContext.isAsync) LogContext.use(new AsyncLocalStorage());
80
+ function withMachineName() {
81
+ const name = hostname();
82
+ return (event) => event.addPropertyIfAbsent("MachineName", name);
83
+ }
84
+ function withProcessId() {
85
+ const pid = process.pid;
86
+ return (event) => event.addPropertyIfAbsent("ProcessId", pid);
87
+ }
88
+ function withProcessInfo() {
89
+ const name = hostname();
90
+ const pid = process.pid;
91
+ const env = process.env.NODE_ENV;
92
+ return (event) => {
93
+ event.addPropertyIfAbsent("MachineName", name);
94
+ event.addPropertyIfAbsent("ProcessId", pid);
95
+ if (env) event.addPropertyIfAbsent("Environment", env);
96
+ };
97
+ }
98
+ function fileSink(options) {
99
+ const formatter = resolveFormatter(options.format, { outputTemplate: options.outputTemplate, colors: false, utc: options.utc }, "json");
100
+ const target = resolve(options.path);
101
+ const dir = dirname(target);
102
+ const ext = extname(target);
103
+ const stem = basename(target, ext);
104
+ const rolling = options.rolling ?? "none";
105
+ const maxBytes = options.maxBytes ?? Infinity;
106
+ const retained = options.retainedFileCount ?? Infinity;
107
+ const utc = options.utc ?? false;
108
+ let fd = null;
109
+ let currentPeriod = "";
110
+ let currentPath = "";
111
+ let size = 0;
112
+ let sequence = 0;
113
+ const period = (date) => {
114
+ if (rolling === "none") return "";
115
+ const p = (n) => String(n).padStart(2, "0");
116
+ const y = utc ? date.getUTCFullYear() : date.getFullYear();
117
+ const mo = p((utc ? date.getUTCMonth() : date.getMonth()) + 1);
118
+ const d = p(utc ? date.getUTCDate() : date.getDate());
119
+ const h = p(utc ? date.getUTCHours() : date.getHours());
120
+ const mi = p(utc ? date.getUTCMinutes() : date.getMinutes());
121
+ switch (rolling) {
122
+ case "month":
123
+ return `${y}${mo}`;
124
+ case "day":
125
+ return `${y}${mo}${d}`;
126
+ case "hour":
127
+ return `${y}${mo}${d}${h}`;
128
+ case "minute":
129
+ return `${y}${mo}${d}${h}${mi}`;
130
+ default:
131
+ return "";
132
+ }
133
+ };
134
+ const fileName = (p, seq) => `${stem}${p ? `-${p}` : ""}${seq ? `_${String(seq).padStart(3, "0")}` : ""}${ext}`;
135
+ const isOurs = (name) => name.startsWith(stem) && name.endsWith(ext) && /^[-_0-9]*$/.test(name.slice(stem.length, name.length - ext.length));
136
+ const open = (p) => {
137
+ if (fd !== null) {
138
+ closeSync(fd);
139
+ fd = null;
140
+ }
141
+ if (options.mkdir ?? true) mkdirSync(dir, { recursive: true });
142
+ sequence = 0;
143
+ if (Number.isFinite(maxBytes)) {
144
+ while (existsSync(join(dir, fileName(p, sequence))) && statSync(join(dir, fileName(p, sequence))).size >= maxBytes) sequence++;
145
+ }
146
+ currentPath = join(dir, fileName(p, sequence));
147
+ fd = openSync(currentPath, "a");
148
+ size = fstatSync(fd).size;
149
+ currentPeriod = p;
150
+ prune();
151
+ };
152
+ const prune = () => {
153
+ if (!Number.isFinite(retained)) return;
154
+ try {
155
+ const files = readdirSync(dir).filter(isOurs).map((name) => ({ name, mtime: statSync(join(dir, name)).mtimeMs })).sort((a, b) => b.mtime - a.mtime || b.name.localeCompare(a.name));
156
+ for (const file of files.slice(retained)) {
157
+ if (join(dir, file.name) === currentPath) continue;
158
+ unlinkSync(join(dir, file.name));
159
+ }
160
+ } catch (error) {
161
+ selfLog.write(`fileSink: pruning ${dir} failed`, error);
162
+ }
163
+ };
164
+ return {
165
+ name: `file(${options.path})`,
166
+ restrictedToMinimumLevel: options.restrictedToMinimumLevel,
167
+ audit: options.audit,
168
+ emit(event) {
169
+ const line = `${formatter(event)}
170
+ `;
171
+ const p = period(event.timestamp);
172
+ if (fd === null || p !== currentPeriod) open(p);
173
+ else if (size + line.length > maxBytes && size > 0) {
174
+ closeSync(fd);
175
+ fd = null;
176
+ sequence++;
177
+ const next = join(dir, fileName(p, sequence));
178
+ if (existsSync(next)) {
179
+ while (existsSync(join(dir, fileName(p, sequence)))) sequence++;
180
+ }
181
+ currentPath = join(dir, fileName(p, sequence));
182
+ fd = openSync(currentPath, "a");
183
+ size = 0;
184
+ prune();
185
+ }
186
+ const bytes = Buffer.byteLength(line);
187
+ writeSync(fd, line);
188
+ size += bytes;
189
+ },
190
+ close() {
191
+ if (fd !== null) {
192
+ closeSync(fd);
193
+ fd = null;
194
+ }
195
+ },
196
+ /** @internal for tests */
197
+ get currentPath() {
198
+ return currentPath;
199
+ }
200
+ };
201
+ }
202
+ function interceptConsole(logger, options = {}) {
203
+ const map = {
204
+ log: "info",
205
+ info: "info",
206
+ debug: "debug",
207
+ warn: "warn",
208
+ error: "error",
209
+ trace: "error",
210
+ ...options.levels
211
+ };
212
+ const source = logger.forSource(options.source ?? "console");
213
+ const original = {};
214
+ for (const method of Object.keys(map)) {
215
+ original[method] = console[method];
216
+ const level = parseLevel(map[method]);
217
+ console[method] = ((...args) => {
218
+ const error = args.find((a) => a instanceof Error);
219
+ const rest = error ? args.filter((a) => a !== error) : args;
220
+ const text = rest.length ? utilFormat(...rest) : error ? error.message : "";
221
+ if (error) source.write(level, error, "{Message:l}", text);
222
+ else source.write(level, "{Message:l}", text);
223
+ });
224
+ }
225
+ return () => {
226
+ for (const method of Object.keys(original)) console[method] = original[method];
227
+ };
228
+ }
229
+ var SIGNAL_NUMBERS = { SIGINT: 2, SIGTERM: 15, SIGHUP: 1, SIGQUIT: 3 };
230
+ function closeOnExit(logger, options = {}) {
231
+ const signals = options.signals ?? ["SIGINT", "SIGTERM"];
232
+ const exit = options.exit ?? true;
233
+ let closing = null;
234
+ const close = () => closing ??= logger.close().catch((error) => selfLog.write("close on exit failed", error));
235
+ const onBeforeExit = () => {
236
+ void close();
237
+ };
238
+ const onSignal = (signal) => {
239
+ void close().then(() => {
240
+ if (exit) process.exit(128 + (SIGNAL_NUMBERS[signal] ?? 0));
241
+ });
242
+ };
243
+ const onCrash = (error, kind) => {
244
+ logger.fatal(error instanceof Error ? error : new Error(String(error)), `{Kind:l} \u2014 the process will exit`, kind);
245
+ void close().then(() => {
246
+ if (exit) process.exit(1);
247
+ });
248
+ };
249
+ const onUncaught = (error) => onCrash(error, "Uncaught exception");
250
+ const onUnhandled = (reason) => onCrash(reason, "Unhandled rejection");
251
+ process.on("beforeExit", onBeforeExit);
252
+ for (const signal of signals) process.on(signal, onSignal);
253
+ if (options.crashes ?? true) {
254
+ process.on("uncaughtException", onUncaught);
255
+ process.on("unhandledRejection", onUnhandled);
256
+ }
257
+ return () => {
258
+ process.off("beforeExit", onBeforeExit);
259
+ for (const signal of signals) process.off(signal, onSignal);
260
+ process.off("uncaughtException", onUncaught);
261
+ process.off("unhandledRejection", onUnhandled);
262
+ };
263
+ }
264
+ export {
265
+ CLEF_MEDIA_TYPE,
266
+ DEFAULT_DESTRUCTURING,
267
+ DEFAULT_OUTPUT_TEMPLATE,
268
+ LEVELS,
269
+ LEVEL_CODES,
270
+ LEVEL_NUMBERS,
271
+ LEVEL_ORDER,
272
+ LevelSwitch,
273
+ LogContext,
274
+ LogEvent,
275
+ Logger,
276
+ LoggerConfiguration,
277
+ Matching,
278
+ Operation,
279
+ SERILOG_LEVEL_NAMES,
280
+ ansi,
281
+ audit,
282
+ batchingSink,
283
+ byExcluding,
284
+ byIncludingOnly,
285
+ byTransforming,
286
+ callbackSink,
287
+ captureError,
288
+ captureProperty,
289
+ captureValue,
290
+ clefFormatter,
291
+ closeAndFlush,
292
+ closeOnExit,
293
+ conditional,
294
+ configure,
295
+ consoleSink,
296
+ createLogger,
297
+ detectColors,
298
+ diagnosticContext,
299
+ fileSink,
300
+ formatDate,
301
+ formatError,
302
+ formatLevel,
303
+ formatNumber,
304
+ fromLogContext,
305
+ httpSink,
306
+ interceptConsole,
307
+ isLevel,
308
+ isoTimestamp,
309
+ jsonFormatter,
310
+ jsonReplacer,
311
+ levelAtLeast,
312
+ log,
313
+ mapSink,
314
+ memorySink,
315
+ parseLevel,
316
+ parseTemplate,
317
+ prettyFormatter,
318
+ rateLimited,
319
+ renderTemplate,
320
+ renderValue,
321
+ resolveFormatter,
322
+ resolveLevel,
323
+ resolveStyles,
324
+ restricted,
325
+ sampled,
326
+ selfLog,
327
+ seqSink,
328
+ streamSink,
329
+ templateHash,
330
+ textFormatter,
331
+ toJson,
332
+ withComputed,
333
+ withEnvironment,
334
+ withMachineName,
335
+ withProcessId,
336
+ withProcessInfo,
337
+ withProperties,
338
+ withProperty,
339
+ withTraceContext
340
+ };
341
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts"],"sourcesContent":["/**\n * `@unhingged/logit/node` — the parts that need Node's built-ins: a rolling\n * file sink, host / process enrichers, capturing `console.*`, and\n * flush-on-exit. Everything isomorphic is re-exported, so one import does.\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { closeSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, renameSync, statSync, unlinkSync, writeSync } from 'node:fs';\nimport { hostname } from 'node:os';\nimport { basename, dirname, extname, join, resolve } from 'node:path';\nimport { format as utilFormat } from 'node:util';\nimport { LogContext } from './context';\nimport type { Enricher } from './enrichers';\nimport type { LogEvent } from './event';\nimport { type FormatName, resolveFormatter } from './format';\nimport type { Formatter } from './format/types';\nimport { type LogLevel, parseLevel } from './levels';\nimport type { Logger } from './logger';\nimport { selfLog } from './selflog';\nimport type { Sink, SinkOptions } from './sinks/types';\n\nexport * from './index';\n\n// Make sure ambient context follows async execution even where detection failed.\nif (!LogContext.isAsync) LogContext.use(new AsyncLocalStorage());\n\nexport function withMachineName(): Enricher {\n const name = hostname();\n return (event) => event.addPropertyIfAbsent('MachineName', name);\n}\n\nexport function withProcessId(): Enricher {\n const pid = process.pid;\n return (event) => event.addPropertyIfAbsent('ProcessId', pid);\n}\n\n/** `MachineName`, `ProcessId` and `Environment` in one go. */\nexport function withProcessInfo(): Enricher {\n const name = hostname();\n const pid = process.pid;\n const env = process.env.NODE_ENV;\n return (event) => {\n event.addPropertyIfAbsent('MachineName', name);\n event.addPropertyIfAbsent('ProcessId', pid);\n if (env) event.addPropertyIfAbsent('Environment', env);\n };\n}\n\nexport type RollingInterval = 'none' | 'minute' | 'hour' | 'day' | 'month';\n\nexport interface FileSinkOptions extends SinkOptions {\n /** `logs/app.log`. With a rolling interval the date goes before the extension: `logs/app-20261008.log`. */\n path: string;\n format?: FormatName | Formatter;\n outputTemplate?: string;\n utc?: boolean;\n /** Start a new file every minute / hour / day / month. Default `'none'`. */\n rolling?: RollingInterval;\n /** Start a new file when the current one exceeds this many bytes (`_001`, `_002`… suffixes). */\n maxBytes?: number;\n /** Keep only this many files for the path; older ones are deleted on roll. */\n retainedFileCount?: number;\n /** Also create the directory. Default true. */\n mkdir?: boolean;\n}\n\n/**\n * Appends one line per event to a file with synchronous writes — nothing is\n * lost when the process dies, at the cost of a syscall per event (fine for\n * the usual thousands of events a second; put a batching sink in front for\n * more). Rolls by time, by size, or both, and prunes old files.\n */\nexport function fileSink(options: FileSinkOptions): Sink {\n const formatter = resolveFormatter(options.format, { outputTemplate: options.outputTemplate, colors: false, utc: options.utc }, 'json');\n const target = resolve(options.path);\n const dir = dirname(target);\n const ext = extname(target);\n const stem = basename(target, ext);\n const rolling = options.rolling ?? 'none';\n const maxBytes = options.maxBytes ?? Infinity;\n const retained = options.retainedFileCount ?? Infinity;\n const utc = options.utc ?? false;\n\n let fd: number | null = null;\n let currentPeriod = '';\n let currentPath = '';\n let size = 0;\n let sequence = 0;\n\n const period = (date: Date): string => {\n if (rolling === 'none') return '';\n const p = (n: number) => String(n).padStart(2, '0');\n const y = utc ? date.getUTCFullYear() : date.getFullYear();\n const mo = p((utc ? date.getUTCMonth() : date.getMonth()) + 1);\n const d = p(utc ? date.getUTCDate() : date.getDate());\n const h = p(utc ? date.getUTCHours() : date.getHours());\n const mi = p(utc ? date.getUTCMinutes() : date.getMinutes());\n switch (rolling) {\n case 'month':\n return `${y}${mo}`;\n case 'day':\n return `${y}${mo}${d}`;\n case 'hour':\n return `${y}${mo}${d}${h}`;\n case 'minute':\n return `${y}${mo}${d}${h}${mi}`;\n default:\n return '';\n }\n };\n\n const fileName = (p: string, seq: number): string => `${stem}${p ? `-${p}` : ''}${seq ? `_${String(seq).padStart(3, '0')}` : ''}${ext}`;\n\n const isOurs = (name: string): boolean => name.startsWith(stem) && name.endsWith(ext) && /^[-_0-9]*$/.test(name.slice(stem.length, name.length - ext.length));\n\n const open = (p: string): void => {\n if (fd !== null) {\n closeSync(fd);\n fd = null;\n }\n if (options.mkdir ?? true) mkdirSync(dir, { recursive: true });\n // Find the first free sequence number for the period, continuing where a previous run left off.\n sequence = 0;\n if (Number.isFinite(maxBytes)) {\n while (existsSync(join(dir, fileName(p, sequence))) && statSync(join(dir, fileName(p, sequence))).size >= maxBytes) sequence++;\n }\n currentPath = join(dir, fileName(p, sequence));\n fd = openSync(currentPath, 'a');\n size = fstatSync(fd).size;\n currentPeriod = p;\n prune();\n };\n\n const prune = (): void => {\n if (!Number.isFinite(retained)) return;\n try {\n const files = readdirSync(dir)\n .filter(isOurs)\n .map((name) => ({ name, mtime: statSync(join(dir, name)).mtimeMs }))\n .sort((a, b) => b.mtime - a.mtime || b.name.localeCompare(a.name));\n for (const file of files.slice(retained)) {\n if (join(dir, file.name) === currentPath) continue;\n unlinkSync(join(dir, file.name));\n }\n } catch (error) {\n selfLog.write(`fileSink: pruning ${dir} failed`, error);\n }\n };\n\n return {\n name: `file(${options.path})`,\n restrictedToMinimumLevel: options.restrictedToMinimumLevel,\n audit: options.audit,\n emit(event: LogEvent) {\n const line = `${formatter(event)}\\n`;\n const p = period(event.timestamp);\n if (fd === null || p !== currentPeriod) open(p);\n else if (size + line.length > maxBytes && size > 0) {\n closeSync(fd!);\n fd = null;\n sequence++;\n const next = join(dir, fileName(p, sequence));\n if (existsSync(next)) {\n // A previous run filled this number too: keep going until a free one.\n while (existsSync(join(dir, fileName(p, sequence)))) sequence++;\n }\n currentPath = join(dir, fileName(p, sequence));\n fd = openSync(currentPath, 'a');\n size = 0;\n prune();\n }\n const bytes = Buffer.byteLength(line);\n writeSync(fd!, line);\n size += bytes;\n },\n close() {\n if (fd !== null) {\n closeSync(fd);\n fd = null;\n }\n },\n /** @internal for tests */\n get currentPath() {\n return currentPath;\n },\n } as Sink & { readonly currentPath: string };\n}\n\n/** Renames `a.log` to `a.log.1`… — kept simple on purpose; `renameSync` is here for size rolling of a fixed name if ever needed. */\nvoid renameSync;\n\nexport interface InterceptConsoleOptions {\n /** Map console methods to levels. Default: log/info → info, debug → debug, warn → warn, error/trace → error. */\n levels?: Partial<Record<'log' | 'info' | 'debug' | 'warn' | 'error' | 'trace', LogLevel | string>>;\n /** `SourceContext` for the captured lines. Default `'console'`. */\n source?: string;\n}\n\n/**\n * Routes `console.log` and friends through a logger, so a framework's own\n * output (Next.js, a dependency's warnings) comes out in the same format and\n * goes to the same sinks. Returns a function that restores the console.\n */\nexport function interceptConsole(logger: Logger, options: InterceptConsoleOptions = {}): () => void {\n const map = {\n log: 'info',\n info: 'info',\n debug: 'debug',\n warn: 'warn',\n error: 'error',\n trace: 'error',\n ...options.levels,\n } as const;\n const source = logger.forSource(options.source ?? 'console');\n const original: Partial<Record<keyof typeof map, (...args: unknown[]) => void>> = {};\n for (const method of Object.keys(map) as Array<keyof typeof map>) {\n original[method] = console[method] as (...args: unknown[]) => void;\n const level = parseLevel(map[method]);\n console[method] = ((...args: unknown[]) => {\n const error = args.find((a): a is Error => a instanceof Error);\n const rest = error ? args.filter((a) => a !== error) : args;\n const text = rest.length ? utilFormat(...rest) : error ? error.message : '';\n if (error) source.write(level, error, '{Message:l}', text);\n else source.write(level, '{Message:l}', text);\n }) as typeof console.log;\n }\n return () => {\n for (const method of Object.keys(original) as Array<keyof typeof map>) console[method] = original[method] as never;\n };\n}\n\nexport interface CloseOnExitOptions {\n /** Signals to handle. Default `['SIGINT', 'SIGTERM']`. Pass `[]` to only hook `beforeExit`. */\n signals?: NodeJS.Signals[];\n /** Exit the process after closing, with 128 + the signal number. Default true. */\n exit?: boolean;\n /** Also log uncaught exceptions and unhandled rejections at `fatal` before exiting. Default true. */\n crashes?: boolean;\n}\n\nconst SIGNAL_NUMBERS: Record<string, number> = { SIGINT: 2, SIGTERM: 15, SIGHUP: 1, SIGQUIT: 3 };\n\n/**\n * Flushes and closes the logger when the process is about to end — on\n * `beforeExit`, on SIGINT / SIGTERM, and (by default) after logging a crash.\n * Register once, at startup. Returns a function that removes the handlers.\n */\nexport function closeOnExit(logger: Logger, options: CloseOnExitOptions = {}): () => void {\n const signals = options.signals ?? ['SIGINT', 'SIGTERM'];\n const exit = options.exit ?? true;\n let closing: Promise<void> | null = null;\n const close = () => (closing ??= logger.close().catch((error) => selfLog.write('close on exit failed', error)));\n\n const onBeforeExit = () => {\n void close();\n };\n const onSignal = (signal: NodeJS.Signals) => {\n void close().then(() => {\n if (exit) process.exit(128 + (SIGNAL_NUMBERS[signal] ?? 0));\n });\n };\n const onCrash = (error: unknown, kind: string) => {\n logger.fatal(error instanceof Error ? error : new Error(String(error)), `{Kind:l} — the process will exit`, kind);\n void close().then(() => {\n if (exit) process.exit(1);\n });\n };\n const onUncaught = (error: unknown) => onCrash(error, 'Uncaught exception');\n const onUnhandled = (reason: unknown) => onCrash(reason, 'Unhandled rejection');\n\n process.on('beforeExit', onBeforeExit);\n for (const signal of signals) process.on(signal, onSignal);\n if (options.crashes ?? true) {\n process.on('uncaughtException', onUncaught);\n process.on('unhandledRejection', onUnhandled);\n }\n return () => {\n process.off('beforeExit', onBeforeExit);\n for (const signal of signals) process.off(signal, onSignal);\n process.off('uncaughtException', onUncaught);\n process.off('unhandledRejection', onUnhandled);\n };\n}\n"],"mappings":"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","names":[]}