logquill 0.2.0 → 0.4.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,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
4
+ var async_hooks = require('async_hooks');
5
+ var module$1 = require('module');
3
6
  var zlib = require('zlib');
4
7
  var fs = require('fs');
5
8
  var path = require('path');
6
9
 
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
11
  // src/core/levels.ts
8
12
  var Level = /* @__PURE__ */ ((Level2) => {
9
13
  Level2[Level2["TRACE"] = 5] = "TRACE";
@@ -60,6 +64,17 @@ var JSONFormatter = class {
60
64
  }
61
65
  };
62
66
 
67
+ // src/core/plugin.ts
68
+ var FunctionPlugin = class {
69
+ func;
70
+ constructor(func) {
71
+ this.func = func;
72
+ }
73
+ beforeLog(record) {
74
+ return this.func(record);
75
+ }
76
+ };
77
+
63
78
  // src/plugins/context-plugin.ts
64
79
  var ContextPlugin = class {
65
80
  context;
@@ -70,6 +85,99 @@ var ContextPlugin = class {
70
85
  return { ...record, meta: { ...this.context, ...record.meta } };
71
86
  }
72
87
  };
88
+ var RunPlugin = class {
89
+ runId;
90
+ step = 0;
91
+ constructor(options = {}) {
92
+ this.runId = options.runId ?? crypto.randomUUID();
93
+ }
94
+ beforeLog(record) {
95
+ record.meta.runId ??= this.runId;
96
+ record.meta.step = this.step++;
97
+ return record;
98
+ }
99
+ };
100
+ var traceparentStore = new async_hooks.AsyncLocalStorage();
101
+ function setTraceparent(value) {
102
+ const previous = traceparentStore.getStore();
103
+ traceparentStore.enterWith(value);
104
+ return () => {
105
+ traceparentStore.enterWith(previous);
106
+ };
107
+ }
108
+ function getTraceparent() {
109
+ return traceparentStore.getStore();
110
+ }
111
+ function generateTraceId() {
112
+ return crypto.randomBytes(16).toString("hex");
113
+ }
114
+ var W3C_TRACEPARENT_RE = /^[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/;
115
+ var XRAY_ROOT_RE = /Root=1-([0-9a-f]{8})-([0-9a-f]{24})/;
116
+ var GCP_TRACE_RE = /^([0-9a-f]{32})\/\d+(;o=\d)?$/;
117
+ function parseTraceHeader(header) {
118
+ const trimmed = header.trim();
119
+ const w3c = W3C_TRACEPARENT_RE.exec(trimmed);
120
+ if (w3c?.[1]) {
121
+ return w3c[1];
122
+ }
123
+ const xray = XRAY_ROOT_RE.exec(trimmed);
124
+ if (xray?.[1] && xray[2]) {
125
+ return xray[1] + xray[2];
126
+ }
127
+ const gcp = GCP_TRACE_RE.exec(trimmed);
128
+ if (gcp?.[1]) {
129
+ return gcp[1];
130
+ }
131
+ return void 0;
132
+ }
133
+ function defaultResolveActiveOtelTraceId() {
134
+ try {
135
+ const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
136
+ const otel = require2("@opentelemetry/api");
137
+ const span = otel.trace.getActiveSpan();
138
+ if (!span) {
139
+ return void 0;
140
+ }
141
+ const spanContext = span.spanContext();
142
+ if (!otel.trace.isSpanContextValid(spanContext)) {
143
+ return void 0;
144
+ }
145
+ return spanContext.traceId;
146
+ } catch {
147
+ return void 0;
148
+ }
149
+ }
150
+ var TraceContextPlugin = class {
151
+ traceKey;
152
+ explicitTraceparent;
153
+ resolveActiveOtelTraceId;
154
+ constructor(options = {}) {
155
+ this.traceKey = options.traceKey ?? "traceId";
156
+ this.explicitTraceparent = options.traceparent;
157
+ this.resolveActiveOtelTraceId = options.resolveActiveOtelTraceId ?? defaultResolveActiveOtelTraceId;
158
+ }
159
+ beforeLog(record) {
160
+ if (record.meta[this.traceKey] != null) {
161
+ return record;
162
+ }
163
+ record.meta[this.traceKey] = this.resolveTraceId();
164
+ return record;
165
+ }
166
+ resolveTraceId() {
167
+ const fromOtel = this.resolveActiveOtelTraceId();
168
+ if (fromOtel) {
169
+ return fromOtel;
170
+ }
171
+ const header = this.explicitTraceparent ?? getTraceparent();
172
+ if (header) {
173
+ const parsed = parseTraceHeader(header);
174
+ if (parsed) {
175
+ return parsed;
176
+ }
177
+ }
178
+ return generateTraceId();
179
+ }
180
+ };
73
181
 
74
182
  // src/plugins/redact-plugin.ts
75
183
  var DEFAULT_REDACTED_KEYS = ["password", "token", "secret", "api_key", "authorization"];
@@ -89,19 +197,409 @@ var RedactPlugin = class {
89
197
  }
90
198
  };
91
199
 
200
+ // src/plugins/pii-redact-plugin.ts
201
+ var DEFAULT_PII_PATTERNS = {
202
+ email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
203
+ ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
204
+ creditCard: /\b(?:\d[ -]?){13,16}\b/g,
205
+ phone: /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
206
+ };
207
+ var MAX_DEPTH = 50;
208
+ var PIIRedactPlugin = class {
209
+ patterns;
210
+ replacement;
211
+ constructor(options = {}) {
212
+ this.patterns = options.patterns ?? DEFAULT_PII_PATTERNS;
213
+ this.replacement = options.replacement ?? "***";
214
+ }
215
+ beforeLog(record) {
216
+ return { ...record, meta: this.redactValue(record.meta, /* @__PURE__ */ new Set(), 0) };
217
+ }
218
+ redactValue(value, seen, depth) {
219
+ if (depth > MAX_DEPTH) {
220
+ return value;
221
+ }
222
+ if (typeof value === "string") {
223
+ return this.redactText(value);
224
+ }
225
+ if (Array.isArray(value)) {
226
+ if (seen.has(value)) {
227
+ return value;
228
+ }
229
+ const nextSeen = new Set(seen).add(value);
230
+ return value.map((entry) => this.redactValue(entry, nextSeen, depth + 1));
231
+ }
232
+ if (value !== null && typeof value === "object") {
233
+ if (seen.has(value)) {
234
+ return value;
235
+ }
236
+ const nextSeen = new Set(seen).add(value);
237
+ const result = {};
238
+ for (const [key, entryValue] of Object.entries(value)) {
239
+ result[key] = this.redactValue(entryValue, nextSeen, depth + 1);
240
+ }
241
+ return result;
242
+ }
243
+ return value;
244
+ }
245
+ redactText(text) {
246
+ let redacted = text;
247
+ for (const pattern of Object.values(this.patterns)) {
248
+ const global = pattern.global ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);
249
+ redacted = redacted.replace(global, this.replacement);
250
+ }
251
+ return redacted;
252
+ }
253
+ };
254
+
92
255
  // src/plugins/sampling-plugin.ts
