@loggerjs/node 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <a href="https://jskits.github.io/loggerjs/"><img src="https://raw.githubusercontent.com/jskits/loggerjs/main/docs/public/logo.png" alt="LoggerJS logo" width="64" height="64" /></a>
3
+ </p>
4
+
1
5
  # @loggerjs/node
2
6
 
3
7
  > Node.js transports, AsyncLocalStorage context, and 16 automatic integrations — re-exports all of `@loggerjs/core`.
@@ -9,8 +13,7 @@
9
13
  The Node platform package for [LoggerJS](../../README.md). It re-exports the entire `@loggerjs/core` API and adds stdout/stderr/file/HTTP/syslog/worker transports, an AsyncLocalStorage context bridge, and integrations that turn process crashes, HTTP frameworks, clients, and queues into structured logs — all opt-in.
10
14
 
11
15
  `@loggerjs/node` is smoke-tested from packed packages on Node 20.19.0, 22, and
12
- 24. The repository development toolchain uses Node >=22.13.0; that root
13
- requirement does not raise the published package runtime floor.
16
+ 24. The repository development toolchain uses Node >=22.13.0; that root requirement does not raise the published package runtime floor.
14
17
 
15
18
  ## Install
16
19
 
@@ -80,25 +83,11 @@ rotatingFileTransport({ path: "audit.log", maxBytes: 10 * 1024 * 1024 });
80
83
  | `nodeSyslogTransport` | RFC 5424 syslog over UDP/TCP |
81
84
  | `workerTransport` | a worker thread (encodes batches with a codec, optional buffer transfer, ready/ack lifecycle, fallback on worker failure) |
82
85
 
83
- File and process-stream transports share the same internal destination logic for
84
- write callbacks, drain waiting, `minLength` buffering, close, and sync crash
85
- flush. Use `await flush()` for normal shutdown. Use `flushSync()` only on fatal
86
- paths, or configure `fileTransport({ sync: true })` when every write must reach
87
- the filesystem before the log call returns.
88
-
89
- `workerTransport()` is fire-and-forget unless you opt into lifecycle checks.
90
- Use `readyTimeoutMs` for workers that send `{ type: "loggerjs:ready" }`, and
91
- `ackTimeoutMs` for workers that acknowledge `{ type: "loggerjs:batch", id }`
92
- with `{ type: "loggerjs:batch:ack", id }`. Pending batches fall back or are
93
- counted as drops when readiness, posting, ack, or worker exit fails. Set
94
- `autoEnd: false` for shared workers you close elsewhere. Explicit
95
- `transport.ready()` / `logger.ready()` waits for the ready handshake when
96
- `readyTimeoutMs` is configured.
97
-
98
- Call `installLoggerDiagnosticsChannel()` to publish LoggerJS internal
99
- diagnostics to Node `diagnostics_channel` channels such as `loggerjs.dispatch`,
100
- `loggerjs.transport`, `loggerjs.flush`, `loggerjs.encode`, and
101
- `loggerjs.worker`.
86
+ File and process-stream transports share the same internal destination logic for write callbacks, drain waiting, `minLength` buffering, close, and sync crash flush. Use `await flush()` for normal shutdown. Use `flushSync()` only on fatal paths, or configure `fileTransport({ sync: true })` when every write must reach the filesystem before the log call returns.
87
+
88
+ `workerTransport()` is fire-and-forget unless you opt into lifecycle checks. Use `readyTimeoutMs` for workers that send `{ type: "loggerjs:ready" }`, and `ackTimeoutMs` for workers that acknowledge `{ type: "loggerjs:batch", id }` with `{ type: "loggerjs:batch:ack", id }`. Pending batches fall back or are counted as drops when readiness, posting, ack, or worker exit fails. Set `autoEnd: false` for shared workers you close elsewhere. Explicit `transport.ready()` / `logger.ready()` waits for the ready handshake when `readyTimeoutMs` is configured.
89
+
90
+ Call `installLoggerDiagnosticsChannel()` to publish LoggerJS internal diagnostics to Node `diagnostics_channel` channels such as `loggerjs.dispatch`, `loggerjs.transport`, `loggerjs.flush`, `loggerjs.encode`, and `loggerjs.worker`.
102
91
 
103
92
  ## Integrations (16)
104
93
 
