logquill 0.4.0 → 1.0.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/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('crypto');
4
3
  var async_hooks = require('async_hooks');
4
+ var crypto = require('crypto');
5
5
  var module$1 = require('module');
6
6
  var zlib = require('zlib');
7
7
  var fs = require('fs');
@@ -59,6 +59,7 @@ function createRecord(params) {
59
59
 
60
60
  // src/core/formatter.ts
61
61
  var JSONFormatter = class {
62
+ /** Returns `JSON.stringify(record)`. */
62
63
  format(record) {
63
64
  return JSON.stringify(record);
64
65
  }
@@ -77,6 +78,7 @@ var FunctionPlugin = class {
77
78
 
78
79
  // src/plugins/context-plugin.ts
79
80
  var ContextPlugin = class {
81
+ /** Fixed key/value pairs merged into every record's `meta`. */
80
82
  context;
81
83
  constructor(context) {
82
84
  this.context = context;
@@ -85,7 +87,151 @@ var ContextPlugin = class {
85
87
  return { ...record, meta: { ...this.context, ...record.meta } };
86
88
  }
87
89
  };
90
+ var contextStore = new async_hooks.AsyncLocalStorage();
91
+ function currentContext() {
92
+ return contextStore.getStore() ?? {};
93
+ }
94
+ function bindContext(values, fn) {
95
+ return contextStore.run({ ...currentContext(), ...values }, fn);
96
+ }
97
+
98
+ // src/plugins/rate-limit-plugin.ts
99
+ function defaultKey(record) {
100
+ return `${record.logger}:${record.level}`;
101
+ }
102
+ var RateLimitPlugin = class {
103
+ /** Records allowed per key within a `perSeconds` window before further records for that key are dropped. */
104
+ maxRecords;
105
+ /** Length, in seconds, of each key's rolling window. */
106
+ perSeconds;
107
+ /** Distinct keys tracked at once before the least-recently-seen one is evicted. */
108
+ maxKeys;
109
+ keyFunc;
110
+ clock;
111
+ windows = /* @__PURE__ */ new Map();
112
+ constructor(maxRecords, perSeconds, options = {}) {
113
+ if (maxRecords < 1) {
114
+ throw new Error(`maxRecords must be at least 1, got ${String(maxRecords)}`);
115
+ }
116
+ if (perSeconds <= 0) {
117
+ throw new Error(`perSeconds must be positive, got ${String(perSeconds)}`);
118
+ }
119
+ this.maxRecords = maxRecords;
120
+ this.perSeconds = perSeconds;
121
+ this.keyFunc = options.keyFunc ?? defaultKey;
122
+ this.maxKeys = options.maxKeys ?? 1e3;
123
+ this.clock = options.clock ?? (() => performance.now() / 1e3);
124
+ }
125
+ beforeLog(record) {
126
+ const key = this.keyFunc(record);
127
+ const now = this.clock();
128
+ const window = this.windows.get(key);
129
+ if (window === void 0 || now - window.start >= this.perSeconds) {
130
+ this.windows.delete(key);
131
+ this.windows.set(key, { start: now, count: 1 });
132
+ while (this.windows.size > this.maxKeys) {
133
+ this.evictOldest();
134
+ }
135
+ return record;
136
+ }
137
+ this.windows.delete(key);
138
+ this.windows.set(key, window);
139
+ if (window.count >= this.maxRecords) {
140
+ return null;
141
+ }
142
+ window.count += 1;
143
+ return record;
144
+ }
145
+ evictOldest() {
146
+ const oldest = this.windows.keys().next();
147
+ if (!oldest.done) {
148
+ this.windows.delete(oldest.value);
149
+ }
150
+ }
151
+ };
152
+
153
+ // src/bridges/level-dispatch.ts
154
+ function callAtLevel(log, level, message, meta) {
155
+ switch (level) {
156
+ case 5 /* TRACE */:
157
+ log.trace(message, meta);
158
+ return;
159
+ case 10 /* DEBUG */:
160
+ log.debug(message, meta);
161
+ return;
162
+ case 20 /* INFO */:
163
+ log.info(message, meta);
164
+ return;
165
+ case 30 /* WARN */:
166
+ log.warn(message, meta);
167
+ return;
168
+ case 40 /* ERROR */:
169
+ log.error(message, meta);
170
+ return;
171
+ case 50 /* FATAL */:
172
+ log.fatal(message, meta);
173
+ return;
174
+ }
175
+ }
176
+
177
+ // src/bridges/pino-destination.ts
178
+ var DEFAULT_PINO_LEVEL_MAP = {
179
+ 10: 5 /* TRACE */,
180
+ 20: 10 /* DEBUG */,
181
+ 30: 20 /* INFO */,
182
+ 40: 30 /* WARN */,
183
+ 50: 40 /* ERROR */,
184
+ 60: 50 /* FATAL */
185
+ };
186
+ var LogQuillPinoDestination = class {
187
+ /**
188
+ * Pino only recognizes a bare object passed as its sole argument
189
+ * (`pino(destination)`, without a separate options argument) as a
190
+ * destination stream, rather than an options object, if it looks
191
+ * stream-like — checking for `.writable`/`._writableState`, the same
192
+ * duck-typing `stream.Writable` itself satisfies. Without this, `pino()`
193
+ * would silently fall back to writing to `process.stdout` instead.
194
+ */
195
+ writable = true;
196
+ target;
197
+ levelMap;
198
+ constructor(logquillLogger, options = {}) {
199
+ this.target = logquillLogger;
200
+ this.levelMap = options.levelMap ?? DEFAULT_PINO_LEVEL_MAP;
201
+ }
202
+ /** Node `Writable`-compatible write hook — splits `chunk` into NDJSON lines and dispatches each as a `Logger` call. */
203
+ write(chunk) {
204
+ for (const line of chunk.split("\n")) {
205
+ if (line.trim()) {
206
+ this.writeLine(line);
207
+ }
208
+ }
209
+ return true;
210
+ }
211
+ writeLine(line) {
212
+ let parsed;
213
+ try {
214
+ parsed = JSON.parse(line);
215
+ } catch {
216
+ return;
217
+ }
218
+ if (typeof parsed !== "object" || parsed === null) {
219
+ return;
220
+ }
221
+ const entry = parsed;
222
+ const pinoLevel = typeof entry.level === "number" ? entry.level : 30;
223
+ const message = typeof entry.msg === "string" ? entry.msg : "";
224
+ const meta = {};
225
+ for (const key of Object.keys(entry)) {
226
+ if (key !== "level" && key !== "msg") {
227
+ meta[key] = entry[key];
228
+ }
229
+ }
230
+ callAtLevel(this.target, this.levelMap[pinoLevel] ?? 20 /* INFO */, message, meta);
231
+ }
232
+ };
88
233
  var RunPlugin = class {
234
+ /** Id stamped onto `meta.runId` for every record this instance processes. */
89
235
  runId;
90
236
  step = 0;
91
237
  constructor(options = {}) {
@@ -148,6 +294,7 @@ function defaultResolveActiveOtelTraceId() {
148
294
  }
149
295
  }
150
296
  var TraceContextPlugin = class {
297
+ /** `meta` key the trace id is written to. */
151
298
  traceKey;
152
299
  explicitTraceparent;
153
300
  resolveActiveOtelTraceId;
@@ -179,10 +326,58 @@ var TraceContextPlugin = class {
179
326
  }
180
327
  };
181
328
 
329
+ // src/plugins/otel-span-processor.ts
330
+ var OTEL_STATUS_CODE_ERROR = 2;
331
+ var OtelSpanProcessor = class {
332
+ log;
333
+ attributesKey;
334
+ constructor(log, options = {}) {
335
+ this.log = log;
336
+ this.attributesKey = options.attributesKey ?? "attributes";
337
+ }
338
+ /** Called by the tracer provider when a span starts — emits `.action()`. */
339
+ onStart(span) {
340
+ this.log.action(span.name, this.baseMeta(span));
341
+ }
342
+ /** Called by the tracer provider when a span ends — emits `.observation()`, or `.error()` if the span's status is an error. */
343
+ onEnd(span) {
344
+ const meta = {
345
+ ...this.baseMeta(span),
346
+ durationMs: span.duration[0] * 1e3 + span.duration[1] / 1e6
347
+ };
348
+ if (span.status.code === OTEL_STATUS_CODE_ERROR) {
349
+ this.log.error(span.name, {
350
+ ...meta,
351
+ error: span.status.message ?? "span ended with an error status"
352
+ });
353
+ return;
354
+ }
355
+ this.log.observation(span.name, meta);
356
+ }
357
+ /** No internal buffering to flush — every span is forwarded to the `Logger` immediately. */
358
+ async forceFlush() {
359
+ }
360
+ /** No resources of its own to release; flush/close the wrapped `Logger` separately. */
361
+ async shutdown() {
362
+ }
363
+ baseMeta(span) {
364
+ const meta = { spanId: span.spanContext().spanId };
365
+ const parentSpanId = span.parentSpanContext?.spanId ?? span.parentSpanId;
366
+ if (parentSpanId) {
367
+ meta.parentSpanId = parentSpanId;
368
+ }
369
+ if (Object.keys(span.attributes).length > 0) {
370
+ meta[this.attributesKey] = span.attributes;
371
+ }
372
+ return meta;
373
+ }
374
+ };
375
+
182
376
  // src/plugins/redact-plugin.ts
183
377
  var DEFAULT_REDACTED_KEYS = ["password", "token", "secret", "api_key", "authorization"];
184
378
  var RedactPlugin = class {
185
379
  keys;
380
+ /** Placeholder a matched value is replaced with. */
186
381
  replacement;
187
382
  constructor(options = {}) {
188
383
  this.keys = new Set((options.keys ?? DEFAULT_REDACTED_KEYS).map((key) => key.toLowerCase()));
@@ -206,7 +401,9 @@ var DEFAULT_PII_PATTERNS = {
206
401
  };
207
402
  var MAX_DEPTH = 50;
208
403
  var PIIRedactPlugin = class {
404
+ /** Named patterns scanned for in every string `meta` value. */
209
405
  patterns;
406
+ /** Placeholder a matched substring is replaced with. */
210
407
  replacement;
211
408
  constructor(options = {}) {
212
409
  this.patterns = options.patterns ?? DEFAULT_PII_PATTERNS;
@@ -254,11 +451,17 @@ var PIIRedactPlugin = class {
254
451
 
255
452
  // src/plugins/sampling-plugin.ts
256
453
  var SamplingPlugin = class {
454
+ /** Fraction of non-elevated records kept, in `[0, 1]`. */
257
455
  rate;
456
+ /** `meta` key holding the trace/run id used to group buffered records. */
258
457
  traceKey;
458
+ /** A record at or above this level elevates its whole trace. */
259
459
  elevateAt;
460
+ /** Transports buffered records are flushed straight to on elevation; `undefined` disables tail-based elevation. */
260
461
  transports;
462
+ /** Total buffered records allowed across every trace before the oldest trace is evicted. */
261
463
  maxBufferedRecords;
464
+ /** Distinct trace ids held at once before the oldest is evicted. */
262
465
  maxTraces;
263
466
  rng;
264
467
  buffer = /* @__PURE__ */ new Map();
@@ -404,8 +607,11 @@ function defaultDedupeKey(record) {
404
607
  return `${record.level}:${record.logger}:${record.message}`;
405
608
  }
406
609
  var AlertingPlugin = class {
610
+ /** A record at or above this level fires an alert. */
407
611
  threshold;
612
+ /** How long a dedupe window stays open before a collapsed follow-up alert (if any) fires. */
408
613
  dedupeWindowMs;
614
+ /** Distinct concurrent dedupe keys tracked at once; beyond this, new keys are dropped rather than tracked. */
409
615
  maxTrackedKeys;
410
616
  dedupeKeyFn;
411
617
  windows = /* @__PURE__ */ new Map();
@@ -479,6 +685,7 @@ function formatMessage(record, occurrences) {
479
685
  return `[${record.level}] ${record.logger}: ${record.message}${suffix}`;
480
686
  }
481
687
  var SlackAlertPlugin = class extends AlertingPlugin {
688
+ /** Slack "Incoming Webhook" URL every alert is posted to. */
482
689
  webhookUrl;
483
690
  sender;
484
691
  constructor(webhookUrl, options = {}) {
@@ -508,6 +715,7 @@ async function fetchPagerDutySender(body) {
508
715
  }
509
716
  }
510
717
  var PagerDutyAlertPlugin = class extends AlertingPlugin {
718
+ /** PagerDuty Events API v2 integration key every alert is sent under. */
511
719
  routingKey;
512
720
  sender;
513
721
  constructor(routingKey, options = {}) {
@@ -537,9 +745,13 @@ var PagerDutyAlertPlugin = class extends AlertingPlugin {
537
745
 
538
746
  // src/plugins/email-alert-plugin.ts
539
747
  var EmailAlertPlugin = class extends AlertingPlugin {
748
+ /** SMTP server hostname. */
540
749
  smtpHost;
750
+ /** SMTP server port. */
541
751
  smtpPort;
752
+ /** Envelope `From` address for every alert. */
542
753
  fromAddr;
754
+ /** Envelope `To` addresses for every alert. */
543
755
  toAddrs;
544
756
  username;
545
757
  password;
@@ -605,10 +817,12 @@ var EmailAlertPlugin = class extends AlertingPlugin {
605
817
 
606
818
  // src/transports/transport.ts
607
819
  var Transport = class {
820
+ /** Turns a `LogRecord` into the string this transport actually writes. Defaults to `JSONFormatter`. */
608
821
  formatter;
609
822
  constructor(formatter = new JSONFormatter()) {
610
823
  this.formatter = formatter;
611
824
  }
825
+ /** Formats `record` via `this.formatter`. Called once per record before `write()`. */
612
826
  format(record) {
613
827
  return this.formatter.format(record);
614
828
  }
@@ -616,9 +830,15 @@ var Transport = class {
616
830
  close() {
617
831
  }
618
832
  };
833
+ function hasFlush(transport) {
834
+ return typeof transport.flush === "function";
835
+ }
619
836
  var CollectingTransport = class extends Transport {
837
+ /** Every formatted string passed to `write()`, in call order. */
620
838
  formatted = [];
839
+ /** Every raw `LogRecord` passed to `write()`, in call order. */
621
840
  records = [];
841
+ /** Set once `close()` has been called. */
622
842
  closed = false;
623
843
  write(formatted, record) {
624
844
  this.formatted.push(formatted);
@@ -631,7 +851,9 @@ var CollectingTransport = class extends Transport {
631
851
 
632
852
  // src/transports/batching-transport.ts
633
853
  var BatchingTransport = class extends Transport {
854
+ /** Buffer is flushed once it holds this many items. */
634
855
  maxRecords;
856
+ /** Buffer is flushed once its estimated byte size reaches this many bytes. */
635
857
  maxBytes;
636
858
  buffer = [];
637
859
  bufferBytes = 0;
@@ -648,6 +870,7 @@ var BatchingTransport = class extends Transport {
648
870
  sizeOf(item) {
649
871
  return JSON.stringify(item).length;
650
872
  }
873
+ /** Buffers the record, flushing the batch once `maxRecords`/`maxBytes` is reached. */
651
874
  write(formatted, record) {
652
875
  const item = this.toItem(formatted, record);
653
876
  this.buffer.push(item);
@@ -682,7 +905,9 @@ function metaString(meta, key) {
682
905
  return typeof value === "string" ? value : null;
683
906
  }
684
907
  var BaseSQLTransport = class extends BatchingTransport {
908
+ /** Table records are inserted into. */
685
909
  tableName;
910
+ /** Whether `createTableSQL()` runs before the first insert. Dev/test convenience only. */
686
911
  ensureSchema;
687
912
  schemaEnsured = false;
688
913
  constructor(options = {}) {
@@ -1155,6 +1380,7 @@ var RedisTransport = class extends BatchingTransport {
1155
1380
 
1156
1381
  // src/transports/queue/base-queue-transport.ts
1157
1382
  var BaseQueueTransport = class extends BatchingTransport {
1383
+ /** Destination name on the backend — a Kafka topic, a RabbitMQ queue name, an SQS queue URL, or a GCP Pub/Sub topic. */
1158
1384
  topic;
1159
1385
  constructor(options) {
1160
1386
  super(options);
@@ -1353,8 +1579,11 @@ var PubSubTransport = class extends BaseQueueTransport {
1353
1579
 
1354
1580
  // src/transports/cloud/cloudwatch-transport.ts
1355
1581
  var CloudWatchTransport = class extends BatchingTransport {
1582
+ /** CloudWatch Logs log group written into. */
1356
1583
  logGroupName;
1584
+ /** CloudWatch Logs log stream, within `logGroupName`, written into. */
1357
1585
  logStreamName;
1586
+ /** AWS region passed to the real SDK client; `undefined` when `client` is injected or the SDK's own credential-chain resolution is used. */
1358
1587
  region;
1359
1588
  injectedClient;
1360
1589
  client;
@@ -1422,7 +1651,9 @@ function gcpSeverity(levelValue) {
1422
1651
  }
1423
1652
  }
1424
1653
  var CloudLoggingTransport = class extends BatchingTransport {
1654
+ /** GCP log name (the last segment of the log resource path). */
1425
1655
  logName;
1656
+ /** GCP project ID passed to the real SDK client; `undefined` when `client` is injected or Application Default Credentials' project is used. */
1426
1657
  projectId;
1427
1658
  injectedClient;
1428
1659
  client;
@@ -1487,6 +1718,7 @@ function appInsightsSeverity(levelValue) {
1487
1718
  }
1488
1719
  }
1489
1720
  var AppInsightsTransport = class extends BatchingTransport {
1721
+ /** Azure Application Insights connection string passed to the real SDK client; `undefined` when `client` is injected. */
1490
1722
  connectionString;
1491
1723
  injectedClient;
1492
1724
  client;
@@ -1546,8 +1778,11 @@ async function fetchDatadogSender(url, apiKey, batch) {
1546
1778
  }
1547
1779
  }
1548
1780
  var DatadogTransport = class extends BatchingTransport {
1781
+ /** Logs intake endpoint derived from `site`. */
1549
1782
  url;
1783
+ /** Datadog API key sent in the `DD-API-KEY` header. */
1550
1784
  apiKey;
1785
+ /** Datadog site (region) this transport sends to. */
1551
1786
  site;
1552
1787
  sender;
1553
1788
  constructor(options) {
@@ -1575,7 +1810,9 @@ async function fetchElasticsearchSender(url, headers, body) {
1575
1810
  }
1576
1811
  }
1577
1812
  var ElasticsearchTransport = class extends BatchingTransport {
1813
+ /** `_bulk` endpoint derived from the `node` option. */
1578
1814
  url;
1815
+ /** Index written into. */
1579
1816
  index;
1580
1817
  apiKey;
1581
1818
  sender;
@@ -1626,7 +1863,9 @@ function resumeTimestamp(retryAfter, now) {
1626
1863
  return Number.isNaN(dateMs) ? now + 6e4 : dateMs;
1627
1864
  }
1628
1865
  var NewRelicTransport = class extends BatchingTransport {
1866
+ /** Ingest endpoint derived from `region`. */
1629
1867
  url;
1868
+ /** New Relic account region this transport sends to. */
1630
1869
  region;
1631
1870
  licenseKey;
1632
1871
  sender;
@@ -1693,6 +1932,7 @@ function defaultColorize() {
1693
1932
  return env?.NO_COLOR === void 0;
1694
1933
  }
1695
1934
  var ConsoleTransport = class extends Transport {
1935
+ /** Whether each line is wrapped in an ANSI color escape for its level. */
1696
1936
  colorize;
1697
1937
  out;
1698
1938
  constructor(options = {}) {
@@ -1700,6 +1940,7 @@ var ConsoleTransport = class extends Transport {
1700
1940
  this.colorize = options.colorize ?? defaultColorize();
1701
1941
  this.out = options.console ?? console;
1702
1942
  }
1943
+ /** Writes `formatted` via `console.log`, or `console.error` for ERROR/FATAL records. */
1703
1944
  write(formatted, record) {
1704
1945
  const level = parseLevel(record.level);
1705
1946
  const line = this.colorize ? this.applyColor(formatted, level) : formatted;
@@ -1713,9 +1954,62 @@ var ConsoleTransport = class extends Transport {
1713
1954
  return `${COLORS[level]}${formatted}${RESET}`;
1714
1955
  }
1715
1956
  };
1957
+
1958
+ // src/transports/beacon-transport.ts
1959
+ function defaultBeaconSender(url, batch) {
1960
+ const body = batch.join("\n");
1961
+ const nav = typeof navigator === "undefined" ? void 0 : navigator;
1962
+ if (nav && typeof nav.sendBeacon === "function") {
1963
+ nav.sendBeacon(url, body);
1964
+ return;
1965
+ }
1966
+ void fetch(url, { method: "POST", body, keepalive: true }).catch((error) => {
1967
+ console.error("BeaconTransport: failed to send log batch", error);
1968
+ });
1969
+ }
1970
+ var BeaconTransport = class extends Transport {
1971
+ /** Endpoint each batch is sent to. */
1972
+ url;
1973
+ /** Buffer is flushed once it holds this many lines. */
1974
+ batchSize;
1975
+ sender;
1976
+ batch = [];
1977
+ constructor(url, options = {}) {
1978
+ super(options.formatter);
1979
+ this.url = url;
1980
+ this.batchSize = options.batchSize ?? 20;
1981
+ this.sender = options.sender ?? defaultBeaconSender;
1982
+ }
1983
+ /** Buffers the formatted line, flushing the batch once `batchSize` is reached. */
1984
+ write(formatted) {
1985
+ this.batch.push(formatted);
1986
+ if (this.batch.length >= this.batchSize) {
1987
+ this.flush();
1988
+ }
1989
+ }
1990
+ /** Send the current batch now, even if it hasn't reached `batchSize`. */
1991
+ flush() {
1992
+ if (this.batch.length === 0) {
1993
+ return;
1994
+ }
1995
+ const batch = this.batch;
1996
+ this.batch = [];
1997
+ try {
1998
+ this.sender(this.url, batch);
1999
+ } catch (error) {
2000
+ console.error("BeaconTransport: failed to send log batch", error);
2001
+ }
2002
+ }
2003
+ close() {
2004
+ this.flush();
2005
+ }
2006
+ };
1716
2007
  var FileTransport = class extends Transport {
2008
+ /** Path of the file records are appended to. */
1717
2009
  path;
2010
+ /** File is rotated once it reaches this many bytes. `0` disables rotation. */
1718
2011
  maxBytes;
2012
+ /** How many rotated backups (`.1`, `.2`, ...) are kept. */
1719
2013
  backupCount;
1720
2014
  fd;
1721
2015
  constructor(path$1, options = {}) {
@@ -1726,6 +2020,7 @@ var FileTransport = class extends Transport {
1726
2020
  fs.mkdirSync(path.dirname(path$1), { recursive: true });
1727
2021
  this.fd = fs.openSync(this.path, "a");
1728
2022
  }
2023
+ /** Appends one formatted line to the file, rotating first if `maxBytes` has been exceeded. */
1729
2024
  write(formatted) {
1730
2025
  fs.writeSync(this.fd, formatted + "\n");
1731
2026
  if (this.maxBytes > 0 && fs.fstatSync(this.fd).size >= this.maxBytes) {
@@ -1772,7 +2067,9 @@ async function fetchSender(url, batch) {
1772
2067
  }
1773
2068
  }
1774
2069
  var HTTPTransport = class extends Transport {
2070
+ /** Endpoint each batch is POSTed to. */
1775
2071
  url;
2072
+ /** Buffer is flushed once it holds this many lines. */
1776
2073
  batchSize;
1777
2074
  sender;
1778
2075
  batch = [];
@@ -1782,6 +2079,7 @@ var HTTPTransport = class extends Transport {
1782
2079
  this.batchSize = options.batchSize ?? 50;
1783
2080
  this.sender = options.sender ?? fetchSender;
1784
2081
  }
2082
+ /** Buffers the formatted line, flushing the batch once `batchSize` is reached. */
1785
2083
  write(formatted) {
1786
2084
  this.batch.push(formatted);
1787
2085
  if (this.batch.length >= this.batchSize) {
@@ -1806,6 +2104,145 @@ var HTTPTransport = class extends Transport {
1806
2104
  this.flush();
1807
2105
  }
1808
2106
  };
2107
+
2108
+ // src/core/dispatch-queue.ts
2109
+ function isPromise(value) {
2110
+ return typeof value === "object" && typeof value.then === "function";
2111
+ }
2112
+ function defaultScheduler(run) {
2113
+ if (typeof setImmediate === "function") {
2114
+ setImmediate(run);
2115
+ } else {
2116
+ queueMicrotask(run);
2117
+ }
2118
+ }
2119
+ function defaultOnDrop(count, policy) {
2120
+ console.warn(
2121
+ `DispatchQueue: dropped ${String(count)} record(s) \u2014 queue exceeded its configured size under the "${policy}" backpressure policy`
2122
+ );
2123
+ }
2124
+ var DispatchQueue = class {
2125
+ /** Maximum number of pending tasks held at once, as configured via `DispatchQueueOptions`. */
2126
+ maxSize;
2127
+ /** Backpressure policy applied once `maxSize` is reached, as configured via `DispatchQueueOptions`. */
2128
+ policy;
2129
+ onDrop;
2130
+ warnIntervalMs;
2131
+ tasks = [];
2132
+ draining = false;
2133
+ scheduled = false;
2134
+ idleWaiters = [];
2135
+ droppedSinceWarning = 0;
2136
+ lastWarnAt = 0;
2137
+ constructor(options = {}) {
2138
+ this.maxSize = options.maxSize ?? 1e4;
2139
+ this.policy = options.policy ?? "dropOldest";
2140
+ this.onDrop = options.onDrop ?? defaultOnDrop;
2141
+ this.warnIntervalMs = options.warnIntervalMs ?? 5e3;
2142
+ }
2143
+ /** Number of tasks currently waiting to run. Bounded by `maxSize`. */
2144
+ get size() {
2145
+ return this.tasks.length;
2146
+ }
2147
+ /**
2148
+ * Queue `task` to run outside the current call stack, applying the
2149
+ * configured backpressure policy if the queue is already full. Under
2150
+ * `"block"`, `task` may run synchronously before this call returns.
2151
+ */
2152
+ enqueue(task) {
2153
+ if (this.tasks.length < this.maxSize) {
2154
+ this.tasks.push(task);
2155
+ this.schedule();
2156
+ return;
2157
+ }
2158
+ switch (this.policy) {
2159
+ case "dropNewest":
2160
+ this.recordDrop();
2161
+ return;
2162
+ case "block":
2163
+ this.runInline(task);
2164
+ return;
2165
+ case "dropOldest":
2166
+ default:
2167
+ this.tasks.shift();
2168
+ this.recordDrop();
2169
+ this.tasks.push(task);
2170
+ this.schedule();
2171
+ return;
2172
+ }
2173
+ }
2174
+ /** Resolves once every task queued so far has run. Safe to call when idle. */
2175
+ async flush() {
2176
+ if (this.tasks.length === 0 && !this.draining) {
2177
+ return;
2178
+ }
2179
+ await new Promise((resolve) => {
2180
+ this.idleWaiters.push(resolve);
2181
+ this.schedule();
2182
+ });
2183
+ }
2184
+ recordDrop() {
2185
+ this.droppedSinceWarning += 1;
2186
+ const now = Date.now();
2187
+ if (now - this.lastWarnAt >= this.warnIntervalMs) {
2188
+ const count = this.droppedSinceWarning;
2189
+ this.droppedSinceWarning = 0;
2190
+ this.lastWarnAt = now;
2191
+ try {
2192
+ this.onDrop(count, this.policy);
2193
+ } catch {
2194
+ }
2195
+ }
2196
+ }
2197
+ runInline(task) {
2198
+ try {
2199
+ const result = task();
2200
+ if (isPromise(result)) {
2201
+ result.catch((error) => {
2202
+ console.error("DispatchQueue: a blocked task failed", error);
2203
+ });
2204
+ }
2205
+ } catch (error) {
2206
+ console.error("DispatchQueue: a blocked task failed", error);
2207
+ }
2208
+ }
2209
+ schedule() {
2210
+ if (this.scheduled) {
2211
+ return;
2212
+ }
2213
+ this.scheduled = true;
2214
+ defaultScheduler(() => {
2215
+ this.scheduled = false;
2216
+ void this.drainAll();
2217
+ });
2218
+ }
2219
+ async drainAll() {
2220
+ if (this.draining) {
2221
+ return;
2222
+ }
2223
+ this.draining = true;
2224
+ try {
2225
+ while (this.tasks.length > 0) {
2226
+ const task = this.tasks.shift();
2227
+ if (!task) {
2228
+ continue;
2229
+ }
2230
+ try {
2231
+ await task();
2232
+ } catch (error) {
2233
+ console.error("DispatchQueue: a queued task failed", error);
2234
+ }
2235
+ }
2236
+ } finally {
2237
+ this.draining = false;
2238
+ const waiters = this.idleWaiters;
2239
+ this.idleWaiters = [];
2240
+ for (const resolve of waiters) {
2241
+ resolve();
2242
+ }
2243
+ }
2244
+ }
2245
+ };
1809
2246
  var spanIdStore = new async_hooks.AsyncLocalStorage();
1810
2247
  function currentSpanId() {
1811
2248
  return spanIdStore.getStore();
@@ -1824,12 +2261,26 @@ function formatSpanError(error) {
1824
2261
  }
1825
2262
  return String(error);
1826
2263
  }
2264
+ function withStackFromErr(meta) {
2265
+ const err = meta.err;
2266
+ if (!(err instanceof Error)) {
2267
+ return meta;
2268
+ }
2269
+ const next = { ...meta };
2270
+ delete next.err;
2271
+ next.stack = err.stack ?? `${err.name}: ${err.message}`;
2272
+ return next;
2273
+ }
1827
2274
  var Logger = class _Logger {
2275
+ /** This logger's name, as passed to the constructor (or derived via `.child()`). Appears on every record as `logger`. */
1828
2276
  name;
2277
+ /** Every transport a written record is sent to. */
1829
2278
  transports;
2279
+ /** Every plugin registered via `.use()`, in registration order. */
1830
2280
  plugins;
1831
2281
  currentLevel;
1832
2282
  baseMeta;
2283
+ dispatchQueue;
1833
2284
  constructor(name, options = {}) {
1834
2285
  this.name = name;
1835
2286
  this.currentLevel = parseLevel(options.level ?? 20 /* INFO */);
@@ -1839,10 +2290,13 @@ var Logger = class _Logger {
1839
2290
  this.use(plugin);
1840
2291
  }
1841
2292
  this.baseMeta = options.meta ? { ...options.meta } : {};
2293
+ this.dispatchQueue = new DispatchQueue(options.queue);
1842
2294
  }
2295
+ /** This logger's current minimum level — records below it are filtered before any plugin runs. */
1843
2296
  get level() {
1844
2297
  return this.currentLevel;
1845
2298
  }
2299
+ /** Changes the minimum level records must meet to reach a transport. Accepts a `Level`, its numeric weight, or its name. */
1846
2300
  setLevel(level) {
1847
2301
  this.currentLevel = parseLevel(level);
1848
2302
  }
@@ -1856,20 +2310,41 @@ var Logger = class _Logger {
1856
2310
  this.plugins.push(typeof plugin === "function" ? new FunctionPlugin(plugin) : plugin);
1857
2311
  return this;
1858
2312
  }
1859
- /** Close every attached transport. Call on shutdown to flush buffered writes. */
1860
- close() {
2313
+ /** Number of dispatched records not yet written to their transports. Bounded by the `queue` option. */
2314
+ get queueSize() {
2315
+ return this.dispatchQueue.size;
2316
+ }
2317
+ /**
2318
+ * Waits for every record dispatched so far to reach its transports'
2319
+ * `write()` (and any plugin `afterLog` hooks). Note this does *not* force
2320
+ * a batching transport (SQL, a queue, `HTTPTransport`, ...) to send a
2321
+ * batch still under its own `maxRecords`/`maxBytes` threshold early — it
2322
+ * only guarantees the record has been handed to that transport, the same
2323
+ * contract `write()` always had. Before a process may pause or exit
2324
+ * (a serverless freeze, a shutdown signal), prefer `withLambda`/
2325
+ * `installShutdownHandlers`, which additionally force every batching
2326
+ * transport to send its current buffer regardless of threshold.
2327
+ */
2328
+ async flush() {
2329
+ await this.dispatchQueue.flush();
2330
+ }
2331
+ /** Flush every pending record, then close every attached transport. Call once, on shutdown. */
2332
+ async close() {
2333
+ await this.flush();
1861
2334
  for (const transport of this.transports) {
1862
2335
  transport.close();
1863
2336
  }
1864
2337
  }
1865
- /** A logger scoped under this one, inheriting its level, transports, and plugins. */
2338
+ /** A logger scoped under this one, inheriting its level, transports, plugins, and dispatch queue. */
1866
2339
  child(name, meta = {}) {
1867
- return new _Logger(`${this.name}.${name}`, {
2340
+ const child = new _Logger(`${this.name}.${name}`, {
1868
2341
  level: this.currentLevel,
1869
2342
  transports: this.transports,
1870
2343
  plugins: this.plugins,
1871
2344
  meta: { ...this.baseMeta, ...meta }
1872
2345
  });
2346
+ child.dispatchQueue = this.dispatchQueue;
2347
+ return child;
1873
2348
  }
1874
2349
  notifyError(plugin, error, record) {
1875
2350
  try {
@@ -1885,7 +2360,7 @@ var Logger = class _Logger {
1885
2360
  level,
1886
2361
  logger: this.name,
1887
2362
  message,
1888
- meta: { ...this.baseMeta, ...meta }
2363
+ meta: { ...this.baseMeta, ...currentContext(), ...withStackFromErr(meta) }
1889
2364
  });
1890
2365
  const parentSpanId = currentSpanId();
1891
2366
  if (parentSpanId !== void 0) {
@@ -1904,6 +2379,12 @@ var Logger = class _Logger {
1904
2379
  }
1905
2380
  record = result;
1906
2381
  }
2382
+ this.dispatchQueue.enqueue(() => {
2383
+ this.writeAndNotify(record);
2384
+ });
2385
+ return record;
2386
+ }
2387
+ writeAndNotify(record) {
1907
2388
  for (const transport of this.transports) {
1908
2389
  try {
1909
2390
  transport.write(transport.format(record), record);
@@ -1918,23 +2399,28 @@ var Logger = class _Logger {
1918
2399
  this.notifyError(plugin, error, record);
1919
2400
  }
1920
2401
  }
1921
- return record;
1922
2402
  }
2403
+ /** Logs at `TRACE` — the lowest level, for fine-grained diagnostic detail. Returns the record, or `null` if filtered/dropped. */
1923
2404
  trace(message, meta = {}) {
1924
2405
  return this.dispatch(5 /* TRACE */, message, meta);
1925
2406
  }
2407
+ /** Logs at `DEBUG`. Returns the record, or `null` if filtered/dropped. */
1926
2408
  debug(message, meta = {}) {
1927
2409
  return this.dispatch(10 /* DEBUG */, message, meta);
1928
2410
  }
2411
+ /** Logs at `INFO`. Returns the record, or `null` if filtered/dropped. */
1929
2412
  info(message, meta = {}) {
1930
2413
  return this.dispatch(20 /* INFO */, message, meta);
1931
2414
  }
2415
+ /** Logs at `WARN`. Returns the record, or `null` if filtered/dropped. */
1932
2416
  warn(message, meta = {}) {
1933
2417
  return this.dispatch(30 /* WARN */, message, meta);
1934
2418
  }
2419
+ /** Logs at `ERROR`. Returns the record, or `null` if filtered/dropped. */
1935
2420
  error(message, meta = {}) {
1936
2421
  return this.dispatch(40 /* ERROR */, message, meta);
1937
2422
  }
2423
+ /** Logs at `FATAL` — the highest level, for errors that precede an unrecoverable failure. Returns the record, or `null` if filtered/dropped. */
1938
2424
  fatal(message, meta = {}) {
1939
2425
  return this.dispatch(50 /* FATAL */, message, meta);
1940
2426
  }
@@ -2000,6 +2486,68 @@ var Logger = class _Logger {
2000
2486
  }
2001
2487
  };
2002
2488
 
2489
+ // src/core/serverless.ts
2490
+ async function flushEverything(logger) {
2491
+ await logger.flush();
2492
+ for (const transport of logger.transports) {
2493
+ if (!hasFlush(transport)) {
2494
+ continue;
2495
+ }
2496
+ try {
2497
+ await transport.flush();
2498
+ } catch (error) {
2499
+ console.error(`${transport.constructor.name}: failed to flush`, error);
2500
+ }
2501
+ }
2502
+ }
2503
+ function withFlush(logger, fn) {
2504
+ return async (...args) => {
2505
+ try {
2506
+ return await fn(...args);
2507
+ } finally {
2508
+ await flushEverything(logger);
2509
+ }
2510
+ };
2511
+ }
2512
+ var withLambda = withFlush;
2513
+ var withCloudFunction = withFlush;
2514
+ var withAzureFunction = withFlush;
2515
+
2516
+ // src/core/shutdown.ts
2517
+ function installShutdownHandlers(logger, options = {}) {
2518
+ const signals = options.signals ?? ["SIGTERM", "SIGINT"];
2519
+ let closed = false;
2520
+ const shutdown = (exitAfter) => {
2521
+ if (closed) {
2522
+ return;
2523
+ }
2524
+ closed = true;
2525
+ logger.close().catch((error) => {
2526
+ console.error("installShutdownHandlers: logger.close() failed during shutdown", error);
2527
+ }).finally(() => {
2528
+ if (exitAfter) {
2529
+ process.exit(0);
2530
+ }
2531
+ });
2532
+ };
2533
+ const onBeforeExit = () => {
2534
+ shutdown(false);
2535
+ };
2536
+ const onSignal = () => {
2537
+ shutdown(true);
2538
+ };
2539
+ process.once("beforeExit", onBeforeExit);
2540
+ for (const signal of signals) {
2541
+ process.once(signal, onSignal);
2542
+ }
2543
+ return () => {
2544
+ process.removeListener("beforeExit", onBeforeExit);
2545
+ for (const signal of signals) {
2546
+ process.removeListener(signal, onSignal);
2547
+ }
2548
+ };
2549
+ }
2550
+
2003
2551
  // src/index.ts
2004
2552
  var VERSION = "0.3.0";
2005
2553
 
@@ -2008,14 +2556,17 @@ exports.AppInsightsTransport = AppInsightsTransport;
2008
2556
  exports.BaseQueueTransport = BaseQueueTransport;
2009
2557
  exports.BaseSQLTransport = BaseSQLTransport;
2010
2558
  exports.BatchingTransport = BatchingTransport;
2559
+ exports.BeaconTransport = BeaconTransport;
2011
2560
  exports.CloudLoggingTransport = CloudLoggingTransport;
2012
2561
  exports.CloudWatchTransport = CloudWatchTransport;
2013
2562
  exports.CollectingTransport = CollectingTransport;
2014
2563
  exports.ConsoleTransport = ConsoleTransport;
2015
2564
  exports.ContextPlugin = ContextPlugin;
2016
2565
  exports.DEFAULT_PII_PATTERNS = DEFAULT_PII_PATTERNS;
2566
+ exports.DEFAULT_PINO_LEVEL_MAP = DEFAULT_PINO_LEVEL_MAP;
2017
2567
  exports.DEFAULT_REDACTED_KEYS = DEFAULT_REDACTED_KEYS;
2018
2568
  exports.DatadogTransport = DatadogTransport;
2569
+ exports.DispatchQueue = DispatchQueue;
2019
2570
  exports.DynamoDBTransport = DynamoDBTransport;
2020
2571
  exports.ElasticsearchTransport = ElasticsearchTransport;
2021
2572
  exports.EmailAlertPlugin = EmailAlertPlugin;
@@ -2026,15 +2577,18 @@ exports.HTTPTransport = HTTPTransport;
2026
2577
  exports.JSONFormatter = JSONFormatter;
2027
2578
  exports.KafkaTransport = KafkaTransport;
2028
2579
  exports.Level = Level;
2580
+ exports.LogQuillPinoDestination = LogQuillPinoDestination;
2029
2581
  exports.Logger = Logger;
2030
2582
  exports.MongoDBTransport = MongoDBTransport;
2031
2583
  exports.MySQLTransport = MySQLTransport;
2032
2584
  exports.NewRelicTransport = NewRelicTransport;
2585
+ exports.OtelSpanProcessor = OtelSpanProcessor;
2033
2586
  exports.PIIRedactPlugin = PIIRedactPlugin;
2034
2587
  exports.PagerDutyAlertPlugin = PagerDutyAlertPlugin;
2035
2588
  exports.PostgresTransport = PostgresTransport;
2036
2589
  exports.PubSubTransport = PubSubTransport;
2037
2590
  exports.RabbitMQTransport = RabbitMQTransport;
2591
+ exports.RateLimitPlugin = RateLimitPlugin;
2038
2592
  exports.RedactPlugin = RedactPlugin;
2039
2593
  exports.RedisTransport = RedisTransport;
2040
2594
  exports.RunPlugin = RunPlugin;
@@ -2046,14 +2600,22 @@ exports.TamperEvidentPlugin = TamperEvidentPlugin;
2046
2600
  exports.TraceContextPlugin = TraceContextPlugin;
2047
2601
  exports.Transport = Transport;
2048
2602
  exports.VERSION = VERSION;
2603
+ exports.bindContext = bindContext;
2049
2604
  exports.createRecord = createRecord;
2605
+ exports.currentContext = currentContext;
2050
2606
  exports.defaultResolveActiveOtelTraceId = defaultResolveActiveOtelTraceId;
2051
2607
  exports.generateTraceId = generateTraceId;
2052
2608
  exports.getTraceparent = getTraceparent;
2609
+ exports.hasFlush = hasFlush;
2610
+ exports.installShutdownHandlers = installShutdownHandlers;
2053
2611
  exports.levelName = levelName;
2054
2612
  exports.parseLevel = parseLevel;
2055
2613
  exports.parseTraceHeader = parseTraceHeader;
2056
2614
  exports.setTraceparent = setTraceparent;
2057
2615
  exports.utcTimestamp = utcTimestamp;
2616
+ exports.withAzureFunction = withAzureFunction;
2617
+ exports.withCloudFunction = withCloudFunction;
2618
+ exports.withFlush = withFlush;
2619
+ exports.withLambda = withLambda;
2058
2620
  //# sourceMappingURL=index.cjs.map
2059
2621
  //# sourceMappingURL=index.cjs.map