93
256
  var SamplingPlugin = class {
94
257
  rate;
258
+ traceKey;
259
+ elevateAt;
260
+ transports;
261
+ maxBufferedRecords;
262
+ maxTraces;
95
263
  rng;
264
+ buffer = /* @__PURE__ */ new Map();
265
+ bufferedCount = 0;
266
+ elevated = /* @__PURE__ */ new Set();
96
267
  constructor(rate, options = {}) {
97
268
  if (rate < 0 || rate > 1) {
98
269
  throw new Error(`rate must be between 0 and 1, got ${String(rate)}`);
99
270
  }
100
271
  this.rate = rate;
101
272
  this.rng = options.rng ?? Math.random;
273
+ this.traceKey = options.traceKey ?? "traceId";
274
+ this.elevateAt = parseLevel(options.elevateAt ?? 40 /* ERROR */);
275
+ this.transports = options.transports;
276
+ this.maxBufferedRecords = options.maxBufferedRecords ?? 1e3;
277
+ this.maxTraces = options.maxTraces ?? 200;
102
278
  }
103
279
  beforeLog(record) {
104
- return this.rng() < this.rate ? record : null;
280
+ const transports = this.transports;
281
+ if (transports === void 0) {
282
+ return this.rng() < this.rate ? record : null;
283
+ }
284
+ const traceId = record.meta[this.traceKey];
285
+ if (traceId !== void 0 && this.elevated.has(traceId)) {
286
+ return record;
287
+ }
288
+ const keep = this.rng() < this.rate;
289
+ const reachedElevateLevel = parseLevel(record.level) >= this.elevateAt;
290
+ if (traceId !== void 0 && reachedElevateLevel) {
291
+ this.elevate(traceId, transports);
292
+ return record;
293
+ }
294
+ if (keep) {
295
+ return record;
296
+ }
297
+ if (traceId !== void 0) {
298
+ this.bufferRecord(traceId, record);
299
+ }
300
+ return null;
301
+ }
302
+ elevate(traceId, transports) {
303
+ this.elevated.add(traceId);
304
+ const buffered = this.buffer.get(traceId) ?? [];
305
+ this.buffer.delete(traceId);
306
+ this.bufferedCount -= buffered.length;
307
+ for (const bufferedRecord of buffered) {
308
+ for (const transport of transports) {
309
+ transport.write(transport.format(bufferedRecord), bufferedRecord);
310
+ }
311
+ }
312
+ }
313
+ bufferRecord(traceId, record) {
314
+ let records = this.buffer.get(traceId);
315
+ if (records) {
316
+ this.buffer.delete(traceId);
317
+ this.buffer.set(traceId, records);
318
+ } else {
319
+ if (this.buffer.size >= this.maxTraces) {
320
+ this.evictOldestTrace();
321
+ }
322
+ records = [];
323
+ this.buffer.set(traceId, records);
324
+ }
325
+ records.push(record);
326
+ this.bufferedCount += 1;
327
+ while (this.bufferedCount > this.maxBufferedRecords && this.buffer.size > 0) {
328
+ this.evictOldestTrace();
329
+ }
330
+ }
331
+ evictOldestTrace() {
332
+ const oldest = this.buffer.entries().next();
333
+ if (oldest.done) {
334
+ return;
335
+ }
336
+ const [oldestKey, oldestRecords] = oldest.value;
337
+ this.buffer.delete(oldestKey);
338
+ this.bufferedCount -= oldestRecords.length;
339
+ }
340
+ };
341
+ var GENESIS_HASH = "0".repeat(64);
342
+ function canonicalStringify(value) {
343
+ if (Array.isArray(value)) {
344
+ return `[${value.map((entry) => canonicalStringify(entry)).join(",")}]`;
345
+ }
346
+ if (value !== null && typeof value === "object") {
347
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
348
+ return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalStringify(entryValue)}`).join(",")}}`;
349
+ }
350
+ if (value === void 0) {
351
+ return "null";
352
+ }
353
+ return JSON.stringify(value);
354
+ }
355
+ function computeHash(record, prevHash) {
356
+ const restMeta = Object.fromEntries(Object.entries(record.meta).filter(([key]) => key !== "hash" && key !== "prevHash"));
357
+ const payload = canonicalStringify({
358
+ timestamp: record.timestamp,
359
+ level: record.level,
360
+ logger: record.logger,
361
+ message: record.message,
362
+ meta: restMeta
363
+ });
364
+ return crypto.createHash("sha256").update(`${prevHash}${payload}`).digest("hex");
365
+ }
366
+ var TamperEvidentPlugin = class {
367
+ genesisHash;
368
+ lastHash;
369
+ constructor(options = {}) {
370
+ this.genesisHash = options.genesisHash ?? GENESIS_HASH;
371
+ this.lastHash = this.genesisHash;
372
+ }
373
+ beforeLog(record) {
374
+ const prevHash = this.lastHash;
375
+ const digest = computeHash(record, prevHash);
376
+ const next = { ...record, meta: { ...record.meta, prevHash, hash: digest } };
377
+ this.lastHash = digest;
378
+ return next;
379
+ }
380
+ /**
381
+ * Returns `true` iff every record's hash matches its content plus the
382
+ * previous record's hash, in the given order. Returns `false` at the
383
+ * first break in the chain (an edited, removed, or reordered record).
384
+ */
385
+ static verifyChain(records, options = {}) {
386
+ let prevHash = options.genesisHash ?? GENESIS_HASH;
387
+ for (const record of records) {
388
+ const storedHash = record.meta.hash;
389
+ const storedPrevHash = record.meta.prevHash;
390
+ if (typeof storedHash !== "string" || storedPrevHash !== prevHash) {
391
+ return false;
392
+ }
393
+ if (computeHash(record, prevHash) !== storedHash) {
394
+ return false;
395
+ }
396
+ prevHash = storedHash;
397
+ }
398
+ return true;
399
+ }
400
+ };
401
+
402
+ // src/plugins/alerting-plugin.ts
403
+ function defaultDedupeKey(record) {
404
+ return `${record.level}:${record.logger}:${record.message}`;
405
+ }
406
+ var AlertingPlugin = class {
407
+ threshold;
408
+ dedupeWindowMs;
409
+ maxTrackedKeys;
410
+ dedupeKeyFn;
411
+ windows = /* @__PURE__ */ new Map();
412
+ constructor(options = {}) {
413
+ this.threshold = parseLevel(options.threshold ?? 40 /* ERROR */);
414
+ this.dedupeWindowMs = options.dedupeWindowMs ?? 3e5;
415
+ this.dedupeKeyFn = options.dedupeKey ?? defaultDedupeKey;
416
+ this.maxTrackedKeys = options.maxTrackedKeys ?? 500;
417
+ }
418
+ afterLog(record) {
419
+ if (parseLevel(record.level) < this.threshold) {
420
+ return;
421
+ }
422
+ const key = this.dedupeKeyFn(record);
423
+ const existing = this.windows.get(key);
424
+ if (existing) {
425
+ existing.count += 1;
426
+ return;
427
+ }
428
+ if (this.windows.size >= this.maxTrackedKeys) {
429
+ return;
430
+ }
431
+ const timer = setTimeout(() => {
432
+ this.flush(key);
433
+ }, this.dedupeWindowMs);
434
+ timer.unref();
435
+ this.windows.set(key, { record, count: 1, timer });
436
+ this.safeSend(record, 1);
437
+ }
438
+ flush(key) {
439
+ const window = this.windows.get(key);
440
+ this.windows.delete(key);
441
+ if (!window || window.count <= 1) {
442
+ return;
443
+ }
444
+ this.safeSend(window.record, window.count);
445
+ }
446
+ safeSend(record, occurrences) {
447
+ Promise.resolve().then(() => this.sendAlert(record, occurrences)).catch((error) => {
448
+ try {
449
+ this.onError?.(error, record);
450
+ } catch {
451
+ }
452
+ });
453
+ }
454
+ /** Cancel any pending dedupe-window timers. Call on logger shutdown. */
455
+ close() {
456
+ const windows = [...this.windows.values()];
457
+ this.windows.clear();
458
+ for (const window of windows) {
459
+ clearTimeout(window.timer);
460
+ }
461
+ }
462
+ };
463
+
464
+ // src/plugins/slack-alert-plugin.ts
465
+ async function fetchSlackSender(webhookUrl, body) {
466
+ const response = await fetch(webhookUrl, {
467
+ method: "POST",
468
+ headers: { "Content-Type": "application/json" },
469
+ body
470
+ });
471
+ if (!response.ok) {
472
+ throw new Error(
473
+ `SlackAlertPlugin: webhook returned HTTP ${String(response.status)} \u2014 check the webhook URL is still valid in Slack's app config`
474
+ );
475
+ }
476
+ }
477
+ function formatMessage(record, occurrences) {
478
+ const suffix = occurrences > 1 ? ` (x${String(occurrences)})` : "";
479
+ return `[${record.level}] ${record.logger}: ${record.message}${suffix}`;
480
+ }
481
+ var SlackAlertPlugin = class extends AlertingPlugin {
482
+ webhookUrl;
483
+ sender;
484
+ constructor(webhookUrl, options = {}) {
485
+ super(options);
486
+ this.webhookUrl = webhookUrl;
487
+ this.sender = options.sender ?? fetchSlackSender;
488
+ }
489
+ async sendAlert(record, occurrences) {
490
+ const body = JSON.stringify({ text: formatMessage(record, occurrences) });
491
+ await this.sender(this.webhookUrl, body);
492
+ }
493
+ };
494
+
495
+ // src/plugins/pagerduty-alert-plugin.ts
496
+ var ENDPOINT = "https://events.pagerduty.com/v2/enqueue";
497
+ var SEVERITY = { ERROR: "error", FATAL: "critical" };
498
+ async function fetchPagerDutySender(body) {
499
+ const response = await fetch(ENDPOINT, {
500
+ method: "POST",
501
+ headers: { "Content-Type": "application/json" },
502
+ body
503
+ });
504
+ if (!response.ok) {
505
+ throw new Error(
506
+ `PagerDutyAlertPlugin: Events API returned HTTP ${String(response.status)} \u2014 check the routing key is a valid Events API v2 integration key`
507
+ );
508
+ }
509
+ }
510
+ var PagerDutyAlertPlugin = class extends AlertingPlugin {
511
+ routingKey;
512
+ sender;
513
+ constructor(routingKey, options = {}) {
514
+ super(options);
515
+ this.routingKey = routingKey;
516
+ this.sender = options.sender ?? fetchPagerDutySender;
517
+ }
518
+ async sendAlert(record, occurrences) {
519
+ let summary = `${record.logger}: ${record.message}`;
520
+ if (occurrences > 1) {
521
+ summary += ` (x${String(occurrences)})`;
522
+ }
523
+ const body = JSON.stringify({
524
+ routing_key: this.routingKey,
525
+ event_action: "trigger",
526
+ payload: {
527
+ summary,
528
+ severity: SEVERITY[record.level] ?? "error",
529
+ source: record.logger,
530
+ timestamp: record.timestamp,
531
+ custom_details: { occurrences, ...record.meta }
532
+ }
533
+ });
534
+ await this.sender(body);
535
+ }
536
+ };
537
+
538
+ // src/plugins/email-alert-plugin.ts
539
+ var EmailAlertPlugin = class extends AlertingPlugin {
540
+ smtpHost;
541
+ smtpPort;
542
+ fromAddr;
543
+ toAddrs;
544
+ username;
545
+ password;
546
+ useTls;
547
+ injectedSender;
548
+ transporter;
549
+ constructor(options) {
550
+ super(options);
551
+ this.smtpHost = options.smtpHost;
552
+ this.smtpPort = options.smtpPort;
553
+ this.fromAddr = options.fromAddr;
554
+ this.toAddrs = options.toAddrs;
555
+ this.username = options.username;
556
+ this.password = options.password;
557
+ this.useTls = options.useTls ?? true;
558
+ this.injectedSender = options.sender;
559
+ }
560
+ async sendAlert(record, occurrences) {
561
+ let subject = `[${record.level}] ${record.logger}`;
562
+ if (occurrences > 1) {
563
+ subject += ` (x${String(occurrences)})`;
564
+ }
565
+ const text = [
566
+ record.message,
567
+ "",
568
+ `occurrences: ${String(occurrences)}`,
569
+ `timestamp: ${record.timestamp}`,
570
+ `meta: ${JSON.stringify(record.meta)}`
571
+ ].join("\n");
572
+ const message = { from: this.fromAddr, to: this.toAddrs, subject, text };
573
+ if (this.injectedSender) {
574
+ await this.injectedSender(message);
575
+ return;
576
+ }
577
+ const transporter = this.transporter ?? await this.importTransporter();
578
+ await transporter.sendMail({ from: message.from, to: message.to.join(", "), subject: message.subject, text: message.text });
579
+ }
580
+ async importTransporter() {
581
+ let createTransport;
582
+ try {
583
+ const moduleName = "nodemailer";
584
+ const mod = await import(moduleName);
585
+ const resolved = mod.default?.createTransport ?? mod.createTransport;
586
+ if (!resolved) {
587
+ throw new Error("no createTransport export found");
588
+ }
589
+ createTransport = resolved;
590
+ } catch {
591
+ throw new Error(
592
+ "EmailAlertPlugin: install `nodemailer` to use this plugin without providing a `sender` \u2014 `npm install nodemailer`"
593
+ );
594
+ }
595
+ this.transporter = createTransport({
596
+ host: this.smtpHost,
597
+ port: this.smtpPort,
598
+ secure: false,
599
+ requireTLS: this.useTls,
600
+ auth: this.username && this.password ? { user: this.username, pass: this.password } : void 0
601
+ });
602
+ return this.transporter;
105
603
  }
106
604
  };