@@ -126,7 +115,9 @@ await withContext({ requestId: "req_123" }, async () => {
126
115
 
127
116
  ## Subpath exports
128
117
 
129
- Transports — `transport-stdout` · `transport-file` · `transport-rotating-file` · `transport-http` · `transport-syslog` · `transport-worker` · `payload-transforms` · `logger-diagnostics` · `context`
118
+ Transports — `transport-stdout` · `transport-file` · `transport-rotating-file` · `transport-http` · `transport-syslog` · `transport-worker`
119
+
120
+ Utilities — `payload-transforms` · `logger-diagnostics` · `context`
130
121
 
131
122
  Integrations — `integration-process` · `integration-cli` · `integration-diagnostics` · `integration-serverless` · `integration-express` · `integration-fastify` · `integration-koa` · `integration-nest` · `integration-hapi` · `integration-fetch` · `integration-http-client` · `integration-redis` · `integration-prisma` · `integration-database` · `integration-queue` · `integration-bullmq`
132
123
 
@@ -165,8 +165,9 @@ function createNodeFileDestination(options) {
165
165
  const flags = options.flags ?? (options.append === false ? "w" : "a");
166
166
  if (options.mkdir) (0, fs.mkdirSync)((0, path.dirname)(options.path), { recursive: true });
167
167
  let fd;
168
+ const syncFlags = options.sync ? flags : "a";
168
169
  const getFd = () => {
169
- fd ??= (0, fs.openSync)(options.path, flags);
170
+ fd ??= (0, fs.openSync)(options.path, syncFlags);
170
171
  return fd;
171
172
  };
172
173
  const syncWrite = (payload) => {
@@ -1 +1 @@
1
- {"version":3,"file":"file-transport.cjs","names":[],"sources":["../src/node-destination.ts","../src/file-transport.ts"],"sourcesContent":["import { closeSync, createWriteStream, mkdirSync, openSync, writeSync, type WriteStream } from \"fs\";\nimport { dirname } from \"path\";\nimport type { TransportContext } from \"@loggerjs/core\";\nimport type { WritableLike } from \"./internal-types\";\n\nexport interface NodeDestination {\n stream?: WriteStream;\n write: (payload: string | Uint8Array, context?: TransportContext) => void;\n flush: () => Promise<void>;\n flushSync: () => void;\n close: () => Promise<void>;\n closeSync: () => void;\n releaseSync?: () => void;\n}\n\nexport interface NodeStreamDestinationOptions {\n name: string;\n stream: WritableLike;\n minLength?: number;\n ignoreEpipe?: boolean;\n reportOperation?: string;\n syncWrite?: (payload: string | Uint8Array) => void;\n}\n\nexport interface NodeFileDestinationOptions {\n name: string;\n path: string;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\ninterface FlushWaiter {\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nconst encoder = new TextEncoder();\n\nconst normalizeError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\nfunction byteLength(payload: string | Uint8Array): number {\n return typeof payload === \"string\" ? encoder.encode(payload).byteLength : payload.byteLength;\n}\n\nfunction coalescePayloads(payloads: Array<string | Uint8Array>): string | Uint8Array {\n if (payloads.every((payload) => typeof payload === \"string\")) {\n return payloads.join(\"\");\n }\n const buffers = payloads.map((payload) =>\n typeof payload === \"string\" ? Buffer.from(payload) : Buffer.from(payload),\n );\n return Buffer.concat(buffers);\n}\n\nfunction isEpipe(error: unknown): boolean {\n return (\n Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === \"EPIPE\"\n );\n}\n\nfunction normalizeMinLength(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value) || value <= 0) return 0;\n return Math.floor(value);\n}\n\nexport function createNodeStreamDestination(\n options: NodeStreamDestinationOptions,\n): NodeDestination {\n const minLength = normalizeMinLength(options.minLength);\n const ignoreEpipe = options.ignoreEpipe ?? false;\n const waiters: FlushWaiter[] = [];\n const buffer: Array<string | Uint8Array> = [];\n const pendingPayloads: Array<string | Uint8Array> = [];\n let bufferedBytes = 0;\n let pendingWrites = 0;\n let pendingDrains = 0;\n let lastError: Error | undefined;\n let lastContext: TransportContext | undefined;\n let closed = false;\n\n const reportInternalError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) return;\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name,\n operation,\n });\n };\n\n const settleWaitersIfIdle = () => {\n if (pendingWrites + pendingDrains > 0) return;\n if (waiters.length === 0) return;\n const error = lastError;\n lastError = undefined;\n for (const waiter of waiters.splice(0)) {\n if (error) waiter.reject(error);\n else waiter.resolve();\n }\n };\n\n const recordError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) {\n closed = true;\n return;\n }\n lastError = normalizeError(error);\n reportInternalError(lastError, operation);\n };\n\n const onStreamError = (error: Error) => {\n recordError(error, \"stream-error\");\n settleWaitersIfIdle();\n };\n const offError = () => options.stream.off?.(\"error\", onStreamError);\n options.stream.on?.(\"error\", onStreamError);\n\n const waitForDrain = () => {\n if (!options.stream.once) return;\n pendingDrains += 1;\n options.stream.once(\"drain\", () => {\n pendingDrains -= 1;\n settleWaitersIfIdle();\n });\n };\n\n const removePending = (payload: string | Uint8Array) => {\n const index = pendingPayloads.indexOf(payload);\n if (index >= 0) pendingPayloads.splice(index, 1);\n };\n\n const writeNow = (payload: string | Uint8Array) => {\n if (closed) return;\n pendingPayloads.push(payload);\n pendingWrites += 1;\n try {\n const result = options.stream.write(payload, (error?: Error | null) => {\n removePending(payload);\n pendingWrites -= 1;\n if (error) recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n });\n if (result === false) waitForDrain();\n } catch (error) {\n removePending(payload);\n pendingWrites -= 1;\n recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n }\n };\n\n const flushBuffered = () => {\n if (buffer.length === 0) return;\n const payload = coalescePayloads(buffer.splice(0));\n bufferedBytes = 0;\n writeNow(payload);\n };\n\n const destination: NodeDestination = {\n write(payload, context) {\n if (closed) return;\n lastContext = context;\n if (minLength > 0) {\n buffer.push(payload);\n bufferedBytes += byteLength(payload);\n if (bufferedBytes >= minLength) flushBuffered();\n return;\n }\n writeNow(payload);\n },\n flush() {\n flushBuffered();\n if (pendingWrites + pendingDrains === 0) {\n const error = lastError;\n lastError = undefined;\n if (error) return Promise.reject(error);\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n flushSync() {\n if (!options.syncWrite) {\n flushBuffered();\n return;\n }\n for (const payload of buffer.splice(0)) options.syncWrite(payload);\n bufferedBytes = 0;\n for (const payload of pendingPayloads.splice(0)) options.syncWrite(payload);\n },\n close() {\n return destination.flush().then(\n () =>\n new Promise<void>((resolve, reject) => {\n if (!options.stream.end) {\n closed = true;\n offError();\n resolve();\n return;\n }\n options.stream.end((error?: Error | null) => {\n closed = true;\n offError();\n if (error && !(ignoreEpipe && isEpipe(error))) reject(error);\n else resolve();\n });\n }),\n (error: unknown) => {\n offError();\n return Promise.reject(error);\n },\n );\n },\n closeSync() {\n destination.flushSync();\n closed = true;\n offError();\n },\n };\n return destination;\n}\n\nexport function createNodeFileDestination(options: NodeFileDestinationOptions): NodeDestination {\n const flags = options.flags ?? (options.append === false ? \"w\" : \"a\");\n if (options.mkdir) mkdirSync(dirname(options.path), { recursive: true });\n\n let fd: number | undefined;\n const getFd = () => {\n fd ??= openSync(options.path, flags);\n return fd;\n };\n const syncWrite = (payload: string | Uint8Array) => {\n if (typeof payload === \"string\") writeSync(getFd(), payload);\n else writeSync(getFd(), payload);\n };\n\n if (options.sync) {\n let closed = false;\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n write(payload) {\n if (closed) return;\n syncWrite(payload);\n },\n flush() {\n return Promise.resolve();\n },\n flushSync() {},\n close() {\n releaseSync();\n closed = true;\n return Promise.resolve();\n },\n closeSync() {\n releaseSync();\n closed = true;\n },\n releaseSync,\n };\n }\n\n const stream = createWriteStream(options.path, { flags });\n const destination = createNodeStreamDestination({\n name: options.name,\n stream,\n minLength: options.minLength,\n syncWrite,\n });\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n ...destination,\n stream,\n close() {\n return destination.close().finally(releaseSync);\n },\n closeSync() {\n destination.closeSync();\n releaseSync();\n },\n releaseSync,\n };\n}\n","import {\n ndjsonCodec,\n toLevelValue,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n} from \"@loggerjs/core\";\nimport type { WriteStream } from \"fs\";\nimport { createNodeFileDestination } from \"./node-destination\";\n\nexport interface FileTransportOptions {\n path: string;\n name?: string;\n codec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\nexport interface FileTransport extends Transport {\n stream?: WriteStream;\n flushSync: () => void;\n}\n\nexport function fileTransport(options: FileTransportOptions): FileTransport {\n const codec = options.codec ?? ndjsonCodec();\n const destination = createNodeFileDestination({\n name: options.name ?? \"file\",\n path: options.path,\n flags: options.flags,\n append: options.append,\n mkdir: options.mkdir,\n sync: options.sync,\n minLength: options.minLength,\n });\n\n return {\n name: options.name ?? \"file\",\n minLevel: options.minLevel,\n stream: destination.stream,\n log(event: LogEvent, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n destination.write(codec.encode(event), context);\n },\n flush: destination.flush,\n flushSync: destination.flushSync,\n close: destination.close,\n };\n}\n"],"mappings":";;;;;AAuCA,MAAM,UAAU,IAAI,YAAY;AAEhC,MAAM,kBAAkB,UACtB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAE1D,SAAS,WAAW,SAAsC;CACxD,OAAO,OAAO,YAAY,WAAW,QAAQ,OAAO,OAAO,CAAC,CAAC,aAAa,QAAQ;AACpF;AAEA,SAAS,iBAAiB,UAA2D;CACnF,IAAI,SAAS,OAAO,YAAY,OAAO,YAAY,QAAQ,GACzD,OAAO,SAAS,KAAK,EAAE;CAEzB,MAAM,UAAU,SAAS,KAAK,YAC5B,OAAO,YAAY,WAAW,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,OAAO,CAC1E;CACA,OAAO,OAAO,OAAO,OAAO;AAC9B;AAEA,SAAS,QAAQ,OAAyB;CACxC,OACE,QAAQ,KAAK,KAAK,OAAO,UAAU,YAAa,MAA6B,SAAS;AAE1F;AAEA,SAAS,mBAAmB,OAAmC;CAC7D,IAAI,UAAU,KAAA,KAAa,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG,OAAO;CACzE,OAAO,KAAK,MAAM,KAAK;AACzB;AAEA,SAAgB,4BACd,SACiB;CACjB,MAAM,YAAY,mBAAmB,QAAQ,SAAS;CACtD,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,UAAyB,CAAC;CAChC,MAAM,SAAqC,CAAC;CAC5C,MAAM,kBAA8C,CAAC;CACrD,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CACpB,IAAI;CACJ,IAAI;CACJ,IAAI,SAAS;CAEb,MAAM,uBAAuB,OAAgB,cAAsB;EACjE,IAAI,eAAe,QAAQ,KAAK,GAAG;EACnC,aAAa,oBAAoB,OAAO;GACtC,OAAO;GACP,WAAW,QAAQ;GACnB;EACF,CAAC;CACH;CAEA,MAAM,4BAA4B;EAChC,IAAI,gBAAgB,gBAAgB,GAAG;EACvC,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,QAAQ;EACd,YAAY,KAAA;EACZ,KAAK,MAAM,UAAU,QAAQ,OAAO,CAAC,GACnC,IAAI,OAAO,OAAO,OAAO,KAAK;OACzB,OAAO,QAAQ;CAExB;CAEA,MAAM,eAAe,OAAgB,cAAsB;EACzD,IAAI,eAAe,QAAQ,KAAK,GAAG;GACjC,SAAS;GACT;EACF;EACA,YAAY,eAAe,KAAK;EAChC,oBAAoB,WAAW,SAAS;CAC1C;CAEA,MAAM,iBAAiB,UAAiB;EACtC,YAAY,OAAO,cAAc;EACjC,oBAAoB;CACtB;CACA,MAAM,iBAAiB,QAAQ,OAAO,MAAM,SAAS,aAAa;CAClE,QAAQ,OAAO,KAAK,SAAS,aAAa;CAE1C,MAAM,qBAAqB;EACzB,IAAI,CAAC,QAAQ,OAAO,MAAM;EAC1B,iBAAiB;EACjB,QAAQ,OAAO,KAAK,eAAe;GACjC,iBAAiB;GACjB,oBAAoB;EACtB,CAAC;CACH;CAEA,MAAM,iBAAiB,YAAiC;EACtD,MAAM,QAAQ,gBAAgB,QAAQ,OAAO;EAC7C,IAAI,SAAS,GAAG,gBAAgB,OAAO,OAAO,CAAC;CACjD;CAEA,MAAM,YAAY,YAAiC;EACjD,IAAI,QAAQ;EACZ,gBAAgB,KAAK,OAAO;EAC5B,iBAAiB;EACjB,IAAI;GAOF,IANe,QAAQ,OAAO,MAAM,UAAU,UAAyB;IACrE,cAAc,OAAO;IACrB,iBAAiB;IACjB,IAAI,OAAO,YAAY,OAAO,QAAQ,mBAAmB,OAAO;IAChE,oBAAoB;GACtB,CACS,MAAM,OAAO,aAAa;EACrC,SAAS,OAAO;GACd,cAAc,OAAO;GACrB,iBAAiB;GACjB,YAAY,OAAO,QAAQ,mBAAmB,OAAO;GACrD,oBAAoB;EACtB;CACF;CAEA,MAAM,sBAAsB;EAC1B,IAAI,OAAO,WAAW,GAAG;EACzB,MAAM,UAAU,iBAAiB,OAAO,OAAO,CAAC,CAAC;EACjD,gBAAgB;EAChB,SAAS,OAAO;CAClB;CAEA,MAAM,cAA+B;EACnC,MAAM,SAAS,SAAS;GACtB,IAAI,QAAQ;GACZ,cAAc;GACd,IAAI,YAAY,GAAG;IACjB,OAAO,KAAK,OAAO;IACnB,iBAAiB,WAAW,OAAO;IACnC,IAAI,iBAAiB,WAAW,cAAc;IAC9C;GACF;GACA,SAAS,OAAO;EAClB;EACA,QAAQ;GACN,cAAc;GACd,IAAI,gBAAgB,kBAAkB,GAAG;IACvC,MAAM,QAAQ;IACd,YAAY,KAAA;IACZ,IAAI,OAAO,OAAO,QAAQ,OAAO,KAAK;IACtC,OAAO,QAAQ,QAAQ;GACzB;GACA,OAAO,IAAI,SAAe,SAAS,WAAW;IAC5C,QAAQ,KAAK;KAAE;KAAS;IAAO,CAAC;GAClC,CAAC;EACH;EACA,YAAY;GACV,IAAI,CAAC,QAAQ,WAAW;IACtB,cAAc;IACd;GACF;GACA,KAAK,MAAM,WAAW,OAAO,OAAO,CAAC,GAAG,QAAQ,UAAU,OAAO;GACjE,gBAAgB;GAChB,KAAK,MAAM,WAAW,gBAAgB,OAAO,CAAC,GAAG,QAAQ,UAAU,OAAO;EAC5E;EACA,QAAQ;GACN,OAAO,YAAY,MAAM,CAAC,CAAC,WAEvB,IAAI,SAAe,SAAS,WAAW;IACrC,IAAI,CAAC,QAAQ,OAAO,KAAK;KACvB,SAAS;KACT,SAAS;KACT,QAAQ;KACR;IACF;IACA,QAAQ,OAAO,KAAK,UAAyB;KAC3C,SAAS;KACT,SAAS;KACT,IAAI,SAAS,EAAE,eAAe,QAAQ,KAAK,IAAI,OAAO,KAAK;UACtD,QAAQ;IACf,CAAC;GACH,CAAC,IACF,UAAmB;IAClB,SAAS;IACT,OAAO,QAAQ,OAAO,KAAK;GAC7B,CACF;EACF;EACA,YAAY;GACV,YAAY,UAAU;GACtB,SAAS;GACT,SAAS;EACX;CACF;CACA,OAAO;AACT;AAEA,SAAgB,0BAA0B,SAAsD;CAC9F,MAAM,QAAQ,QAAQ,UAAU,QAAQ,WAAW,QAAQ,MAAM;CACjE,IAAI,QAAQ,OAAO,CAAA,GAAA,GAAA,UAAA,EAAA,GAAA,KAAA,QAAA,CAAkB,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAEvE,IAAI;CACJ,MAAM,cAAc;EAClB,QAAA,GAAA,GAAA,SAAA,CAAgB,QAAQ,MAAM,KAAK;EACnC,OAAO;CACT;CACA,MAAM,aAAa,YAAiC;EAClD,IAAI,OAAO,YAAY,UAAU,CAAA,GAAA,GAAA,UAAA,CAAU,MAAM,GAAG,OAAO;OACtD,CAAA,GAAA,GAAA,UAAA,CAAU,MAAM,GAAG,OAAO;CACjC;CAEA,IAAI,QAAQ,MAAM;EAChB,IAAI,SAAS;EACb,MAAM,oBAAoB;GACxB,IAAI,OAAO,KAAA,GAAW;IACpB,CAAA,GAAA,GAAA,UAAA,CAAU,EAAE;IACZ,KAAK,KAAA;GACP;EACF;EACA,OAAO;GACL,MAAM,SAAS;IACb,IAAI,QAAQ;IACZ,UAAU,OAAO;GACnB;GACA,QAAQ;IACN,OAAO,QAAQ,QAAQ;GACzB;GACA,YAAY,CAAC;GACb,QAAQ;IACN,YAAY;IACZ,SAAS;IACT,OAAO,QAAQ,QAAQ;GACzB;GACA,YAAY;IACV,YAAY;IACZ,SAAS;GACX;GACA;EACF;CACF;CAEA,MAAM,UAAA,GAAA,GAAA,kBAAA,CAA2B,QAAQ,MAAM,EAAE,MAAM,CAAC;CACxD,MAAM,cAAc,4BAA4B;EAC9C,MAAM,QAAQ;EACd;EACA,WAAW,QAAQ;EACnB;CACF,CAAC;CACD,MAAM,oBAAoB;EACxB,IAAI,OAAO,KAAA,GAAW;GACpB,CAAA,GAAA,GAAA,UAAA,CAAU,EAAE;GACZ,KAAK,KAAA;EACP;CACF;CACA,OAAO;EACL,GAAG;EACH;EACA,QAAQ;GACN,OAAO,YAAY,MAAM,CAAC,CAAC,QAAQ,WAAW;EAChD;EACA,YAAY;GACV,YAAY,UAAU;GACtB,YAAY;EACd;EACA;CACF;AACF;;;AC3QA,SAAgB,cAAc,SAA8C;CAC1E,MAAM,QAAQ,QAAQ,UAAA,GAAA,eAAA,YAAA,CAAqB;CAC3C,MAAM,cAAc,0BAA0B;EAC5C,MAAM,QAAQ,QAAQ;EACtB,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,MAAM,QAAQ;EACd,WAAW,QAAQ;CACrB,CAAC;CAED,OAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ;EAClB,QAAQ,YAAY;EACpB,IAAI,OAAiB,SAAS;GAC5B,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,SAAA,GAAA,eAAA,aAAA,CAAqB,QAAQ,QAAQ,GAAG;GACpF,YAAY,MAAM,MAAM,OAAO,KAAK,GAAG,OAAO;EAChD;EACA,OAAO,YAAY;EACnB,WAAW,YAAY;EACvB,OAAO,YAAY;CACrB;AACF"}
1
+ {"version":3,"file":"file-transport.cjs","names":[],"sources":["../src/node-destination.ts","../src/file-transport.ts"],"sourcesContent":["import { closeSync, createWriteStream, mkdirSync, openSync, writeSync, type WriteStream } from \"fs\";\nimport { dirname } from \"path\";\nimport type { TransportContext } from \"@loggerjs/core\";\nimport type { WritableLike } from \"./internal-types\";\n\nexport interface NodeDestination {\n stream?: WriteStream;\n write: (payload: string | Uint8Array, context?: TransportContext) => void;\n flush: () => Promise<void>;\n flushSync: () => void;\n close: () => Promise<void>;\n closeSync: () => void;\n releaseSync?: () => void;\n}\n\nexport interface NodeStreamDestinationOptions {\n name: string;\n stream: WritableLike;\n minLength?: number;\n ignoreEpipe?: boolean;\n reportOperation?: string;\n syncWrite?: (payload: string | Uint8Array) => void;\n}\n\nexport interface NodeFileDestinationOptions {\n name: string;\n path: string;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\ninterface FlushWaiter {\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nconst encoder = new TextEncoder();\n\nconst normalizeError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\nfunction byteLength(payload: string | Uint8Array): number {\n return typeof payload === \"string\" ? encoder.encode(payload).byteLength : payload.byteLength;\n}\n\nfunction coalescePayloads(payloads: Array<string | Uint8Array>): string | Uint8Array {\n if (payloads.every((payload) => typeof payload === \"string\")) {\n return payloads.join(\"\");\n }\n const buffers = payloads.map((payload) =>\n typeof payload === \"string\" ? Buffer.from(payload) : Buffer.from(payload),\n );\n return Buffer.concat(buffers);\n}\n\nfunction isEpipe(error: unknown): boolean {\n return (\n Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === \"EPIPE\"\n );\n}\n\nfunction normalizeMinLength(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value) || value <= 0) return 0;\n return Math.floor(value);\n}\n\nexport function createNodeStreamDestination(\n options: NodeStreamDestinationOptions,\n): NodeDestination {\n const minLength = normalizeMinLength(options.minLength);\n const ignoreEpipe = options.ignoreEpipe ?? false;\n const waiters: FlushWaiter[] = [];\n const buffer: Array<string | Uint8Array> = [];\n const pendingPayloads: Array<string | Uint8Array> = [];\n let bufferedBytes = 0;\n let pendingWrites = 0;\n let pendingDrains = 0;\n let lastError: Error | undefined;\n let lastContext: TransportContext | undefined;\n let closed = false;\n\n const reportInternalError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) return;\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name,\n operation,\n });\n };\n\n const settleWaitersIfIdle = () => {\n if (pendingWrites + pendingDrains > 0) return;\n if (waiters.length === 0) return;\n const error = lastError;\n lastError = undefined;\n for (const waiter of waiters.splice(0)) {\n if (error) waiter.reject(error);\n else waiter.resolve();\n }\n };\n\n const recordError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) {\n closed = true;\n return;\n }\n lastError = normalizeError(error);\n reportInternalError(lastError, operation);\n };\n\n const onStreamError = (error: Error) => {\n recordError(error, \"stream-error\");\n settleWaitersIfIdle();\n };\n const offError = () => options.stream.off?.(\"error\", onStreamError);\n options.stream.on?.(\"error\", onStreamError);\n\n const waitForDrain = () => {\n if (!options.stream.once) return;\n pendingDrains += 1;\n options.stream.once(\"drain\", () => {\n pendingDrains -= 1;\n settleWaitersIfIdle();\n });\n };\n\n const removePending = (payload: string | Uint8Array) => {\n const index = pendingPayloads.indexOf(payload);\n if (index >= 0) pendingPayloads.splice(index, 1);\n };\n\n const writeNow = (payload: string | Uint8Array) => {\n if (closed) return;\n pendingPayloads.push(payload);\n pendingWrites += 1;\n try {\n const result = options.stream.write(payload, (error?: Error | null) => {\n removePending(payload);\n pendingWrites -= 1;\n if (error) recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n });\n if (result === false) waitForDrain();\n } catch (error) {\n removePending(payload);\n pendingWrites -= 1;\n recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n }\n };\n\n const flushBuffered = () => {\n if (buffer.length === 0) return;\n const payload = coalescePayloads(buffer.splice(0));\n bufferedBytes = 0;\n writeNow(payload);\n };\n\n const destination: NodeDestination = {\n write(payload, context) {\n if (closed) return;\n lastContext = context;\n if (minLength > 0) {\n buffer.push(payload);\n bufferedBytes += byteLength(payload);\n if (bufferedBytes >= minLength) flushBuffered();\n return;\n }\n writeNow(payload);\n },\n flush() {\n flushBuffered();\n if (pendingWrites + pendingDrains === 0) {\n const error = lastError;\n lastError = undefined;\n if (error) return Promise.reject(error);\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n flushSync() {\n if (!options.syncWrite) {\n flushBuffered();\n return;\n }\n for (const payload of buffer.splice(0)) options.syncWrite(payload);\n bufferedBytes = 0;\n for (const payload of pendingPayloads.splice(0)) options.syncWrite(payload);\n },\n close() {\n return destination.flush().then(\n () =>\n new Promise<void>((resolve, reject) => {\n if (!options.stream.end) {\n closed = true;\n offError();\n resolve();\n return;\n }\n options.stream.end((error?: Error | null) => {\n closed = true;\n offError();\n if (error && !(ignoreEpipe && isEpipe(error))) reject(error);\n else resolve();\n });\n }),\n (error: unknown) => {\n offError();\n return Promise.reject(error);\n },\n );\n },\n closeSync() {\n destination.flushSync();\n closed = true;\n offError();\n },\n };\n return destination;\n}\n\nexport function createNodeFileDestination(options: NodeFileDestinationOptions): NodeDestination {\n const flags = options.flags ?? (options.append === false ? \"w\" : \"a\");\n if (options.mkdir) mkdirSync(dirname(options.path), { recursive: true });\n\n let fd: number | undefined;\n // In sync mode this fd is the only writer, so it honors the configured flags.\n // In async stream mode the stream already opened (and, for \"w\", truncated)\n // the file; the crash-path fd must append or it would erase earlier logs.\n const syncFlags = options.sync ? flags : \"a\";\n const getFd = () => {\n fd ??= openSync(options.path, syncFlags);\n return fd;\n };\n const syncWrite = (payload: string | Uint8Array) => {\n if (typeof payload === \"string\") writeSync(getFd(), payload);\n else writeSync(getFd(), payload);\n };\n\n if (options.sync) {\n let closed = false;\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n write(payload) {\n if (closed) return;\n syncWrite(payload);\n },\n flush() {\n return Promise.resolve();\n },\n flushSync() {},\n close() {\n releaseSync();\n closed = true;\n return Promise.resolve();\n },\n closeSync() {\n releaseSync();\n closed = true;\n },\n releaseSync,\n };\n }\n\n const stream = createWriteStream(options.path, { flags });\n const destination = createNodeStreamDestination({\n name: options.name,\n stream,\n minLength: options.minLength,\n syncWrite,\n });\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n ...destination,\n stream,\n close() {\n return destination.close().finally(releaseSync);\n },\n closeSync() {\n destination.closeSync();\n releaseSync();\n },\n releaseSync,\n };\n}\n","import {\n ndjsonCodec,\n toLevelValue,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n} from \"@loggerjs/core\";\nimport type { WriteStream } from \"fs\";\nimport { createNodeFileDestination } from \"./node-destination\";\n\nexport interface FileTransportOptions {\n path: string;\n name?: string;\n codec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\nexport interface FileTransport extends Transport {\n stream?: WriteStream;\n flushSync: () => void;\n}\n\nexport function fileTransport(options: FileTransportOptions): FileTransport {\n const codec = options.codec ?? ndjsonCodec();\n const destination = createNodeFileDestination({\n name: options.name ?? \"file\",\n path: options.path,\n flags: options.flags,\n append: options.append,\n mkdir: options.mkdir,\n sync: options.sync,\n minLength: options.minLength,\n });\n\n return {\n name: options.name ?? \"file\",\n minLevel: options.minLevel,\n stream: destination.stream,\n log(event: LogEvent, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n destination.write(codec.encode(event), context);\n },\n flush: destination.flush,\n flushSync: destination.flushSync,\n close: destination.close,\n };\n}\n"],"mappings":"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"}
@@ -164,8 +164,9 @@ function createNodeFileDestination(options) {
164
164
  const flags = options.flags ?? (options.append === false ? "w" : "a");
165
165
  if (options.mkdir) mkdirSync(dirname(options.path), { recursive: true });
166
166
  let fd;
167
+ const syncFlags = options.sync ? flags : "a";
167
168
  const getFd = () => {
168
- fd ??= openSync(options.path, flags);
169
+ fd ??= openSync(options.path, syncFlags);
169
170
  return fd;
170
171
  };
171
172
  const syncWrite = (payload) => {
@@ -1 +1 @@
1
- {"version":3,"file":"file-transport.js","names":[],"sources":["../src/node-destination.ts","../src/file-transport.ts"],"sourcesContent":["import { closeSync, createWriteStream, mkdirSync, openSync, writeSync, type WriteStream } from \"fs\";\nimport { dirname } from \"path\";\nimport type { TransportContext } from \"@loggerjs/core\";\nimport type { WritableLike } from \"./internal-types\";\n\nexport interface NodeDestination {\n stream?: WriteStream;\n write: (payload: string | Uint8Array, context?: TransportContext) => void;\n flush: () => Promise<void>;\n flushSync: () => void;\n close: () => Promise<void>;\n closeSync: () => void;\n releaseSync?: () => void;\n}\n\nexport interface NodeStreamDestinationOptions {\n name: string;\n stream: WritableLike;\n minLength?: number;\n ignoreEpipe?: boolean;\n reportOperation?: string;\n syncWrite?: (payload: string | Uint8Array) => void;\n}\n\nexport interface NodeFileDestinationOptions {\n name: string;\n path: string;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\ninterface FlushWaiter {\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nconst encoder = new TextEncoder();\n\nconst normalizeError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\nfunction byteLength(payload: string | Uint8Array): number {\n return typeof payload === \"string\" ? encoder.encode(payload).byteLength : payload.byteLength;\n}\n\nfunction coalescePayloads(payloads: Array<string | Uint8Array>): string | Uint8Array {\n if (payloads.every((payload) => typeof payload === \"string\")) {\n return payloads.join(\"\");\n }\n const buffers = payloads.map((payload) =>\n typeof payload === \"string\" ? Buffer.from(payload) : Buffer.from(payload),\n );\n return Buffer.concat(buffers);\n}\n\nfunction isEpipe(error: unknown): boolean {\n return (\n Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === \"EPIPE\"\n );\n}\n\nfunction normalizeMinLength(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value) || value <= 0) return 0;\n return Math.floor(value);\n}\n\nexport function createNodeStreamDestination(\n options: NodeStreamDestinationOptions,\n): NodeDestination {\n const minLength = normalizeMinLength(options.minLength);\n const ignoreEpipe = options.ignoreEpipe ?? false;\n const waiters: FlushWaiter[] = [];\n const buffer: Array<string | Uint8Array> = [];\n const pendingPayloads: Array<string | Uint8Array> = [];\n let bufferedBytes = 0;\n let pendingWrites = 0;\n let pendingDrains = 0;\n let lastError: Error | undefined;\n let lastContext: TransportContext | undefined;\n let closed = false;\n\n const reportInternalError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) return;\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name,\n operation,\n });\n };\n\n const settleWaitersIfIdle = () => {\n if (pendingWrites + pendingDrains > 0) return;\n if (waiters.length === 0) return;\n const error = lastError;\n lastError = undefined;\n for (const waiter of waiters.splice(0)) {\n if (error) waiter.reject(error);\n else waiter.resolve();\n }\n };\n\n const recordError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) {\n closed = true;\n return;\n }\n lastError = normalizeError(error);\n reportInternalError(lastError, operation);\n };\n\n const onStreamError = (error: Error) => {\n recordError(error, \"stream-error\");\n settleWaitersIfIdle();\n };\n const offError = () => options.stream.off?.(\"error\", onStreamError);\n options.stream.on?.(\"error\", onStreamError);\n\n const waitForDrain = () => {\n if (!options.stream.once) return;\n pendingDrains += 1;\n options.stream.once(\"drain\", () => {\n pendingDrains -= 1;\n settleWaitersIfIdle();\n });\n };\n\n const removePending = (payload: string | Uint8Array) => {\n const index = pendingPayloads.indexOf(payload);\n if (index >= 0) pendingPayloads.splice(index, 1);\n };\n\n const writeNow = (payload: string | Uint8Array) => {\n if (closed) return;\n pendingPayloads.push(payload);\n pendingWrites += 1;\n try {\n const result = options.stream.write(payload, (error?: Error | null) => {\n removePending(payload);\n pendingWrites -= 1;\n if (error) recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n });\n if (result === false) waitForDrain();\n } catch (error) {\n removePending(payload);\n pendingWrites -= 1;\n recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n }\n };\n\n const flushBuffered = () => {\n if (buffer.length === 0) return;\n const payload = coalescePayloads(buffer.splice(0));\n bufferedBytes = 0;\n writeNow(payload);\n };\n\n const destination: NodeDestination = {\n write(payload, context) {\n if (closed) return;\n lastContext = context;\n if (minLength > 0) {\n buffer.push(payload);\n bufferedBytes += byteLength(payload);\n if (bufferedBytes >= minLength) flushBuffered();\n return;\n }\n writeNow(payload);\n },\n flush() {\n flushBuffered();\n if (pendingWrites + pendingDrains === 0) {\n const error = lastError;\n lastError = undefined;\n if (error) return Promise.reject(error);\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n flushSync() {\n if (!options.syncWrite) {\n flushBuffered();\n return;\n }\n for (const payload of buffer.splice(0)) options.syncWrite(payload);\n bufferedBytes = 0;\n for (const payload of pendingPayloads.splice(0)) options.syncWrite(payload);\n },\n close() {\n return destination.flush().then(\n () =>\n new Promise<void>((resolve, reject) => {\n if (!options.stream.end) {\n closed = true;\n offError();\n resolve();\n return;\n }\n options.stream.end((error?: Error | null) => {\n closed = true;\n offError();\n if (error && !(ignoreEpipe && isEpipe(error))) reject(error);\n else resolve();\n });\n }),\n (error: unknown) => {\n offError();\n return Promise.reject(error);\n },\n );\n },\n closeSync() {\n destination.flushSync();\n closed = true;\n offError();\n },\n };\n return destination;\n}\n\nexport function createNodeFileDestination(options: NodeFileDestinationOptions): NodeDestination {\n const flags = options.flags ?? (options.append === false ? \"w\" : \"a\");\n if (options.mkdir) mkdirSync(dirname(options.path), { recursive: true });\n\n let fd: number | undefined;\n const getFd = () => {\n fd ??= openSync(options.path, flags);\n return fd;\n };\n const syncWrite = (payload: string | Uint8Array) => {\n if (typeof payload === \"string\") writeSync(getFd(), payload);\n else writeSync(getFd(), payload);\n };\n\n if (options.sync) {\n let closed = false;\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n write(payload) {\n if (closed) return;\n syncWrite(payload);\n },\n flush() {\n return Promise.resolve();\n },\n flushSync() {},\n close() {\n releaseSync();\n closed = true;\n return Promise.resolve();\n },\n closeSync() {\n releaseSync();\n closed = true;\n },\n releaseSync,\n };\n }\n\n const stream = createWriteStream(options.path, { flags });\n const destination = createNodeStreamDestination({\n name: options.name,\n stream,\n minLength: options.minLength,\n syncWrite,\n });\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n ...destination,\n stream,\n close() {\n return destination.close().finally(releaseSync);\n },\n closeSync() {\n destination.closeSync();\n releaseSync();\n },\n releaseSync,\n };\n}\n","import {\n ndjsonCodec,\n toLevelValue,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n} from \"@loggerjs/core\";\nimport type { WriteStream } from \"fs\";\nimport { createNodeFileDestination } from \"./node-destination\";\n\nexport interface FileTransportOptions {\n path: string;\n name?: string;\n codec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\nexport interface FileTransport extends Transport {\n stream?: WriteStream;\n flushSync: () => void;\n}\n\nexport function fileTransport(options: FileTransportOptions): FileTransport {\n const codec = options.codec ?? ndjsonCodec();\n const destination = createNodeFileDestination({\n name: options.name ?? \"file\",\n path: options.path,\n flags: options.flags,\n append: options.append,\n mkdir: options.mkdir,\n sync: options.sync,\n minLength: options.minLength,\n });\n\n return {\n name: options.name ?? \"file\",\n minLevel: options.minLevel,\n stream: destination.stream,\n log(event: LogEvent, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n destination.write(codec.encode(event), context);\n },\n flush: destination.flush,\n flushSync: destination.flushSync,\n close: destination.close,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-transport.js","names":[],"sources":["../src/node-destination.ts","../src/file-transport.ts"],"sourcesContent":["import { closeSync, createWriteStream, mkdirSync, openSync, writeSync, type WriteStream } from \"fs\";\nimport { dirname } from \"path\";\nimport type { TransportContext } from \"@loggerjs/core\";\nimport type { WritableLike } from \"./internal-types\";\n\nexport interface NodeDestination {\n stream?: WriteStream;\n write: (payload: string | Uint8Array, context?: TransportContext) => void;\n flush: () => Promise<void>;\n flushSync: () => void;\n close: () => Promise<void>;\n closeSync: () => void;\n releaseSync?: () => void;\n}\n\nexport interface NodeStreamDestinationOptions {\n name: string;\n stream: WritableLike;\n minLength?: number;\n ignoreEpipe?: boolean;\n reportOperation?: string;\n syncWrite?: (payload: string | Uint8Array) => void;\n}\n\nexport interface NodeFileDestinationOptions {\n name: string;\n path: string;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\ninterface FlushWaiter {\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nconst encoder = new TextEncoder();\n\nconst normalizeError = (error: unknown): Error =>\n error instanceof Error ? error : new Error(String(error));\n\nfunction byteLength(payload: string | Uint8Array): number {\n return typeof payload === \"string\" ? encoder.encode(payload).byteLength : payload.byteLength;\n}\n\nfunction coalescePayloads(payloads: Array<string | Uint8Array>): string | Uint8Array {\n if (payloads.every((payload) => typeof payload === \"string\")) {\n return payloads.join(\"\");\n }\n const buffers = payloads.map((payload) =>\n typeof payload === \"string\" ? Buffer.from(payload) : Buffer.from(payload),\n );\n return Buffer.concat(buffers);\n}\n\nfunction isEpipe(error: unknown): boolean {\n return (\n Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === \"EPIPE\"\n );\n}\n\nfunction normalizeMinLength(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value) || value <= 0) return 0;\n return Math.floor(value);\n}\n\nexport function createNodeStreamDestination(\n options: NodeStreamDestinationOptions,\n): NodeDestination {\n const minLength = normalizeMinLength(options.minLength);\n const ignoreEpipe = options.ignoreEpipe ?? false;\n const waiters: FlushWaiter[] = [];\n const buffer: Array<string | Uint8Array> = [];\n const pendingPayloads: Array<string | Uint8Array> = [];\n let bufferedBytes = 0;\n let pendingWrites = 0;\n let pendingDrains = 0;\n let lastError: Error | undefined;\n let lastContext: TransportContext | undefined;\n let closed = false;\n\n const reportInternalError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) return;\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name,\n operation,\n });\n };\n\n const settleWaitersIfIdle = () => {\n if (pendingWrites + pendingDrains > 0) return;\n if (waiters.length === 0) return;\n const error = lastError;\n lastError = undefined;\n for (const waiter of waiters.splice(0)) {\n if (error) waiter.reject(error);\n else waiter.resolve();\n }\n };\n\n const recordError = (error: unknown, operation: string) => {\n if (ignoreEpipe && isEpipe(error)) {\n closed = true;\n return;\n }\n lastError = normalizeError(error);\n reportInternalError(lastError, operation);\n };\n\n const onStreamError = (error: Error) => {\n recordError(error, \"stream-error\");\n settleWaitersIfIdle();\n };\n const offError = () => options.stream.off?.(\"error\", onStreamError);\n options.stream.on?.(\"error\", onStreamError);\n\n const waitForDrain = () => {\n if (!options.stream.once) return;\n pendingDrains += 1;\n options.stream.once(\"drain\", () => {\n pendingDrains -= 1;\n settleWaitersIfIdle();\n });\n };\n\n const removePending = (payload: string | Uint8Array) => {\n const index = pendingPayloads.indexOf(payload);\n if (index >= 0) pendingPayloads.splice(index, 1);\n };\n\n const writeNow = (payload: string | Uint8Array) => {\n if (closed) return;\n pendingPayloads.push(payload);\n pendingWrites += 1;\n try {\n const result = options.stream.write(payload, (error?: Error | null) => {\n removePending(payload);\n pendingWrites -= 1;\n if (error) recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n });\n if (result === false) waitForDrain();\n } catch (error) {\n removePending(payload);\n pendingWrites -= 1;\n recordError(error, options.reportOperation ?? \"write\");\n settleWaitersIfIdle();\n }\n };\n\n const flushBuffered = () => {\n if (buffer.length === 0) return;\n const payload = coalescePayloads(buffer.splice(0));\n bufferedBytes = 0;\n writeNow(payload);\n };\n\n const destination: NodeDestination = {\n write(payload, context) {\n if (closed) return;\n lastContext = context;\n if (minLength > 0) {\n buffer.push(payload);\n bufferedBytes += byteLength(payload);\n if (bufferedBytes >= minLength) flushBuffered();\n return;\n }\n writeNow(payload);\n },\n flush() {\n flushBuffered();\n if (pendingWrites + pendingDrains === 0) {\n const error = lastError;\n lastError = undefined;\n if (error) return Promise.reject(error);\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n flushSync() {\n if (!options.syncWrite) {\n flushBuffered();\n return;\n }\n for (const payload of buffer.splice(0)) options.syncWrite(payload);\n bufferedBytes = 0;\n for (const payload of pendingPayloads.splice(0)) options.syncWrite(payload);\n },\n close() {\n return destination.flush().then(\n () =>\n new Promise<void>((resolve, reject) => {\n if (!options.stream.end) {\n closed = true;\n offError();\n resolve();\n return;\n }\n options.stream.end((error?: Error | null) => {\n closed = true;\n offError();\n if (error && !(ignoreEpipe && isEpipe(error))) reject(error);\n else resolve();\n });\n }),\n (error: unknown) => {\n offError();\n return Promise.reject(error);\n },\n );\n },\n closeSync() {\n destination.flushSync();\n closed = true;\n offError();\n },\n };\n return destination;\n}\n\nexport function createNodeFileDestination(options: NodeFileDestinationOptions): NodeDestination {\n const flags = options.flags ?? (options.append === false ? \"w\" : \"a\");\n if (options.mkdir) mkdirSync(dirname(options.path), { recursive: true });\n\n let fd: number | undefined;\n // In sync mode this fd is the only writer, so it honors the configured flags.\n // In async stream mode the stream already opened (and, for \"w\", truncated)\n // the file; the crash-path fd must append or it would erase earlier logs.\n const syncFlags = options.sync ? flags : \"a\";\n const getFd = () => {\n fd ??= openSync(options.path, syncFlags);\n return fd;\n };\n const syncWrite = (payload: string | Uint8Array) => {\n if (typeof payload === \"string\") writeSync(getFd(), payload);\n else writeSync(getFd(), payload);\n };\n\n if (options.sync) {\n let closed = false;\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n write(payload) {\n if (closed) return;\n syncWrite(payload);\n },\n flush() {\n return Promise.resolve();\n },\n flushSync() {},\n close() {\n releaseSync();\n closed = true;\n return Promise.resolve();\n },\n closeSync() {\n releaseSync();\n closed = true;\n },\n releaseSync,\n };\n }\n\n const stream = createWriteStream(options.path, { flags });\n const destination = createNodeStreamDestination({\n name: options.name,\n stream,\n minLength: options.minLength,\n syncWrite,\n });\n const releaseSync = () => {\n if (fd !== undefined) {\n closeSync(fd);\n fd = undefined;\n }\n };\n return {\n ...destination,\n stream,\n close() {\n return destination.close().finally(releaseSync);\n },\n closeSync() {\n destination.closeSync();\n releaseSync();\n },\n releaseSync,\n };\n}\n","import {\n ndjsonCodec,\n toLevelValue,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n} from \"@loggerjs/core\";\nimport type { WriteStream } from \"fs\";\nimport { createNodeFileDestination } from \"./node-destination\";\n\nexport interface FileTransportOptions {\n path: string;\n name?: string;\n codec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n flags?: string;\n append?: boolean;\n mkdir?: boolean;\n sync?: boolean;\n minLength?: number;\n}\n\nexport interface FileTransport extends Transport {\n stream?: WriteStream;\n flushSync: () => void;\n}\n\nexport function fileTransport(options: FileTransportOptions): FileTransport {\n const codec = options.codec ?? ndjsonCodec();\n const destination = createNodeFileDestination({\n name: options.name ?? \"file\",\n path: options.path,\n flags: options.flags,\n append: options.append,\n mkdir: options.mkdir,\n sync: options.sync,\n minLength: options.minLength,\n });\n\n return {\n name: options.name ?? \"file\",\n minLevel: options.minLevel,\n stream: destination.stream,\n log(event: LogEvent, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n destination.write(codec.encode(event), context);\n },\n flush: destination.flush,\n flushSync: destination.flushSync,\n close: destination.close,\n };\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -172,8 +172,9 @@ function createNodeFileDestination(options) {
172
172
  const flags = options.flags ?? (options.append === false ? "w" : "a");
173
173
  if (options.mkdir) (0, fs.mkdirSync)((0, path.dirname)(options.path), { recursive: true });
174
174
  let fd;
175
+ const syncFlags = options.sync ? flags : "a";
175
176
  const getFd = () => {
176
- fd ??= (0, fs.openSync)(options.path, flags);
177
+ fd ??= (0, fs.openSync)(options.path, syncFlags);
177
178
  return fd;
178
179
  };
179
180
  const syncWrite = (payload) => {
@@ -1953,6 +1954,15 @@ const signalExitCodes = {
1953
1954
  SIGINT: 130,
1954
1955
  SIGTERM: 143
1955
1956
  };
1957
+ function unhandledRejectionsMode() {
1958
+ const flags = [...process.execArgv, ...(process.env.NODE_OPTIONS ?? "").split(/\s+/)];
1959
+ let mode = "throw";
1960
+ for (const flag of flags) {
1961
+ const match = /^--unhandled-rejections=(.+)$/.exec(flag);
1962
+ if (match?.[1]) mode = match[1];
1963
+ }
1964
+ return mode;
1965
+ }
1956
1966
  function captureProcessIntegration(options = {}) {
1957
1967
  const uncaughtException = options.uncaughtException ?? true;
1958
1968
  const unhandledRejection = options.unhandledRejection ?? true;
@@ -1963,6 +1973,8 @@ function captureProcessIntegration(options = {}) {
1963
1973
  const signals = options.signals ?? ["SIGTERM"];
1964
1974
  const exitOnSignal = options.exitOnSignal ?? true;
1965
1975
  const exitOnUncaught = options.exitOnUncaught ?? true;
1976
+ const rejectionsMode = unhandledRejectionsMode();
1977
+ const exitOnUnhandledRejection = options.exitOnUnhandledRejection ?? (rejectionsMode === "throw" || rejectionsMode === "strict");
1966
1978
  const flushTimeoutMs = options.flushTimeoutMs ?? 250;
1967
1979
  const exitFn = options.exitFn ?? ((code) => process.exit(code));
1968
1980
  return {
@@ -2008,7 +2020,7 @@ function captureProcessIntegration(options = {}) {
2008
2020
  if (unhandledRejection) {
2009
2021
  const onUnhandledRejection = (reason) => {
2010
2022
  capture({
2011
- level: "error",
2023
+ level: exitOnUnhandledRejection ? "fatal" : "error",
2012
2024
  message: reason instanceof Error ? reason.message : "Unhandled promise rejection",
2013
2025
  error: reason,
2014
2026
  props: {
@@ -2016,6 +2028,14 @@ function captureProcessIntegration(options = {}) {
2016
2028
  reason: (0, _loggerjs_core.normalizeValue)(reason, { maxDepth: 5 })
2017
2029
  }
2018
2030
  });
2031
+ if (exitOnUnhandledRejection) {
2032
+ flushSync();
2033
+ flushBounded().finally(() => exitFn(1));
2034
+ return;
2035
+ }
2036
+ if (options.exitOnUnhandledRejection === void 0) {
2037
+ if (rejectionsMode === "warn-with-error-code") process.exitCode = 1;
2038
+ }
2019
2039
  flushBestEffort();
2020
2040
  };
2021
2041
  process.on("unhandledRejection", onUnhandledRejection);