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