107
605
 
@@ -1308,8 +1806,24 @@ var HTTPTransport = class extends Transport {
1308
1806
  this.flush();
1309
1807
  }
1310
1808
  };
1809
+ var spanIdStore = new async_hooks.AsyncLocalStorage();
1810
+ function currentSpanId() {
1811
+ return spanIdStore.getStore();
1812
+ }
1813
+ function newSpanId() {
1814
+ return crypto.randomBytes(8).toString("hex");
1815
+ }
1816
+ function runInSpan(spanId, fn) {
1817
+ return spanIdStore.run(spanId, fn);
1818
+ }
1311
1819
 
1312
1820
  // src/core/logger.ts
1821
+ function formatSpanError(error) {
1822
+ if (error instanceof Error) {
1823
+ return `${error.name}: ${error.message}`;
1824
+ }
1825
+ return String(error);
1826
+ }
1313
1827
  var Logger = class _Logger {
1314
1828
  name;
1315
1829
  transports;
@@ -1320,7 +1834,10 @@ var Logger = class _Logger {
1320
1834
  this.name = name;
1321
1835
  this.currentLevel = parseLevel(options.level ?? 20 /* INFO */);
1322
1836
  this.transports = options.transports ? [...options.transports] : [];
1323
- this.plugins = options.plugins ? [...options.plugins] : [];
1837
+ this.plugins = [];
1838
+ for (const plugin of options.plugins ?? []) {
1839
+ this.use(plugin);
1840
+ }
1324
1841
  this.baseMeta = options.meta ? { ...options.meta } : {};
1325
1842
  }
1326
1843
  get level() {
@@ -1329,9 +1846,14 @@ var Logger = class _Logger {
1329
1846
  setLevel(level) {
1330
1847
  this.currentLevel = parseLevel(level);
1331
1848
  }
1332
- /** Register a plugin. Returns `this` so calls can be chained. */
1849
+ /**
1850
+ * Register a plugin, or a plain `beforeLog`-style function. A function is
1851
+ * wrapped internally as an anonymous `Plugin` (`FunctionPlugin`) — the
1852
+ * same middleware ergonomics as Express/Koa, without needing to read the
1853
+ * `Plugin` interface first. Returns `this` so calls can be chained.
1854
+ */
1333
1855
  use(plugin) {
1334
- this.plugins.push(plugin);
1856
+ this.plugins.push(typeof plugin === "function" ? new FunctionPlugin(plugin) : plugin);
1335
1857
  return this;
1336
1858
  }
1337
1859
  /** Close every attached transport. Call on shutdown to flush buffered writes. */
@@ -1365,6 +1887,10 @@ var Logger = class _Logger {
1365
1887
  message,
1366
1888
  meta: { ...this.baseMeta, ...meta }
1367
1889
  });
1890
+ const parentSpanId = currentSpanId();
1891
+ if (parentSpanId !== void 0) {
1892
+ record.meta.parentSpanId ??= parentSpanId;
1893
+ }
1368
1894
  for (const plugin of this.plugins) {
1369
1895
  let result;
1370
1896
  try {
@@ -1379,7 +1905,11 @@ var Logger = class _Logger {
1379
1905
  record = result;
1380
1906
  }
1381
1907
  for (const transport of this.transports) {
1382
- transport.write(transport.format(record), record);
1908
+ try {
1909
+ transport.write(transport.format(record), record);
1910
+ } catch (error) {
1911
+ console.error(`${transport.constructor.name}: failed to write a log record`, error);
1912
+ }
1383
1913
  }
1384
1914
  for (const plugin of this.plugins) {
1385
1915
  try {
@@ -1408,11 +1938,72 @@ var Logger = class _Logger {
1408
1938
  fatal(message, meta = {}) {
1409
1939
  return this.dispatch(50 /* FATAL */, message, meta);
1410
1940
  }
1941
+ /** `.info()` tagged `meta.kind = "thought"` — an agent's internal reasoning step, for harness/agentic tracing. */
1942
+ thought(message, meta = {}) {
1943
+ return this.dispatch(20 /* INFO */, message, { kind: "thought", ...meta });
1944
+ }
1945
+ /** `.info()` tagged `meta.kind = "action"` — an agent taking an action (a tool call, an LLM request), for harness/agentic tracing. */
1946
+ action(message, meta = {}) {
1947
+ return this.dispatch(20 /* INFO */, message, { kind: "action", ...meta });
1948
+ }
1949
+ /** `.info()` tagged `meta.kind = "observation"` — the result an agent observed from an action, for harness/agentic tracing. */
1950
+ observation(message, meta = {}) {
1951
+ return this.dispatch(20 /* INFO */, message, { kind: "observation", ...meta });
1952
+ }
1953
+ /** `.info()` tagged `meta.kind = "decision"` — an agent's concluding decision for a step or run, for harness/agentic tracing. */
1954
+ decision(message, meta = {}) {
1955
+ return this.dispatch(20 /* INFO */, message, { kind: "decision", ...meta });
1956
+ }
1957
+ /**
1958
+ * `await logger.span("callLlm", async () => {...})` — runs `fn`, and on
1959
+ * settling (success or throw) emits one record for the span itself
1960
+ * carrying `meta.spanId` and `meta.durationMs`. Every record logged
1961
+ * inside `fn` — through any method, and through any further `await` —
1962
+ * is automatically stamped with `meta.parentSpanId` pointing at this
1963
+ * span, so nested/sub-agent calls reconstruct their exact nesting when
1964
+ * sorted by `spanId`/`parentSpanId`.
1965
+ *
1966
+ * Still emits its record — at `ERROR`, with `meta.error` set — if `fn`
1967
+ * throws; the error itself propagates unchanged to the caller.
1968
+ *
1969
+ * `spanId`/`parentSpanId` normally auto-generate/auto-nest; pass them in
1970
+ * `options` to adopt an id handed in from elsewhere (e.g. a framework
1971
+ * adapter translating an id it already received).
1972
+ */
1973
+ async span(name, fn, options = {}) {
1974
+ const { spanId: explicitSpanId, parentSpanId: explicitParentSpanId, ...meta } = options;
1975
+ const spanId = explicitSpanId ?? newSpanId();
1976
+ const start = performance.now();
1977
+ try {
1978
+ const result = await runInSpan(spanId, () => fn());
1979
+ this.finishSpan(name, spanId, explicitParentSpanId, performance.now() - start, meta);
1980
+ return result;
1981
+ } catch (error) {
1982
+ this.finishSpan(name, spanId, explicitParentSpanId, performance.now() - start, meta, error);
1983
+ throw error;
1984
+ }
1985
+ }
1986
+ finishSpan(name, spanId, explicitParentSpanId, durationMs, meta, error) {
1987
+ const fullMeta = {
1988
+ spanId,
1989
+ durationMs: Math.round(durationMs * 1e3) / 1e3,
1990
+ ...meta
1991
+ };
1992
+ if (explicitParentSpanId !== void 0) {
1993
+ fullMeta.parentSpanId = explicitParentSpanId;
1994
+ }
1995
+ fullMeta.kind ??= "span";
1996
+ if (error !== void 0) {
1997
+ fullMeta.error = formatSpanError(error);
1998
+ }
1999
+ this.dispatch(error !== void 0 ? 40 /* ERROR */ : 20 /* INFO */, name, fullMeta);
2000
+ }
1411
2001
  };
1412
2002
 
1413
2003
  // src/index.ts
1414
- var VERSION = "0.2.0";
2004
+ var VERSION = "0.3.0";
1415
2005
 
2006
+ exports.AlertingPlugin = AlertingPlugin;
1416
2007
  exports.AppInsightsTransport = AppInsightsTransport;
1417
2008
  exports.BaseQueueTransport = BaseQueueTransport;
1418
2009
  exports.BaseSQLTransport = BaseSQLTransport;
@@ -1422,11 +2013,15 @@ exports.CloudWatchTransport = CloudWatchTransport;
1422
2013
  exports.CollectingTransport = CollectingTransport;
1423
2014
  exports.ConsoleTransport = ConsoleTransport;
1424
2015
  exports.ContextPlugin = ContextPlugin;
2016
+ exports.DEFAULT_PII_PATTERNS = DEFAULT_PII_PATTERNS;
1425
2017
  exports.DEFAULT_REDACTED_KEYS = DEFAULT_REDACTED_KEYS;
1426
2018
  exports.DatadogTransport = DatadogTransport;
1427
2019
  exports.DynamoDBTransport = DynamoDBTransport;
1428
2020
  exports.ElasticsearchTransport = ElasticsearchTransport;
2021
+ exports.EmailAlertPlugin = EmailAlertPlugin;
1429
2022
  exports.FileTransport = FileTransport;
2023
+ exports.FunctionPlugin = FunctionPlugin;
2024
+ exports.GENESIS_HASH = GENESIS_HASH;
1430
2025
  exports.HTTPTransport = HTTPTransport;
1431
2026
  exports.JSONFormatter = JSONFormatter;
1432
2027
  exports.KafkaTransport = KafkaTransport;
@@ -1435,19 +2030,30 @@ exports.Logger = Logger;
1435
2030
  exports.MongoDBTransport = MongoDBTransport;
1436
2031
  exports.MySQLTransport = MySQLTransport;
1437
2032
  exports.NewRelicTransport = NewRelicTransport;
2033
+ exports.PIIRedactPlugin = PIIRedactPlugin;
2034
+ exports.PagerDutyAlertPlugin = PagerDutyAlertPlugin;
1438
2035
  exports.PostgresTransport = PostgresTransport;
1439
2036
  exports.PubSubTransport = PubSubTransport;
1440
2037
  exports.RabbitMQTransport = RabbitMQTransport;
1441
2038
  exports.RedactPlugin = RedactPlugin;
1442
2039
  exports.RedisTransport = RedisTransport;
2040
+ exports.RunPlugin = RunPlugin;
1443
2041
  exports.SQLiteTransport = SQLiteTransport;
1444
2042
  exports.SQSTransport = SQSTransport;
1445
2043
  exports.SamplingPlugin = SamplingPlugin;
2044
+ exports.SlackAlertPlugin = SlackAlertPlugin;
2045
+ exports.TamperEvidentPlugin = TamperEvidentPlugin;
2046
+ exports.TraceContextPlugin = TraceContextPlugin;
1446
2047
  exports.Transport = Transport;
1447
2048
  exports.VERSION = VERSION;
1448
2049
  exports.createRecord = createRecord;
2050
+ exports.defaultResolveActiveOtelTraceId = defaultResolveActiveOtelTraceId;
2051
+ exports.generateTraceId = generateTraceId;
2052
+ exports.getTraceparent = getTraceparent;
1449
2053
  exports.levelName = levelName;
1450
2054
  exports.parseLevel = parseLevel;
2055
+ exports.parseTraceHeader = parseTraceHeader;
2056
+ exports.setTraceparent = setTraceparent;
1451
2057
  exports.utcTimestamp = utcTimestamp;
1452
2058
  //# sourceMappingURL=index.cjs.map
1453
2059
  //# sourceMappingURL=index.cjs.map