@smithers-orchestrator/observability 0.24.0 → 0.25.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/package.json +2 -2
- package/src/ResolvedSmithersObservabilityOptions.ts +1 -0
- package/src/SmithersEvent.ts +5 -0
- package/src/SmithersObservabilityOptions.ts +1 -0
- package/src/_coreCorrelation/withCorrelationContext.js +13 -0
- package/src/_coreMetrics.js +2 -315
- package/src/_coreTracing.js +18 -31
- package/src/_traceRedaction.js +2 -1
- package/src/createSmithersObservabilityLayer.js +7 -1
- package/src/index.d.ts +148 -1
- package/src/index.js +1 -1
- package/src/logging.js +66 -3
- package/src/metrics/gatewayRunEventBackpressureDisconnectTotal.js +2 -0
- package/src/metrics/index.js +1 -0
- package/src/resolveSmithersObservabilityOptions.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Concrete Smithers metrics, logging, tracing, and observability integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@effect/platform": "^0.96.0",
|
|
36
36
|
"@effect/platform-bun": "^0.89.0",
|
|
37
37
|
"effect": "^3.21.1",
|
|
38
|
-
"@smithers-orchestrator/agents": "0.
|
|
38
|
+
"@smithers-orchestrator/agents": "0.25.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/bun": "latest",
|
package/src/SmithersEvent.ts
CHANGED
|
@@ -5,6 +5,19 @@ import { mergeCorrelationContext } from "./mergeCorrelationContext.js";
|
|
|
5
5
|
/** @typedef {import("./CorrelationPatch.ts").CorrelationPatch} CorrelationPatch */
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
+
* Bridge the Effect-tracked correlation context onto the imperative
|
|
9
|
+
* AsyncLocalStorage store so plain (non-Effect) `getCurrentCorrelationContext()`
|
|
10
|
+
* reads — e.g. from the imperative logger — see the active run/node correlation
|
|
11
|
+
* while the effect executes.
|
|
12
|
+
*
|
|
13
|
+
* IMPORTANT: run the resulting effect with `Effect.runPromise`/`runFork`, never
|
|
14
|
+
* `Effect.runSync`. The acquire step calls `AsyncLocalStorage.enterWith()`, which
|
|
15
|
+
* mutates the *caller's* async context. Under `runSync` the caller is whatever
|
|
16
|
+
* synchronous context invoked it (e.g. a test-runner's root context); enabling
|
|
17
|
+
* ALS async-hooks there leaks into every subsequent timer/promise on that
|
|
18
|
+
* context. `runPromise`/`runFork` execute on an ephemeral fiber context, keeping
|
|
19
|
+
* the enterWith scoped to that fiber.
|
|
20
|
+
*
|
|
8
21
|
* @template A, E, R
|
|
9
22
|
* @param {Effect.Effect<A, E, R>} effect
|
|
10
23
|
* @param {CorrelationPatch} patch
|
package/src/_coreMetrics.js
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import { Context
|
|
2
|
-
import {
|
|
3
|
-
/** @typedef {import("./MetricName.ts").MetricName} MetricName */
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
import { toPrometheusMetricName } from "./_corePrometheus.js";
|
|
4
3
|
/** @typedef {import("./SmithersMetricType.ts").SmithersMetricType} SmithersMetricType */
|
|
5
|
-
/** @typedef {import("./SmithersMetricUnit.ts").SmithersMetricUnit} SmithersMetricUnit */
|
|
6
|
-
/** @typedef {import("./_corePrometheusShape.ts").MetricLabels} MetricLabels */
|
|
7
|
-
/** @typedef {import("./_corePrometheusShape.ts").PrometheusSample} PrometheusSample */
|
|
8
|
-
/** @typedef {import("./_coreMetricsShape.ts").CounterEntry} CounterEntry */
|
|
9
|
-
/** @typedef {import("./_coreMetricsShape.ts").GaugeEntry} GaugeEntry */
|
|
10
|
-
/** @typedef {import("./_coreMetricsShape.ts").HistogramEntry} HistogramEntry */
|
|
11
|
-
/** @typedef {import("./_coreMetricsShape.ts").MetricsSnapshot} MetricsSnapshot */
|
|
12
4
|
/** @typedef {import("./_coreMetricsShape.ts").MetricsServiceShape} MetricsServiceShape */
|
|
13
|
-
/** @typedef {import("./_coreMetricsShape.ts").SmithersMetricEvent} SmithersMetricEvent */
|
|
14
5
|
/** @typedef {import("./SmithersMetricDefinition.ts").SmithersMetricDefinition} SmithersMetricDefinition */
|
|
15
6
|
|
|
16
7
|
/**
|
|
@@ -204,307 +195,3 @@ export const smithersMetrics = Object.freeze(Object.fromEntries(smithersMetricCa
|
|
|
204
195
|
const _MetricsServiceBase = /** @type {Context.TagClass<MetricsService, "MetricsService", MetricsServiceShape>} */ (/** @type {unknown} */ (Context.Tag("MetricsService")()));
|
|
205
196
|
export class MetricsService extends _MetricsServiceBase {
|
|
206
197
|
}
|
|
207
|
-
const DEFAULT_HISTOGRAM_BUCKETS = [
|
|
208
|
-
1,
|
|
209
|
-
5,
|
|
210
|
-
10,
|
|
211
|
-
25,
|
|
212
|
-
50,
|
|
213
|
-
100,
|
|
214
|
-
250,
|
|
215
|
-
500,
|
|
216
|
-
1_000,
|
|
217
|
-
2_500,
|
|
218
|
-
5_000,
|
|
219
|
-
10_000,
|
|
220
|
-
30_000,
|
|
221
|
-
];
|
|
222
|
-
/**
|
|
223
|
-
* @param {MetricLabels} [labels]
|
|
224
|
-
* @returns {string}
|
|
225
|
-
*/
|
|
226
|
-
function labelsKey(labels = {}) {
|
|
227
|
-
return JSON.stringify(Object.entries(labels).sort(([left], [right]) => left.localeCompare(right)));
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* @param {string} name
|
|
231
|
-
* @param {MetricLabels} [labels]
|
|
232
|
-
* @returns {string}
|
|
233
|
-
*/
|
|
234
|
-
function metricKey(name, labels) {
|
|
235
|
-
return `${name}|${labelsKey(labels)}`;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* @param {MetricLabels} [labels]
|
|
239
|
-
* @returns {MetricLabels}
|
|
240
|
-
*/
|
|
241
|
-
function cloneLabels(labels = {}) {
|
|
242
|
-
return Object.freeze({ ...labels });
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* @returns {Context.Tag.Service<MetricsService>}
|
|
246
|
-
*/
|
|
247
|
-
export function makeInMemoryMetricsService() {
|
|
248
|
-
const registry = new Map();
|
|
249
|
-
const processStartMs = Date.now();
|
|
250
|
-
const asyncExternalWaitCounts = {
|
|
251
|
-
approval: 0,
|
|
252
|
-
event: 0,
|
|
253
|
-
};
|
|
254
|
-
/**
|
|
255
|
-
* @param {string} name
|
|
256
|
-
* @param {MetricLabels} [labels]
|
|
257
|
-
* @returns {CounterEntry}
|
|
258
|
-
*/
|
|
259
|
-
function upsertCounter(name, labels) {
|
|
260
|
-
const key = metricKey(name, labels);
|
|
261
|
-
const existing = registry.get(key);
|
|
262
|
-
if (existing?.type === "counter")
|
|
263
|
-
return existing;
|
|
264
|
-
const created = {
|
|
265
|
-
type: "counter",
|
|
266
|
-
value: 0,
|
|
267
|
-
labels: cloneLabels(labels),
|
|
268
|
-
};
|
|
269
|
-
registry.set(key, created);
|
|
270
|
-
return created;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* @param {string} name
|
|
274
|
-
* @param {MetricLabels} [labels]
|
|
275
|
-
* @returns {GaugeEntry}
|
|
276
|
-
*/
|
|
277
|
-
function upsertGauge(name, labels) {
|
|
278
|
-
const key = metricKey(name, labels);
|
|
279
|
-
const existing = registry.get(key);
|
|
280
|
-
if (existing?.type === "gauge")
|
|
281
|
-
return existing;
|
|
282
|
-
const created = {
|
|
283
|
-
type: "gauge",
|
|
284
|
-
value: 0,
|
|
285
|
-
labels: cloneLabels(labels),
|
|
286
|
-
};
|
|
287
|
-
registry.set(key, created);
|
|
288
|
-
return created;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* @param {string} name
|
|
292
|
-
* @param {MetricLabels} [labels]
|
|
293
|
-
* @returns {HistogramEntry}
|
|
294
|
-
*/
|
|
295
|
-
function upsertHistogram(name, labels) {
|
|
296
|
-
const key = metricKey(name, labels);
|
|
297
|
-
const existing = registry.get(key);
|
|
298
|
-
if (existing?.type === "histogram")
|
|
299
|
-
return existing;
|
|
300
|
-
const created = {
|
|
301
|
-
type: "histogram",
|
|
302
|
-
sum: 0,
|
|
303
|
-
count: 0,
|
|
304
|
-
labels: cloneLabels(labels),
|
|
305
|
-
buckets: new Map(DEFAULT_HISTOGRAM_BUCKETS.map((bucket) => [bucket, 0])),
|
|
306
|
-
};
|
|
307
|
-
registry.set(key, created);
|
|
308
|
-
return created;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* @returns {PrometheusSample[]}
|
|
312
|
-
*/
|
|
313
|
-
function samples() {
|
|
314
|
-
return [...registry.entries()].map(([key, entry]) => {
|
|
315
|
-
const name = key.slice(0, key.indexOf("|"));
|
|
316
|
-
if (entry.type === "histogram") {
|
|
317
|
-
return {
|
|
318
|
-
name,
|
|
319
|
-
type: entry.type,
|
|
320
|
-
labels: entry.labels,
|
|
321
|
-
buckets: new Map(entry.buckets),
|
|
322
|
-
sum: entry.sum,
|
|
323
|
-
count: entry.count,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
return {
|
|
327
|
-
name,
|
|
328
|
-
type: entry.type,
|
|
329
|
-
labels: entry.labels,
|
|
330
|
-
value: entry.value,
|
|
331
|
-
};
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
const service = {
|
|
335
|
-
increment: (name, labels) => service.incrementBy(name, 1, labels),
|
|
336
|
-
incrementBy: (name, value, labels) => Effect.sync(() => {
|
|
337
|
-
const key = metricKey(name, labels);
|
|
338
|
-
const existing = registry.get(key);
|
|
339
|
-
const definition = smithersMetricCatalogByName.get(name);
|
|
340
|
-
if (existing?.type === "gauge" || definition?.type === "gauge") {
|
|
341
|
-
upsertGauge(name, labels).value += value;
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
upsertCounter(name, labels).value += value;
|
|
345
|
-
}),
|
|
346
|
-
gauge: (name, value, labels) => Effect.sync(() => {
|
|
347
|
-
upsertGauge(name, labels).value = value;
|
|
348
|
-
}),
|
|
349
|
-
histogram: (name, value, labels) => Effect.sync(() => {
|
|
350
|
-
const entry = upsertHistogram(name, labels);
|
|
351
|
-
entry.count += 1;
|
|
352
|
-
entry.sum += value;
|
|
353
|
-
for (const boundary of DEFAULT_HISTOGRAM_BUCKETS) {
|
|
354
|
-
if (value <= boundary) {
|
|
355
|
-
entry.buckets.set(boundary, (entry.buckets.get(boundary) ?? 0) + 1);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}),
|
|
359
|
-
recordEvent: (event) => {
|
|
360
|
-
const eventType = String(event.type);
|
|
361
|
-
const countEvent = service.increment("smithers.events.emitted_total", {
|
|
362
|
-
type: eventType,
|
|
363
|
-
});
|
|
364
|
-
switch (event.type) {
|
|
365
|
-
case "RunStarted":
|
|
366
|
-
return Effect.all([
|
|
367
|
-
countEvent,
|
|
368
|
-
service.increment("smithers.runs.total"),
|
|
369
|
-
service.incrementBy("smithers.runs.active", 1),
|
|
370
|
-
], { discard: true });
|
|
371
|
-
case "RunFinished":
|
|
372
|
-
return Effect.all([
|
|
373
|
-
countEvent,
|
|
374
|
-
service.incrementBy("smithers.runs.active", -1),
|
|
375
|
-
service.increment("smithers.runs.finished_total"),
|
|
376
|
-
], { discard: true });
|
|
377
|
-
case "RunFailed":
|
|
378
|
-
return Effect.all([
|
|
379
|
-
countEvent,
|
|
380
|
-
service.incrementBy("smithers.runs.active", -1),
|
|
381
|
-
service.increment("smithers.runs.failed_total"),
|
|
382
|
-
service.increment("smithers.errors.total"),
|
|
383
|
-
], { discard: true });
|
|
384
|
-
case "RunCancelled":
|
|
385
|
-
return Effect.all([
|
|
386
|
-
countEvent,
|
|
387
|
-
service.incrementBy("smithers.runs.active", -1),
|
|
388
|
-
service.increment("smithers.runs.cancelled_total"),
|
|
389
|
-
], { discard: true });
|
|
390
|
-
case "RunContinuedAsNew":
|
|
391
|
-
return Effect.all([countEvent, service.increment("smithers.runs.continued_total")], { discard: true });
|
|
392
|
-
case "NodeStarted":
|
|
393
|
-
return Effect.all([
|
|
394
|
-
countEvent,
|
|
395
|
-
service.increment("smithers.nodes.started"),
|
|
396
|
-
service.incrementBy("smithers.nodes.active", 1),
|
|
397
|
-
], { discard: true });
|
|
398
|
-
case "NodeFinished":
|
|
399
|
-
return Effect.all([
|
|
400
|
-
countEvent,
|
|
401
|
-
service.increment("smithers.nodes.finished"),
|
|
402
|
-
service.incrementBy("smithers.nodes.active", -1),
|
|
403
|
-
typeof event.durationMs === "number"
|
|
404
|
-
? service.histogram("smithers.node.duration_ms", event.durationMs)
|
|
405
|
-
: Effect.void,
|
|
406
|
-
], { discard: true });
|
|
407
|
-
case "NodeFailed":
|
|
408
|
-
return Effect.all([
|
|
409
|
-
countEvent,
|
|
410
|
-
service.increment("smithers.nodes.failed"),
|
|
411
|
-
service.increment("smithers.errors.total"),
|
|
412
|
-
service.incrementBy("smithers.nodes.active", -1),
|
|
413
|
-
], { discard: true });
|
|
414
|
-
case "CacheHit":
|
|
415
|
-
return Effect.all([countEvent, service.increment("smithers.cache.hits")], { discard: true });
|
|
416
|
-
case "CacheMiss":
|
|
417
|
-
return Effect.all([countEvent, service.increment("smithers.cache.misses")], { discard: true });
|
|
418
|
-
case "ApprovalRequested":
|
|
419
|
-
return Effect.all([
|
|
420
|
-
countEvent,
|
|
421
|
-
service.increment("smithers.approvals.requested"),
|
|
422
|
-
service.incrementBy("smithers.approval.pending", 1),
|
|
423
|
-
], { discard: true });
|
|
424
|
-
case "ApprovalResolved": {
|
|
425
|
-
const approved = event.approved === true || event.status === "approved";
|
|
426
|
-
return Effect.all([
|
|
427
|
-
countEvent,
|
|
428
|
-
service.increment(approved
|
|
429
|
-
? "smithers.approvals.granted"
|
|
430
|
-
: "smithers.approvals.denied"),
|
|
431
|
-
service.incrementBy("smithers.approval.pending", -1),
|
|
432
|
-
], { discard: true });
|
|
433
|
-
}
|
|
434
|
-
case "TimerCreated":
|
|
435
|
-
return Effect.all([
|
|
436
|
-
countEvent,
|
|
437
|
-
service.increment("smithers.timers.created"),
|
|
438
|
-
service.incrementBy("smithers.timers.pending", 1),
|
|
439
|
-
], { discard: true });
|
|
440
|
-
case "TimerFired":
|
|
441
|
-
return Effect.all([
|
|
442
|
-
countEvent,
|
|
443
|
-
service.increment("smithers.timers.fired"),
|
|
444
|
-
service.incrementBy("smithers.timers.pending", -1),
|
|
445
|
-
], { discard: true });
|
|
446
|
-
case "TaskHeartbeat":
|
|
447
|
-
return Effect.all([countEvent, service.increment("smithers.heartbeats.total")], { discard: true });
|
|
448
|
-
case "TaskHeartbeatTimeout":
|
|
449
|
-
return Effect.all([
|
|
450
|
-
countEvent,
|
|
451
|
-
service.increment("smithers.heartbeats.timeout_total"),
|
|
452
|
-
service.increment("smithers.errors.total"),
|
|
453
|
-
], { discard: true });
|
|
454
|
-
case "TokenUsageReported": {
|
|
455
|
-
const effects = [countEvent];
|
|
456
|
-
const labels = {
|
|
457
|
-
...(typeof event.agent === "string" ? { agent: event.agent } : {}),
|
|
458
|
-
...(typeof event.model === "string" ? { model: event.model } : {}),
|
|
459
|
-
};
|
|
460
|
-
/**
|
|
461
|
-
* @param {string} name
|
|
462
|
-
* @param {unknown} value
|
|
463
|
-
*/
|
|
464
|
-
const push = (name, value) => {
|
|
465
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
466
|
-
effects.push(service.incrementBy(name, value, labels));
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
push("smithers.tokens.input_total", event.inputTokens);
|
|
470
|
-
push("smithers.tokens.output_total", event.outputTokens);
|
|
471
|
-
push("smithers.tokens.cache_read_total", event.cacheReadTokens);
|
|
472
|
-
push("smithers.tokens.cache_write_total", event.cacheWriteTokens);
|
|
473
|
-
push("smithers.tokens.reasoning_total", event.reasoningTokens);
|
|
474
|
-
return Effect.all(effects, { discard: true });
|
|
475
|
-
}
|
|
476
|
-
default:
|
|
477
|
-
return countEvent;
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
updateProcessMetrics: () => Effect.sync(() => {
|
|
481
|
-
const uptimeS = (Date.now() - processStartMs) / 1000;
|
|
482
|
-
const mem = process.memoryUsage();
|
|
483
|
-
upsertGauge("smithers.process.uptime_seconds").value = uptimeS;
|
|
484
|
-
upsertGauge("smithers.process.memory_rss_bytes").value = mem.rss;
|
|
485
|
-
upsertGauge("smithers.process.heap_used_bytes").value = mem.heapUsed;
|
|
486
|
-
}),
|
|
487
|
-
updateAsyncExternalWaitPending: (kind, delta) => Effect.sync(() => {
|
|
488
|
-
asyncExternalWaitCounts[kind] = Math.max(0, asyncExternalWaitCounts[kind] + delta);
|
|
489
|
-
upsertGauge("smithers.external_wait.async_pending", { kind }).value =
|
|
490
|
-
asyncExternalWaitCounts[kind];
|
|
491
|
-
}),
|
|
492
|
-
renderPrometheus: () => Effect.sync(() => renderPrometheusSamples(samples())),
|
|
493
|
-
snapshot: () => Effect.sync(() => new Map(registry)),
|
|
494
|
-
};
|
|
495
|
-
return service;
|
|
496
|
-
}
|
|
497
|
-
/** @type {Layer.Layer<MetricsService, never, never>} */
|
|
498
|
-
export const MetricsServiceLive = Layer.sync(MetricsService, makeInMemoryMetricsService);
|
|
499
|
-
/** @type {Layer.Layer<MetricsService, never, never>} */
|
|
500
|
-
export const MetricsServiceNoop = Layer.succeed(MetricsService, {
|
|
501
|
-
increment: () => Effect.void,
|
|
502
|
-
incrementBy: () => Effect.void,
|
|
503
|
-
gauge: () => Effect.void,
|
|
504
|
-
histogram: () => Effect.void,
|
|
505
|
-
recordEvent: () => Effect.void,
|
|
506
|
-
updateProcessMetrics: () => Effect.void,
|
|
507
|
-
updateAsyncExternalWaitPending: () => Effect.void,
|
|
508
|
-
renderPrometheus: () => Effect.succeed(""),
|
|
509
|
-
snapshot: () => Effect.succeed(new Map()),
|
|
510
|
-
});
|
package/src/_coreTracing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { correlationContextToLogAnnotations, getCurrentCorrelationContext, withCorrelationContext, } from "./_coreCorrelation/index.js";
|
|
3
|
+
import { smithersSpanAttributeAliases } from "./_smithersSpanAttributeAliases.js";
|
|
3
4
|
/** @typedef {import("./SmithersLogFormat.ts").SmithersLogFormat} SmithersLogFormat */
|
|
4
5
|
/** @typedef {import("./_coreTracingShape.ts").SmithersSpanAttributesInput} SmithersSpanAttributesInput */
|
|
5
6
|
/** @typedef {import("./_coreTracingShape.ts").TracingServiceShape} TracingServiceShape */
|
|
@@ -30,25 +31,7 @@ export function getCurrentSmithersTraceAnnotations() {
|
|
|
30
31
|
* @returns {Record<string, unknown>}
|
|
31
32
|
*/
|
|
32
33
|
export function makeSmithersSpanAttributes(attributes = {}) {
|
|
33
|
-
const aliases =
|
|
34
|
-
runId: "smithers.run_id",
|
|
35
|
-
run_id: "smithers.run_id",
|
|
36
|
-
workflowName: "smithers.workflow_name",
|
|
37
|
-
workflow_name: "smithers.workflow_name",
|
|
38
|
-
nodeId: "smithers.node_id",
|
|
39
|
-
node_id: "smithers.node_id",
|
|
40
|
-
iteration: "smithers.iteration",
|
|
41
|
-
attempt: "smithers.attempt",
|
|
42
|
-
nodeLabel: "smithers.node_label",
|
|
43
|
-
node_label: "smithers.node_label",
|
|
44
|
-
toolName: "smithers.tool_name",
|
|
45
|
-
tool_name: "smithers.tool_name",
|
|
46
|
-
agent: "smithers.agent",
|
|
47
|
-
model: "smithers.model",
|
|
48
|
-
status: "smithers.status",
|
|
49
|
-
waitReason: "smithers.wait_reason",
|
|
50
|
-
wait_reason: "smithers.wait_reason",
|
|
51
|
-
};
|
|
34
|
+
const aliases = smithersSpanAttributeAliases;
|
|
52
35
|
const result = {};
|
|
53
36
|
for (const [key, value] of Object.entries(attributes)) {
|
|
54
37
|
if (value !== undefined) {
|
|
@@ -114,18 +97,22 @@ export function annotateSmithersTrace(attributes = {}) {
|
|
|
114
97
|
*/
|
|
115
98
|
export function withSmithersSpan(name, effect, attributes) {
|
|
116
99
|
const spanAttributes = makeSmithersSpanAttributes(attributes);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
return Effect.suspend(() => {
|
|
101
|
+
const annotations = correlationContextToLogAnnotations(getCurrentCorrelationContext());
|
|
102
|
+
let program = effect;
|
|
103
|
+
if (Object.keys(spanAttributes).length > 0) {
|
|
104
|
+
program = program.pipe(Effect.annotateSpans(spanAttributes));
|
|
105
|
+
}
|
|
106
|
+
if (hasAttributes(attributes)) {
|
|
107
|
+
program = program.pipe(Effect.annotateLogs(attributes));
|
|
108
|
+
}
|
|
109
|
+
if (annotations) {
|
|
110
|
+
program = program.pipe(Effect.annotateLogs(annotations));
|
|
111
|
+
}
|
|
112
|
+
return program.pipe(Effect.withLogSpan(name), Effect.withSpan(inferSmithersSpanName(name, attributes), {
|
|
113
|
+
attributes: spanAttributes,
|
|
114
|
+
}));
|
|
115
|
+
});
|
|
129
116
|
}
|
|
130
117
|
/** @type {Layer.Layer<TracingService, never, never>} */
|
|
131
118
|
export const TracingServiceLive = Layer.succeed(TracingService, {
|
package/src/_traceRedaction.js
CHANGED
|
@@ -27,7 +27,8 @@ const rules = [
|
|
|
27
27
|
{
|
|
28
28
|
id: "secret-ish",
|
|
29
29
|
pattern: /\b(?:api[_-]?key|token|secret|password)=([^\s"']+)/gi,
|
|
30
|
-
replace:
|
|
30
|
+
// No `replace` field: redactValue special-cases this rule by id and
|
|
31
|
+
// rewrites the captured value itself, so a top-level replace is never read.
|
|
31
32
|
},
|
|
32
33
|
];
|
|
33
34
|
|
|
@@ -45,5 +45,11 @@ function makeService(options) {
|
|
|
45
45
|
*/
|
|
46
46
|
export function createSmithersObservabilityLayer(options = {}) {
|
|
47
47
|
const resolved = resolveSmithersObservabilityOptions(options);
|
|
48
|
-
|
|
48
|
+
const loggerLayers = resolved.installLogger
|
|
49
|
+
? [
|
|
50
|
+
Logger.replace(Logger.defaultLogger, resolveLogger(resolved.logFormat)),
|
|
51
|
+
Logger.minimumLogLevel(resolved.logLevel),
|
|
52
|
+
]
|
|
53
|
+
: [];
|
|
54
|
+
return Layer.mergeAll(BunContext.layer, ...loggerLayers, createSmithersOtelLayer(resolved), MetricsServiceLive, TracingServiceLive, Layer.succeed(SmithersObservability, makeService(resolved)));
|
|
49
55
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ type ResolvedSmithersObservabilityOptions$2 = {
|
|
|
13
13
|
readonly serviceName: string;
|
|
14
14
|
readonly logFormat: SmithersLogFormat$1;
|
|
15
15
|
readonly logLevel: LogLevel.LogLevel;
|
|
16
|
+
readonly installLogger: boolean;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
type SmithersObservabilityService$1 = {
|
|
@@ -27,6 +28,7 @@ type SmithersObservabilityOptions$4 = {
|
|
|
27
28
|
readonly serviceName?: string;
|
|
28
29
|
readonly logFormat?: SmithersLogFormat$1;
|
|
29
30
|
readonly logLevel?: LogLevel.LogLevel | string;
|
|
31
|
+
readonly installLogger?: boolean;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type MetricLabels$1 = Readonly<Record<string, string | number | boolean>>;
|
|
@@ -47,6 +49,89 @@ type SmithersMetricDefinition$2 = {
|
|
|
47
49
|
readonly boundaries?: readonly number[];
|
|
48
50
|
};
|
|
49
51
|
|
|
52
|
+
type AgentFamily = "pi" | "codex" | "claude-code" | "antigravity" | "gemini" | "kimi" | "openai" | "anthropic" | "amp" | "forge" | "unknown";
|
|
53
|
+
type AgentCaptureMode = "sdk-events" | "rpc-events" | "cli-json-stream" | "cli-json" | "cli-text" | "artifact-import";
|
|
54
|
+
type TraceCompleteness = "full-observed" | "partial-observed" | "final-only" | "capture-failed";
|
|
55
|
+
type CanonicalAgentTraceEventKind = "session.start" | "session.end" | "turn.start" | "turn.end" | "message.start" | "message.update" | "message.end" | "assistant.text.delta" | "assistant.thinking.delta" | "assistant.message.final" | "tool.execution.start" | "tool.execution.update" | "tool.execution.end" | "tool.result" | "retry.start" | "retry.end" | "compaction.start" | "compaction.end" | "stderr" | "stdout" | "usage" | "capture.warning" | "capture.error" | "artifact.created";
|
|
56
|
+
type CanonicalAgentTraceEventPhase = "agent" | "turn" | "message" | "tool" | "session" | "capture" | "artifact";
|
|
57
|
+
type CanonicalAgentTraceEvent = {
|
|
58
|
+
traceVersion: "1";
|
|
59
|
+
runId: string;
|
|
60
|
+
workflowPath?: string;
|
|
61
|
+
workflowHash?: string;
|
|
62
|
+
nodeId: string;
|
|
63
|
+
iteration: number;
|
|
64
|
+
attempt: number;
|
|
65
|
+
timestampMs: number;
|
|
66
|
+
event: {
|
|
67
|
+
sequence: number;
|
|
68
|
+
kind: CanonicalAgentTraceEventKind;
|
|
69
|
+
phase: CanonicalAgentTraceEventPhase;
|
|
70
|
+
};
|
|
71
|
+
source: {
|
|
72
|
+
agentFamily: AgentFamily;
|
|
73
|
+
captureMode: AgentCaptureMode;
|
|
74
|
+
rawType?: string;
|
|
75
|
+
rawEventId?: string;
|
|
76
|
+
observed: boolean;
|
|
77
|
+
};
|
|
78
|
+
traceCompleteness: TraceCompleteness;
|
|
79
|
+
payload: Record<string, unknown> | null;
|
|
80
|
+
raw: unknown;
|
|
81
|
+
redaction: {
|
|
82
|
+
applied: boolean;
|
|
83
|
+
ruleIds: string[];
|
|
84
|
+
};
|
|
85
|
+
annotations: Record<string, string | number | boolean>;
|
|
86
|
+
};
|
|
87
|
+
type AgentTraceSummary = {
|
|
88
|
+
traceVersion: "1";
|
|
89
|
+
runId: string;
|
|
90
|
+
workflowPath?: string;
|
|
91
|
+
workflowHash?: string;
|
|
92
|
+
nodeId: string;
|
|
93
|
+
iteration: number;
|
|
94
|
+
attempt: number;
|
|
95
|
+
traceStartedAtMs: number;
|
|
96
|
+
traceFinishedAtMs: number;
|
|
97
|
+
agentFamily: AgentFamily;
|
|
98
|
+
agentId?: string;
|
|
99
|
+
model?: string;
|
|
100
|
+
captureMode: AgentCaptureMode;
|
|
101
|
+
traceCompleteness: TraceCompleteness;
|
|
102
|
+
unsupportedEventKinds: CanonicalAgentTraceEventKind[];
|
|
103
|
+
missingExpectedEventKinds: CanonicalAgentTraceEventKind[];
|
|
104
|
+
rawArtifactRefs: string[];
|
|
105
|
+
};
|
|
106
|
+
type AgentSessionTranscriptEvent = {
|
|
107
|
+
transcriptVersion: "1";
|
|
108
|
+
runId: string;
|
|
109
|
+
workflowPath?: string;
|
|
110
|
+
workflowHash?: string;
|
|
111
|
+
nodeId: string;
|
|
112
|
+
iteration: number;
|
|
113
|
+
attempt: number;
|
|
114
|
+
timestampMs: number;
|
|
115
|
+
event: {
|
|
116
|
+
sequence: number;
|
|
117
|
+
rowType: string;
|
|
118
|
+
};
|
|
119
|
+
source: {
|
|
120
|
+
agentFamily: AgentFamily;
|
|
121
|
+
captureMode: AgentCaptureMode;
|
|
122
|
+
ingestSource: "live" | "artifact";
|
|
123
|
+
observedLive: boolean;
|
|
124
|
+
providerSessionId?: string;
|
|
125
|
+
providerThreadId?: string;
|
|
126
|
+
};
|
|
127
|
+
raw: unknown;
|
|
128
|
+
redaction: {
|
|
129
|
+
applied: boolean;
|
|
130
|
+
ruleIds: string[];
|
|
131
|
+
};
|
|
132
|
+
annotations: Record<string, string | number | boolean>;
|
|
133
|
+
};
|
|
134
|
+
|
|
50
135
|
type RunStatus = "running" | "waiting-approval" | "waiting-event" | "waiting-timer" | "finished" | "continued" | "failed" | "cancelled";
|
|
51
136
|
type RunState = "running" | "waiting-approval" | "waiting-event" | "waiting-timer" | "recovering" | "stale" | "orphaned" | "failed" | "cancelled" | "succeeded" | "unknown";
|
|
52
137
|
type AgentCliActionKind = "turn" | "command" | "tool" | "file_change" | "web_search" | "todo_list" | "reasoning" | "warning" | "note";
|
|
@@ -230,6 +315,11 @@ type SmithersEvent$2 = {
|
|
|
230
315
|
runId: string;
|
|
231
316
|
frameNo: number;
|
|
232
317
|
xmlHash: string;
|
|
318
|
+
trigger?: {
|
|
319
|
+
reason: string;
|
|
320
|
+
nodeId?: string;
|
|
321
|
+
iteration?: number;
|
|
322
|
+
};
|
|
233
323
|
timestampMs: number;
|
|
234
324
|
} | {
|
|
235
325
|
type: "NodePending";
|
|
@@ -565,6 +655,30 @@ type SmithersEvent$2 = {
|
|
|
565
655
|
runId: string;
|
|
566
656
|
timerId: string;
|
|
567
657
|
timestampMs: number;
|
|
658
|
+
} | {
|
|
659
|
+
type: "AgentTraceEvent";
|
|
660
|
+
runId: string;
|
|
661
|
+
nodeId: string;
|
|
662
|
+
iteration: number;
|
|
663
|
+
attempt: number;
|
|
664
|
+
trace: CanonicalAgentTraceEvent;
|
|
665
|
+
timestampMs: number;
|
|
666
|
+
} | {
|
|
667
|
+
type: "AgentTraceSummary";
|
|
668
|
+
runId: string;
|
|
669
|
+
nodeId: string;
|
|
670
|
+
iteration: number;
|
|
671
|
+
attempt: number;
|
|
672
|
+
summary: AgentTraceSummary;
|
|
673
|
+
timestampMs: number;
|
|
674
|
+
} | {
|
|
675
|
+
type: "AgentSessionEvent";
|
|
676
|
+
runId: string;
|
|
677
|
+
nodeId: string;
|
|
678
|
+
iteration: number;
|
|
679
|
+
attempt: number;
|
|
680
|
+
transcript: AgentSessionTranscriptEvent;
|
|
681
|
+
timestampMs: number;
|
|
568
682
|
};
|
|
569
683
|
|
|
570
684
|
type MetricName = string;
|
|
@@ -995,11 +1109,40 @@ declare function correlationContextToLogAnnotations(context?: CorrelationContext
|
|
|
995
1109
|
type CorrelationContext$1 = CorrelationContext$5;
|
|
996
1110
|
|
|
997
1111
|
/**
|
|
1112
|
+
* Temporary compatibility shim for legacy, non-Effect callers.
|
|
1113
|
+
*
|
|
1114
|
+
* Unlike the FiberRef-based core implementation
|
|
1115
|
+
* ({@link import("./_coreCorrelation/updateCurrentCorrelationContext.js").updateCurrentCorrelationContext}),
|
|
1116
|
+
* which returns an Effect and sets a fresh merged context on the
|
|
1117
|
+
* `correlationContextFiberRef`, this shim runs synchronously and applies the
|
|
1118
|
+
* patch by **mutating the current context object in place** via
|
|
1119
|
+
* `Object.assign(current, next)`. Any references already holding the current
|
|
1120
|
+
* context object will observe the mutation. This in-place semantics is
|
|
1121
|
+
* intentional and exists only to preserve behavior for callers that captured a
|
|
1122
|
+
* context reference before the Effect-based API existed.
|
|
1123
|
+
*
|
|
1124
|
+
* If there is no current context, the patch is a no-op (nothing is created).
|
|
1125
|
+
*
|
|
1126
|
+
* @deprecated Prefer the Effect-returning
|
|
1127
|
+
* `updateCurrentCorrelationContext` from
|
|
1128
|
+
* `@smithers-orchestrator/observability` (the `_coreCorrelation` version),
|
|
1129
|
+
* which does not mutate shared state. This shim will be removed once legacy
|
|
1130
|
+
* callers migrate.
|
|
1131
|
+
*
|
|
998
1132
|
* @param {CorrelationPatch} patch
|
|
1133
|
+
* @returns {void}
|
|
999
1134
|
*/
|
|
1000
1135
|
declare function updateCurrentCorrelationContext(patch: CorrelationPatch$1): void;
|
|
1001
1136
|
type CorrelationPatch$1 = CorrelationPatch$5;
|
|
1002
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Install the Effect runtime used by fire-and-forget observability logs.
|
|
1140
|
+
* Returns a restore function so tests and embedded hosts can scope overrides.
|
|
1141
|
+
*
|
|
1142
|
+
* @param {SmithersLogRunner | null} runner
|
|
1143
|
+
* @returns {() => void}
|
|
1144
|
+
*/
|
|
1145
|
+
declare function setSmithersLogRunner(runner: SmithersLogRunner | null): () => void;
|
|
1003
1146
|
/**
|
|
1004
1147
|
* @param {string} message
|
|
1005
1148
|
* @param {LogAnnotations} [annotations]
|
|
@@ -1024,6 +1167,10 @@ declare function logWarning(message: string, annotations?: LogAnnotations, span?
|
|
|
1024
1167
|
* @param {string} [span]
|
|
1025
1168
|
*/
|
|
1026
1169
|
declare function logError(message: string, annotations?: LogAnnotations, span?: string): void;
|
|
1170
|
+
type SmithersLogRunner = {
|
|
1171
|
+
runFork: (effect: Effect.Effect<void, never, never>) => unknown;
|
|
1172
|
+
runPromise: (effect: Effect.Effect<void, never, never>) => Promise<void>;
|
|
1173
|
+
};
|
|
1027
1174
|
type LogAnnotations = Record<string, unknown> | undefined;
|
|
1028
1175
|
|
|
1029
1176
|
type CorrelationContext = CorrelationContext$5;
|
|
@@ -1039,4 +1186,4 @@ type SmithersMetricDefinition = SmithersMetricDefinition$2;
|
|
|
1039
1186
|
type SmithersObservabilityOptions = SmithersObservabilityOptions$4;
|
|
1040
1187
|
type SmithersObservabilityService = SmithersObservabilityService$1;
|
|
1041
1188
|
|
|
1042
|
-
export { type CorrelationContext, CorrelationContextLive, type CorrelationContextPatch, CorrelationContextService, type CorrelationPatch, type MetricLabels, MetricsService, MetricsServiceLive, type MetricsServiceShape, type MetricsSnapshot, type ResolvedSmithersObservabilityOptions, type SmithersEvent, type SmithersLogFormat, type SmithersMetricDefinition, SmithersObservability, type SmithersObservabilityOptions, type SmithersObservabilityService, TracingService, TracingServiceLive, activeNodes, activeRuns, annotateSmithersTrace, approvalPending, approvalWaitDuration, approvalsDenied, approvalsGranted, approvalsRequested, attemptDuration, cacheHits, cacheMisses, correlationContextFiberRef, correlationContextToLogAnnotations, createSmithersObservabilityLayer, createSmithersOtelLayer, createSmithersRuntimeLayer, dbQueryDuration, dbRetries, dbTransactionDuration, dbTransactionRetries, dbTransactionRollbacks, errorsTotal, eventsEmittedTotal, externalWaitAsyncPending, getCurrentCorrelationContext, getCurrentCorrelationContextEffect, getCurrentSmithersTraceAnnotations, getCurrentSmithersTraceSpan, hotReloadDuration, hotReloadFailures, hotReloads, httpRequestDuration, httpRequests, logDebug, logError, logInfo, logWarning, makeSmithersSpanAttributes, mergeCorrelationContext, metricsServiceAdapter, nodeDuration, nodeRetriesTotal, nodesFailed, nodesFinished, nodesStarted, processHeapUsedBytes, processMemoryRssBytes, processUptimeSeconds, prometheusContentType, promptSizeBytes, renderPrometheusMetrics, replaysStarted, resolveSmithersObservabilityOptions, responseSizeBytes, rewindDurationMs, rewindFramesDeleted, rewindRollbackTotal, rewindSandboxesReverted, rewindTotal, runDuration, runForksCreated, runWithCorrelationContext, runsAncestryDepth, runsCancelledTotal, runsCarriedStateBytes, runsContinuedTotal, runsFailedTotal, runsFinishedTotal, runsResumedTotal, runsTotal, sandboxActive, sandboxBundleSizeBytes, sandboxCompletedTotal, sandboxCreatedTotal, sandboxDurationMs, sandboxPatchCount, sandboxTransportDurationMs, schedulerConcurrencyUtilization, schedulerQueueDepth, schedulerWaitDuration, scorerEventsFailed, scorerEventsFinished, scorerEventsStarted, smithersMetricCatalog, smithersMetrics, smithersSpanNames, snapshotDuration, snapshotsCaptured, timerDelayDuration, timersCancelled, timersCreated, timersFired, timersPending, toPrometheusMetricName, tokensCacheReadTotal, tokensCacheWriteTotal, tokensContextWindowBucketTotal, tokensContextWindowPerCall, tokensInputPerCall, tokensInputTotal, tokensOutputPerCall, tokensOutputTotal, tokensReasoningTotal, toolCallErrorsTotal, toolCallsTotal, toolDuration, toolOutputTruncatedTotal, trackEvent as trackSmithersEvent, updateCurrentCorrelationContext, updateProcessMetrics, vcsDuration, withCorrelationContext, withCurrentCorrelationContext, withSmithersSpan };
|
|
1189
|
+
export { type CorrelationContext, CorrelationContextLive, type CorrelationContextPatch, CorrelationContextService, type CorrelationPatch, type MetricLabels, MetricsService, MetricsServiceLive, type MetricsServiceShape, type MetricsSnapshot, type ResolvedSmithersObservabilityOptions, type SmithersEvent, type SmithersLogFormat, type SmithersMetricDefinition, SmithersObservability, type SmithersObservabilityOptions, type SmithersObservabilityService, TracingService, TracingServiceLive, activeNodes, activeRuns, annotateSmithersTrace, approvalPending, approvalWaitDuration, approvalsDenied, approvalsGranted, approvalsRequested, attemptDuration, cacheHits, cacheMisses, correlationContextFiberRef, correlationContextToLogAnnotations, createSmithersObservabilityLayer, createSmithersOtelLayer, createSmithersRuntimeLayer, dbQueryDuration, dbRetries, dbTransactionDuration, dbTransactionRetries, dbTransactionRollbacks, errorsTotal, eventsEmittedTotal, externalWaitAsyncPending, getCurrentCorrelationContext, getCurrentCorrelationContextEffect, getCurrentSmithersTraceAnnotations, getCurrentSmithersTraceSpan, hotReloadDuration, hotReloadFailures, hotReloads, httpRequestDuration, httpRequests, logDebug, logError, logInfo, logWarning, makeSmithersSpanAttributes, mergeCorrelationContext, metricsServiceAdapter, nodeDuration, nodeRetriesTotal, nodesFailed, nodesFinished, nodesStarted, processHeapUsedBytes, processMemoryRssBytes, processUptimeSeconds, prometheusContentType, promptSizeBytes, renderPrometheusMetrics, replaysStarted, resolveSmithersObservabilityOptions, responseSizeBytes, rewindDurationMs, rewindFramesDeleted, rewindRollbackTotal, rewindSandboxesReverted, rewindTotal, runDuration, runForksCreated, runWithCorrelationContext, runsAncestryDepth, runsCancelledTotal, runsCarriedStateBytes, runsContinuedTotal, runsFailedTotal, runsFinishedTotal, runsResumedTotal, runsTotal, sandboxActive, sandboxBundleSizeBytes, sandboxCompletedTotal, sandboxCreatedTotal, sandboxDurationMs, sandboxPatchCount, sandboxTransportDurationMs, schedulerConcurrencyUtilization, schedulerQueueDepth, schedulerWaitDuration, scorerEventsFailed, scorerEventsFinished, scorerEventsStarted, setSmithersLogRunner, smithersMetricCatalog, smithersMetrics, smithersSpanNames, snapshotDuration, snapshotsCaptured, timerDelayDuration, timersCancelled, timersCreated, timersFired, timersPending, toPrometheusMetricName, tokensCacheReadTotal, tokensCacheWriteTotal, tokensContextWindowBucketTotal, tokensContextWindowPerCall, tokensInputPerCall, tokensInputTotal, tokensOutputPerCall, tokensOutputTotal, tokensReasoningTotal, toolCallErrorsTotal, toolCallsTotal, toolDuration, toolOutputTruncatedTotal, trackEvent as trackSmithersEvent, updateCurrentCorrelationContext, updateProcessMetrics, vcsDuration, withCorrelationContext, withCurrentCorrelationContext, withSmithersSpan };
|
package/src/index.js
CHANGED
|
@@ -32,4 +32,4 @@ export { rewindTotal, rewindRollbackTotal, rewindDurationMs, rewindFramesDeleted
|
|
|
32
32
|
export { activeNodes, activeRuns, approvalPending, externalWaitAsyncPending, approvalsDenied, approvalsGranted, approvalsRequested, approvalWaitDuration, timerDelayDuration, timersCancelled, timersCreated, timersFired, timersPending, attemptDuration, cacheHits, cacheMisses, dbQueryDuration, dbRetries, dbTransactionDuration, dbTransactionRetries, dbTransactionRollbacks, errorsTotal, eventsEmittedTotal, hotReloadDuration, hotReloadFailures, hotReloads, httpRequestDuration, httpRequests, nodeDuration, nodeRetriesTotal, nodesFailed, nodesFinished, nodesStarted, processHeapUsedBytes, processMemoryRssBytes, processUptimeSeconds, promptSizeBytes, responseSizeBytes, runDuration, runsCancelledTotal, runsContinuedTotal, runsFailedTotal, runsFinishedTotal, runsResumedTotal, runsAncestryDepth, runsCarriedStateBytes, sandboxActive, sandboxBundleSizeBytes, sandboxCompletedTotal, sandboxCreatedTotal, sandboxDurationMs, sandboxPatchCount, sandboxTransportDurationMs, runsTotal, schedulerConcurrencyUtilization, schedulerQueueDepth, schedulerWaitDuration, tokensCacheReadTotal, tokensCacheWriteTotal, tokensContextWindowBucketTotal, tokensContextWindowPerCall, tokensInputPerCall, tokensInputTotal, tokensOutputPerCall, tokensOutputTotal, tokensReasoningTotal, toolCallErrorsTotal, toolCallsTotal, toolDuration, toolOutputTruncatedTotal, scorerEventsStarted, scorerEventsFinished, scorerEventsFailed, snapshotsCaptured, runForksCreated, replaysStarted, snapshotDuration, trackEvent as trackSmithersEvent, updateProcessMetrics, vcsDuration, toPrometheusMetricName, smithersMetricCatalog, metricsServiceAdapter, } from "./metrics/index.js";
|
|
33
33
|
export { correlationContextFiberRef, correlationContextToLogAnnotations, CorrelationContextLive, CorrelationContextService, getCurrentCorrelationContext, getCurrentCorrelationContextEffect, mergeCorrelationContext, runWithCorrelationContext, withCorrelationContext, withCurrentCorrelationContext, } from "./correlation.js";
|
|
34
34
|
export { updateCurrentCorrelationContext } from "./correlation.js";
|
|
35
|
-
export { logDebug, logInfo, logWarning, logError, } from "./logging.js";
|
|
35
|
+
export { logDebug, logInfo, logWarning, logError, setSmithersLogRunner, } from "./logging.js";
|
package/src/logging.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Logger, LogLevel } from "effect";
|
|
2
2
|
import { getCurrentSmithersTraceAnnotations } from "./getCurrentSmithersTraceAnnotations.js";
|
|
3
3
|
import { correlationContextToLogAnnotations, getCurrentCorrelationContext, withCurrentCorrelationContext, } from "./correlation.js";
|
|
4
4
|
/**
|
|
@@ -11,6 +11,7 @@ const LOG_LEVEL_DEBUG = 1;
|
|
|
11
11
|
const LOG_LEVEL_INFO = 2;
|
|
12
12
|
const LOG_LEVEL_WARNING = 3;
|
|
13
13
|
const LOG_LEVEL_ERROR = 4;
|
|
14
|
+
const LOG_RUNNER_KEY = Symbol.for("smithers.observability.logRunner");
|
|
14
15
|
|
|
15
16
|
/** @returns {number} */
|
|
16
17
|
function resolveMinLevel() {
|
|
@@ -31,6 +32,58 @@ function resolveMinLevel() {
|
|
|
31
32
|
|
|
32
33
|
const minLevel = resolveMinLevel();
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {{
|
|
37
|
+
* runFork: (effect: Effect.Effect<void, never, never>) => unknown;
|
|
38
|
+
* runPromise: (effect: Effect.Effect<void, never, never>) => Promise<void>;
|
|
39
|
+
* }} SmithersLogRunner
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** @returns {{ runner: SmithersLogRunner | null }} */
|
|
43
|
+
function getRunnerState() {
|
|
44
|
+
const globalState = /** @type {typeof globalThis & { [LOG_RUNNER_KEY]?: { runner: SmithersLogRunner | null } }} */ (globalThis);
|
|
45
|
+
globalState[LOG_RUNNER_KEY] ??= { runner: null };
|
|
46
|
+
return globalState[LOG_RUNNER_KEY];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @type {SmithersLogRunner} */
|
|
50
|
+
const defaultRunner = {
|
|
51
|
+
runFork: (effect) => Effect.runFork(effect),
|
|
52
|
+
runPromise: (effect) => Effect.runPromise(effect),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** @returns {SmithersLogRunner} */
|
|
56
|
+
function getLogRunner() {
|
|
57
|
+
return getRunnerState().runner ?? defaultRunner;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Install the Effect runtime used by fire-and-forget observability logs.
|
|
62
|
+
* Returns a restore function so tests and embedded hosts can scope overrides.
|
|
63
|
+
*
|
|
64
|
+
* @param {SmithersLogRunner | null} runner
|
|
65
|
+
* @returns {() => void}
|
|
66
|
+
*/
|
|
67
|
+
export function setSmithersLogRunner(runner) {
|
|
68
|
+
const state = getRunnerState();
|
|
69
|
+
const previous = state.runner;
|
|
70
|
+
state.runner = runner;
|
|
71
|
+
return () => {
|
|
72
|
+
state.runner = previous;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @param {number} level */
|
|
77
|
+
function toEffectLogLevel(level) {
|
|
78
|
+
switch (level) {
|
|
79
|
+
case LOG_LEVEL_DEBUG: return LogLevel.Debug;
|
|
80
|
+
case LOG_LEVEL_INFO: return LogLevel.Info;
|
|
81
|
+
case LOG_LEVEL_WARNING: return LogLevel.Warning;
|
|
82
|
+
case LOG_LEVEL_ERROR: return LogLevel.Error;
|
|
83
|
+
default: return LogLevel.All;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
/**
|
|
35
88
|
* @param {Effect.Effect<void, never, never>} effect
|
|
36
89
|
* @param {LogAnnotations} [annotations]
|
|
@@ -66,7 +119,12 @@ function buildLogProgram(effect, annotations, span) {
|
|
|
66
119
|
function emitLog(effect, annotations, span, level = LOG_LEVEL_INFO) {
|
|
67
120
|
if (level < minLevel) return;
|
|
68
121
|
const program = buildLogProgram(effect, annotations, span);
|
|
69
|
-
if (program)
|
|
122
|
+
if (!program) return;
|
|
123
|
+
try {
|
|
124
|
+
void getLogRunner().runFork(program.pipe(Logger.withMinimumLogLevel(toEffectLogLevel(level))));
|
|
125
|
+
} catch {
|
|
126
|
+
// Logging must never break the caller.
|
|
127
|
+
}
|
|
70
128
|
}
|
|
71
129
|
|
|
72
130
|
/**
|
|
@@ -79,7 +137,12 @@ function emitLog(effect, annotations, span, level = LOG_LEVEL_INFO) {
|
|
|
79
137
|
async function emitLogAwait(effect, annotations, span, level = LOG_LEVEL_INFO) {
|
|
80
138
|
if (level < minLevel) return;
|
|
81
139
|
const program = buildLogProgram(effect, annotations, span);
|
|
82
|
-
if (program)
|
|
140
|
+
if (!program) return;
|
|
141
|
+
try {
|
|
142
|
+
await getLogRunner().runPromise(program.pipe(Logger.withMinimumLogLevel(toEffectLogLevel(level))));
|
|
143
|
+
} catch {
|
|
144
|
+
// Logging must never break the caller.
|
|
145
|
+
}
|
|
83
146
|
}
|
|
84
147
|
/**
|
|
85
148
|
* @param {string} message
|
package/src/metrics/index.js
CHANGED
|
@@ -92,6 +92,7 @@ export { gatewayWebhooksRejectedTotal } from "./gatewayWebhooksRejectedTotal.js"
|
|
|
92
92
|
export { devtoolsSubscribeTotal } from "./devtoolsSubscribeTotal.js";
|
|
93
93
|
export { devtoolsEventTotal } from "./devtoolsEventTotal.js";
|
|
94
94
|
export { devtoolsBackpressureDisconnectTotal } from "./devtoolsBackpressureDisconnectTotal.js";
|
|
95
|
+
export { gatewayRunEventBackpressureDisconnectTotal } from "./gatewayRunEventBackpressureDisconnectTotal.js";
|
|
95
96
|
export { eventsEmittedTotal } from "./eventsEmittedTotal.js";
|
|
96
97
|
export { taskHeartbeatsTotal } from "./taskHeartbeatsTotal.js";
|
|
97
98
|
export { taskHeartbeatTimeoutTotal } from "./taskHeartbeatTimeoutTotal.js";
|
|
@@ -75,5 +75,6 @@ export function resolveSmithersObservabilityOptions(options = {}) {
|
|
|
75
75
|
? resolveLogFormat(options.logFormat)
|
|
76
76
|
: resolveLogFormat(process.env.SMITHERS_LOG_FORMAT),
|
|
77
77
|
logLevel: resolveLogLevel(options.logLevel ?? process.env.SMITHERS_LOG_LEVEL),
|
|
78
|
+
installLogger: options.installLogger !== false,
|
|
78
79
|
};
|
|
79
80
|
}
|