@contractspec/lib.observability 3.7.5 → 3.7.7
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/AGENTS.md +46 -21
- package/CHANGELOG.md +10 -0
- package/README.md +56 -63
- package/dist/index.d.ts +12 -12
- package/dist/index.js +298 -298
- package/dist/logging/index.js +1 -1
- package/dist/metrics/index.d.ts +1 -1
- package/dist/node/index.js +298 -298
- package/dist/node/logging/index.js +1 -1
- package/dist/node/tracing/middleware.js +24 -24
- package/dist/tracing/middleware.js +24 -24
- package/package.json +8 -8
package/dist/node/index.js
CHANGED
|
@@ -147,241 +147,6 @@ class RootCauseAnalyzer {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// src/tracing/core.ts
|
|
151
|
-
import {
|
|
152
|
-
SpanStatusCode,
|
|
153
|
-
trace
|
|
154
|
-
} from "@opentelemetry/api";
|
|
155
|
-
var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
|
|
156
|
-
function getTracer(name = DEFAULT_TRACER_NAME) {
|
|
157
|
-
return trace.getTracer(name);
|
|
158
|
-
}
|
|
159
|
-
async function traceAsync(name, fn, tracerName) {
|
|
160
|
-
const tracer = getTracer(tracerName);
|
|
161
|
-
return tracer.startActiveSpan(name, async (span) => {
|
|
162
|
-
try {
|
|
163
|
-
const result = await fn(span);
|
|
164
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
165
|
-
return result;
|
|
166
|
-
} catch (error) {
|
|
167
|
-
span.recordException(error);
|
|
168
|
-
span.setStatus({
|
|
169
|
-
code: SpanStatusCode.ERROR,
|
|
170
|
-
message: error instanceof Error ? error.message : String(error)
|
|
171
|
-
});
|
|
172
|
-
throw error;
|
|
173
|
-
} finally {
|
|
174
|
-
span.end();
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
function traceSync(name, fn, tracerName) {
|
|
179
|
-
const tracer = getTracer(tracerName);
|
|
180
|
-
return tracer.startActiveSpan(name, (span) => {
|
|
181
|
-
try {
|
|
182
|
-
const result = fn(span);
|
|
183
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
184
|
-
return result;
|
|
185
|
-
} catch (error) {
|
|
186
|
-
span.recordException(error);
|
|
187
|
-
span.setStatus({
|
|
188
|
-
code: SpanStatusCode.ERROR,
|
|
189
|
-
message: error instanceof Error ? error.message : String(error)
|
|
190
|
-
});
|
|
191
|
-
throw error;
|
|
192
|
-
} finally {
|
|
193
|
-
span.end();
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// src/tracing/model-selection.span.ts
|
|
199
|
-
async function traceModelSelection(fn, input) {
|
|
200
|
-
const startMs = performance.now();
|
|
201
|
-
return traceAsync("model.selection", async (span) => {
|
|
202
|
-
const result = await fn();
|
|
203
|
-
const durationMs = performance.now() - startMs;
|
|
204
|
-
span.setAttribute("model.selected", input.modelId);
|
|
205
|
-
span.setAttribute("model.provider", input.providerKey);
|
|
206
|
-
span.setAttribute("model.score", input.score);
|
|
207
|
-
span.setAttribute("model.alternatives_count", input.alternativesCount);
|
|
208
|
-
span.setAttribute("model.selection_duration_ms", durationMs);
|
|
209
|
-
span.setAttribute("model.reason", input.reason);
|
|
210
|
-
if (input.dimension) {
|
|
211
|
-
span.setAttribute("model.dimension", input.dimension);
|
|
212
|
-
}
|
|
213
|
-
if (input.constraints) {
|
|
214
|
-
span.setAttribute("model.constraints", JSON.stringify(input.constraints));
|
|
215
|
-
}
|
|
216
|
-
return result;
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
// src/telemetry/model-selection-telemetry.ts
|
|
220
|
-
class ModelSelectionTelemetry {
|
|
221
|
-
provider;
|
|
222
|
-
eventName;
|
|
223
|
-
constructor(provider, options) {
|
|
224
|
-
this.provider = provider;
|
|
225
|
-
this.eventName = options?.eventName ?? "$model_selection";
|
|
226
|
-
}
|
|
227
|
-
async trackSelection(distinctId, properties) {
|
|
228
|
-
await this.provider.capture({
|
|
229
|
-
distinctId,
|
|
230
|
-
event: this.eventName,
|
|
231
|
-
timestamp: new Date,
|
|
232
|
-
properties: {
|
|
233
|
-
$model_id: properties.modelId,
|
|
234
|
-
$model_provider: properties.providerKey,
|
|
235
|
-
$model_score: properties.score,
|
|
236
|
-
$model_dimension: properties.dimension ?? null,
|
|
237
|
-
$model_reason: properties.reason,
|
|
238
|
-
$model_alternatives_count: properties.alternativesCount,
|
|
239
|
-
$model_cost_estimate_input: properties.costEstimateInput ?? null,
|
|
240
|
-
$model_cost_estimate_output: properties.costEstimateOutput ?? null,
|
|
241
|
-
$model_selection_duration_ms: properties.selectionDurationMs ?? null
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// src/metrics/index.ts
|
|
248
|
-
import {
|
|
249
|
-
metrics
|
|
250
|
-
} from "@opentelemetry/api";
|
|
251
|
-
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
252
|
-
function getMeter(name = DEFAULT_METER_NAME) {
|
|
253
|
-
return metrics.getMeter(name);
|
|
254
|
-
}
|
|
255
|
-
function createCounter(name, description, meterName) {
|
|
256
|
-
return getMeter(meterName).createCounter(name, { description });
|
|
257
|
-
}
|
|
258
|
-
function createUpDownCounter(name, description, meterName) {
|
|
259
|
-
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
260
|
-
}
|
|
261
|
-
function createHistogram(name, description, meterName) {
|
|
262
|
-
return getMeter(meterName).createHistogram(name, { description });
|
|
263
|
-
}
|
|
264
|
-
var standardMetrics = {
|
|
265
|
-
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
266
|
-
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
267
|
-
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
268
|
-
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// src/logging/index.ts
|
|
272
|
-
import { trace as trace2, context } from "@opentelemetry/api";
|
|
273
|
-
|
|
274
|
-
class Logger {
|
|
275
|
-
serviceName;
|
|
276
|
-
constructor(serviceName) {
|
|
277
|
-
this.serviceName = serviceName;
|
|
278
|
-
}
|
|
279
|
-
log(level, message, meta = {}) {
|
|
280
|
-
const span = trace2.getSpan(context.active());
|
|
281
|
-
const traceId = span?.spanContext().traceId;
|
|
282
|
-
const spanId = span?.spanContext().spanId;
|
|
283
|
-
const entry = {
|
|
284
|
-
timestamp: new Date().toISOString(),
|
|
285
|
-
service: this.serviceName,
|
|
286
|
-
level,
|
|
287
|
-
message,
|
|
288
|
-
traceId,
|
|
289
|
-
spanId,
|
|
290
|
-
...meta
|
|
291
|
-
};
|
|
292
|
-
console.log(JSON.stringify(entry));
|
|
293
|
-
}
|
|
294
|
-
debug(message, meta) {
|
|
295
|
-
this.log("debug", message, meta);
|
|
296
|
-
}
|
|
297
|
-
info(message, meta) {
|
|
298
|
-
this.log("info", message, meta);
|
|
299
|
-
}
|
|
300
|
-
warn(message, meta) {
|
|
301
|
-
this.log("warn", message, meta);
|
|
302
|
-
}
|
|
303
|
-
error(message, meta) {
|
|
304
|
-
this.log("error", message, meta);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
var logger = new Logger(process.env.OTEL_SERVICE_NAME || "unknown-service");
|
|
308
|
-
|
|
309
|
-
// src/tracing/middleware.ts
|
|
310
|
-
function createTracingMiddleware(options = {}) {
|
|
311
|
-
return async (req, next) => {
|
|
312
|
-
const method = req.method;
|
|
313
|
-
const url = new URL(req.url);
|
|
314
|
-
const path = url.pathname;
|
|
315
|
-
standardMetrics.httpRequests.add(1, { method, path });
|
|
316
|
-
const startTime = performance.now();
|
|
317
|
-
return traceAsync(`HTTP ${method} ${path}`, async (span) => {
|
|
318
|
-
span.setAttribute("http.method", method);
|
|
319
|
-
span.setAttribute("http.url", req.url);
|
|
320
|
-
try {
|
|
321
|
-
const response = await next();
|
|
322
|
-
span.setAttribute("http.status_code", response.status);
|
|
323
|
-
const duration = (performance.now() - startTime) / 1000;
|
|
324
|
-
standardMetrics.httpDuration.record(duration, {
|
|
325
|
-
method,
|
|
326
|
-
path,
|
|
327
|
-
status: response.status.toString()
|
|
328
|
-
});
|
|
329
|
-
emitTelemetrySample({
|
|
330
|
-
req,
|
|
331
|
-
res: response,
|
|
332
|
-
span,
|
|
333
|
-
success: true,
|
|
334
|
-
durationMs: duration * 1000,
|
|
335
|
-
options
|
|
336
|
-
});
|
|
337
|
-
return response;
|
|
338
|
-
} catch (error) {
|
|
339
|
-
standardMetrics.operationErrors.add(1, { method, path });
|
|
340
|
-
emitTelemetrySample({
|
|
341
|
-
req,
|
|
342
|
-
span,
|
|
343
|
-
success: false,
|
|
344
|
-
durationMs: performance.now() - startTime,
|
|
345
|
-
error,
|
|
346
|
-
options
|
|
347
|
-
});
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
function emitTelemetrySample({
|
|
354
|
-
req,
|
|
355
|
-
res,
|
|
356
|
-
span,
|
|
357
|
-
success,
|
|
358
|
-
durationMs,
|
|
359
|
-
error,
|
|
360
|
-
options
|
|
361
|
-
}) {
|
|
362
|
-
if (!options.onSample || !options.resolveOperation)
|
|
363
|
-
return;
|
|
364
|
-
const operation = options.resolveOperation({ req, res });
|
|
365
|
-
if (!operation)
|
|
366
|
-
return;
|
|
367
|
-
const sample = {
|
|
368
|
-
operation,
|
|
369
|
-
durationMs,
|
|
370
|
-
success,
|
|
371
|
-
timestamp: new Date,
|
|
372
|
-
errorCode: !success && error instanceof Error ? error.name : success ? undefined : "unknown",
|
|
373
|
-
tenantId: options.tenantResolver?.(req),
|
|
374
|
-
actorId: options.actorResolver?.(req),
|
|
375
|
-
traceId: span.spanContext().traceId,
|
|
376
|
-
metadata: {
|
|
377
|
-
method: req.method,
|
|
378
|
-
path: new URL(req.url).pathname,
|
|
379
|
-
status: res?.status
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
options.onSample(sample);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
150
|
// src/intent/aggregator.ts
|
|
386
151
|
var DEFAULT_WINDOW_MS = 15 * 60 * 1000;
|
|
387
152
|
|
|
@@ -400,11 +165,11 @@ class IntentAggregator {
|
|
|
400
165
|
const minTimestamp = now.getTime() - this.windowMs;
|
|
401
166
|
const windowSamples = this.samples.filter((sample) => sample.timestamp.getTime() >= minTimestamp);
|
|
402
167
|
this.samples.length = 0;
|
|
403
|
-
const
|
|
168
|
+
const metrics = this.aggregateMetrics(windowSamples);
|
|
404
169
|
const sequences = this.buildSequences(windowSamples);
|
|
405
170
|
const timestamps = windowSamples.map((sample) => sample.timestamp.getTime());
|
|
406
171
|
return {
|
|
407
|
-
metrics
|
|
172
|
+
metrics,
|
|
408
173
|
sequences,
|
|
409
174
|
sampleCount: windowSamples.length,
|
|
410
175
|
windowStart: timestamps.length ? new Date(Math.min(...timestamps)) : undefined,
|
|
@@ -619,6 +384,68 @@ class IntentDetector {
|
|
|
619
384
|
}
|
|
620
385
|
}
|
|
621
386
|
|
|
387
|
+
// src/logging/index.ts
|
|
388
|
+
import { context, trace } from "@opentelemetry/api";
|
|
389
|
+
|
|
390
|
+
class Logger {
|
|
391
|
+
serviceName;
|
|
392
|
+
constructor(serviceName) {
|
|
393
|
+
this.serviceName = serviceName;
|
|
394
|
+
}
|
|
395
|
+
log(level, message, meta = {}) {
|
|
396
|
+
const span = trace.getSpan(context.active());
|
|
397
|
+
const traceId = span?.spanContext().traceId;
|
|
398
|
+
const spanId = span?.spanContext().spanId;
|
|
399
|
+
const entry = {
|
|
400
|
+
timestamp: new Date().toISOString(),
|
|
401
|
+
service: this.serviceName,
|
|
402
|
+
level,
|
|
403
|
+
message,
|
|
404
|
+
traceId,
|
|
405
|
+
spanId,
|
|
406
|
+
...meta
|
|
407
|
+
};
|
|
408
|
+
console.log(JSON.stringify(entry));
|
|
409
|
+
}
|
|
410
|
+
debug(message, meta) {
|
|
411
|
+
this.log("debug", message, meta);
|
|
412
|
+
}
|
|
413
|
+
info(message, meta) {
|
|
414
|
+
this.log("info", message, meta);
|
|
415
|
+
}
|
|
416
|
+
warn(message, meta) {
|
|
417
|
+
this.log("warn", message, meta);
|
|
418
|
+
}
|
|
419
|
+
error(message, meta) {
|
|
420
|
+
this.log("error", message, meta);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
var logger = new Logger(process.env.OTEL_SERVICE_NAME || "unknown-service");
|
|
424
|
+
|
|
425
|
+
// src/metrics/index.ts
|
|
426
|
+
import {
|
|
427
|
+
metrics
|
|
428
|
+
} from "@opentelemetry/api";
|
|
429
|
+
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
430
|
+
function getMeter(name = DEFAULT_METER_NAME) {
|
|
431
|
+
return metrics.getMeter(name);
|
|
432
|
+
}
|
|
433
|
+
function createCounter(name, description, meterName) {
|
|
434
|
+
return getMeter(meterName).createCounter(name, { description });
|
|
435
|
+
}
|
|
436
|
+
function createUpDownCounter(name, description, meterName) {
|
|
437
|
+
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
438
|
+
}
|
|
439
|
+
function createHistogram(name, description, meterName) {
|
|
440
|
+
return getMeter(meterName).createHistogram(name, { description });
|
|
441
|
+
}
|
|
442
|
+
var standardMetrics = {
|
|
443
|
+
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
444
|
+
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
445
|
+
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
446
|
+
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
447
|
+
};
|
|
448
|
+
|
|
622
449
|
// src/pipeline/evolution-pipeline.ts
|
|
623
450
|
import { EventEmitter } from "node:events";
|
|
624
451
|
class EvolutionPipeline {
|
|
@@ -726,71 +553,41 @@ class LifecycleKpiPipeline {
|
|
|
726
553
|
if (previous !== undefined) {
|
|
727
554
|
this.stageUpDownCounter.add(-1, {
|
|
728
555
|
stage: getStageLabel(previous),
|
|
729
|
-
tenantId
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });
|
|
733
|
-
this.currentStageByTenant.set(tenantId, stage);
|
|
734
|
-
this.emitter.emit("event", {
|
|
735
|
-
type: "stage.changed",
|
|
736
|
-
payload: { tenantId, previousStage: previous, nextStage: stage }
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// src/telemetry/posthog-telemetry.ts
|
|
742
|
-
class PosthogTelemetryProvider {
|
|
743
|
-
provider;
|
|
744
|
-
eventPrefix;
|
|
745
|
-
includeMetadata;
|
|
746
|
-
constructor(provider, options = {}) {
|
|
747
|
-
this.provider = provider;
|
|
748
|
-
this.eventPrefix = options.eventPrefix ?? "observability";
|
|
749
|
-
this.includeMetadata = options.includeMetadata ?? false;
|
|
750
|
-
}
|
|
751
|
-
async captureSample(sample) {
|
|
752
|
-
await this.provider.capture({
|
|
753
|
-
distinctId: sample.actorId ?? sample.tenantId ?? "unknown",
|
|
754
|
-
event: `${this.eventPrefix}.operation`,
|
|
755
|
-
timestamp: sample.timestamp,
|
|
756
|
-
properties: {
|
|
757
|
-
operation: sample.operation.name,
|
|
758
|
-
version: sample.operation.version,
|
|
759
|
-
durationMs: sample.durationMs,
|
|
760
|
-
success: sample.success,
|
|
761
|
-
errorCode: sample.errorCode ?? null,
|
|
762
|
-
tenantId: sample.tenantId ?? null,
|
|
763
|
-
traceId: sample.traceId ?? null,
|
|
764
|
-
...this.includeMetadata && sample.metadata ? { metadata: sample.metadata } : {}
|
|
765
|
-
}
|
|
556
|
+
tenantId
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });
|
|
560
|
+
this.currentStageByTenant.set(tenantId, stage);
|
|
561
|
+
this.emitter.emit("event", {
|
|
562
|
+
type: "stage.changed",
|
|
563
|
+
payload: { tenantId, previousStage: previous, nextStage: stage }
|
|
766
564
|
});
|
|
767
565
|
}
|
|
768
|
-
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/telemetry/model-selection-telemetry.ts
|
|
569
|
+
class ModelSelectionTelemetry {
|
|
570
|
+
provider;
|
|
571
|
+
eventName;
|
|
572
|
+
constructor(provider, options) {
|
|
573
|
+
this.provider = provider;
|
|
574
|
+
this.eventName = options?.eventName ?? "$model_selection";
|
|
575
|
+
}
|
|
576
|
+
async trackSelection(distinctId, properties) {
|
|
769
577
|
await this.provider.capture({
|
|
770
|
-
distinctId
|
|
771
|
-
event:
|
|
772
|
-
timestamp:
|
|
578
|
+
distinctId,
|
|
579
|
+
event: this.eventName,
|
|
580
|
+
timestamp: new Date,
|
|
773
581
|
properties: {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
totalCalls: metric.totalCalls,
|
|
784
|
-
successRate: metric.successRate,
|
|
785
|
-
errorRate: metric.errorRate,
|
|
786
|
-
averageLatencyMs: metric.averageLatencyMs,
|
|
787
|
-
p95LatencyMs: metric.p95LatencyMs,
|
|
788
|
-
p99LatencyMs: metric.p99LatencyMs,
|
|
789
|
-
maxLatencyMs: metric.maxLatencyMs,
|
|
790
|
-
topErrors: metric.topErrors
|
|
791
|
-
})),
|
|
792
|
-
sequences: snapshot.sequences
|
|
793
|
-
} : {}
|
|
582
|
+
$model_id: properties.modelId,
|
|
583
|
+
$model_provider: properties.providerKey,
|
|
584
|
+
$model_score: properties.score,
|
|
585
|
+
$model_dimension: properties.dimension ?? null,
|
|
586
|
+
$model_reason: properties.reason,
|
|
587
|
+
$model_alternatives_count: properties.alternativesCount,
|
|
588
|
+
$model_cost_estimate_input: properties.costEstimateInput ?? null,
|
|
589
|
+
$model_cost_estimate_output: properties.costEstimateOutput ?? null,
|
|
590
|
+
$model_selection_duration_ms: properties.selectionDurationMs ?? null
|
|
794
591
|
}
|
|
795
592
|
});
|
|
796
593
|
}
|
|
@@ -1101,6 +898,209 @@ function toDate(value) {
|
|
|
1101
898
|
function isRecord(value) {
|
|
1102
899
|
return typeof value === "object" && value !== null;
|
|
1103
900
|
}
|
|
901
|
+
|
|
902
|
+
// src/telemetry/posthog-telemetry.ts
|
|
903
|
+
class PosthogTelemetryProvider {
|
|
904
|
+
provider;
|
|
905
|
+
eventPrefix;
|
|
906
|
+
includeMetadata;
|
|
907
|
+
constructor(provider, options = {}) {
|
|
908
|
+
this.provider = provider;
|
|
909
|
+
this.eventPrefix = options.eventPrefix ?? "observability";
|
|
910
|
+
this.includeMetadata = options.includeMetadata ?? false;
|
|
911
|
+
}
|
|
912
|
+
async captureSample(sample) {
|
|
913
|
+
await this.provider.capture({
|
|
914
|
+
distinctId: sample.actorId ?? sample.tenantId ?? "unknown",
|
|
915
|
+
event: `${this.eventPrefix}.operation`,
|
|
916
|
+
timestamp: sample.timestamp,
|
|
917
|
+
properties: {
|
|
918
|
+
operation: sample.operation.name,
|
|
919
|
+
version: sample.operation.version,
|
|
920
|
+
durationMs: sample.durationMs,
|
|
921
|
+
success: sample.success,
|
|
922
|
+
errorCode: sample.errorCode ?? null,
|
|
923
|
+
tenantId: sample.tenantId ?? null,
|
|
924
|
+
traceId: sample.traceId ?? null,
|
|
925
|
+
...this.includeMetadata && sample.metadata ? { metadata: sample.metadata } : {}
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
async captureSnapshot(snapshot) {
|
|
930
|
+
await this.provider.capture({
|
|
931
|
+
distinctId: "system",
|
|
932
|
+
event: `${this.eventPrefix}.window`,
|
|
933
|
+
timestamp: snapshot.windowEnd ?? new Date,
|
|
934
|
+
properties: {
|
|
935
|
+
sampleCount: snapshot.sampleCount,
|
|
936
|
+
metricsCount: snapshot.metrics.length,
|
|
937
|
+
sequencesCount: snapshot.sequences.length,
|
|
938
|
+
windowStart: snapshot.windowStart?.toISOString() ?? null,
|
|
939
|
+
windowEnd: snapshot.windowEnd?.toISOString() ?? null,
|
|
940
|
+
...this.includeMetadata ? {
|
|
941
|
+
metrics: snapshot.metrics.map((metric) => ({
|
|
942
|
+
operation: metric.operation.name,
|
|
943
|
+
version: metric.operation.version,
|
|
944
|
+
totalCalls: metric.totalCalls,
|
|
945
|
+
successRate: metric.successRate,
|
|
946
|
+
errorRate: metric.errorRate,
|
|
947
|
+
averageLatencyMs: metric.averageLatencyMs,
|
|
948
|
+
p95LatencyMs: metric.p95LatencyMs,
|
|
949
|
+
p99LatencyMs: metric.p99LatencyMs,
|
|
950
|
+
maxLatencyMs: metric.maxLatencyMs,
|
|
951
|
+
topErrors: metric.topErrors
|
|
952
|
+
})),
|
|
953
|
+
sequences: snapshot.sequences
|
|
954
|
+
} : {}
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/tracing/core.ts
|
|
961
|
+
import {
|
|
962
|
+
SpanStatusCode,
|
|
963
|
+
trace as trace2
|
|
964
|
+
} from "@opentelemetry/api";
|
|
965
|
+
var DEFAULT_TRACER_NAME = "@contractspec/lib.observability";
|
|
966
|
+
function getTracer(name = DEFAULT_TRACER_NAME) {
|
|
967
|
+
return trace2.getTracer(name);
|
|
968
|
+
}
|
|
969
|
+
async function traceAsync(name, fn, tracerName) {
|
|
970
|
+
const tracer = getTracer(tracerName);
|
|
971
|
+
return tracer.startActiveSpan(name, async (span) => {
|
|
972
|
+
try {
|
|
973
|
+
const result = await fn(span);
|
|
974
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
975
|
+
return result;
|
|
976
|
+
} catch (error) {
|
|
977
|
+
span.recordException(error);
|
|
978
|
+
span.setStatus({
|
|
979
|
+
code: SpanStatusCode.ERROR,
|
|
980
|
+
message: error instanceof Error ? error.message : String(error)
|
|
981
|
+
});
|
|
982
|
+
throw error;
|
|
983
|
+
} finally {
|
|
984
|
+
span.end();
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
function traceSync(name, fn, tracerName) {
|
|
989
|
+
const tracer = getTracer(tracerName);
|
|
990
|
+
return tracer.startActiveSpan(name, (span) => {
|
|
991
|
+
try {
|
|
992
|
+
const result = fn(span);
|
|
993
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
994
|
+
return result;
|
|
995
|
+
} catch (error) {
|
|
996
|
+
span.recordException(error);
|
|
997
|
+
span.setStatus({
|
|
998
|
+
code: SpanStatusCode.ERROR,
|
|
999
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1000
|
+
});
|
|
1001
|
+
throw error;
|
|
1002
|
+
} finally {
|
|
1003
|
+
span.end();
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/tracing/model-selection.span.ts
|
|
1009
|
+
async function traceModelSelection(fn, input) {
|
|
1010
|
+
const startMs = performance.now();
|
|
1011
|
+
return traceAsync("model.selection", async (span) => {
|
|
1012
|
+
const result = await fn();
|
|
1013
|
+
const durationMs = performance.now() - startMs;
|
|
1014
|
+
span.setAttribute("model.selected", input.modelId);
|
|
1015
|
+
span.setAttribute("model.provider", input.providerKey);
|
|
1016
|
+
span.setAttribute("model.score", input.score);
|
|
1017
|
+
span.setAttribute("model.alternatives_count", input.alternativesCount);
|
|
1018
|
+
span.setAttribute("model.selection_duration_ms", durationMs);
|
|
1019
|
+
span.setAttribute("model.reason", input.reason);
|
|
1020
|
+
if (input.dimension) {
|
|
1021
|
+
span.setAttribute("model.dimension", input.dimension);
|
|
1022
|
+
}
|
|
1023
|
+
if (input.constraints) {
|
|
1024
|
+
span.setAttribute("model.constraints", JSON.stringify(input.constraints));
|
|
1025
|
+
}
|
|
1026
|
+
return result;
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
// src/tracing/middleware.ts
|
|
1030
|
+
function createTracingMiddleware(options = {}) {
|
|
1031
|
+
return async (req, next) => {
|
|
1032
|
+
const method = req.method;
|
|
1033
|
+
const url = new URL(req.url);
|
|
1034
|
+
const path = url.pathname;
|
|
1035
|
+
standardMetrics.httpRequests.add(1, { method, path });
|
|
1036
|
+
const startTime = performance.now();
|
|
1037
|
+
return traceAsync(`HTTP ${method} ${path}`, async (span) => {
|
|
1038
|
+
span.setAttribute("http.method", method);
|
|
1039
|
+
span.setAttribute("http.url", req.url);
|
|
1040
|
+
try {
|
|
1041
|
+
const response = await next();
|
|
1042
|
+
span.setAttribute("http.status_code", response.status);
|
|
1043
|
+
const duration = (performance.now() - startTime) / 1000;
|
|
1044
|
+
standardMetrics.httpDuration.record(duration, {
|
|
1045
|
+
method,
|
|
1046
|
+
path,
|
|
1047
|
+
status: response.status.toString()
|
|
1048
|
+
});
|
|
1049
|
+
emitTelemetrySample({
|
|
1050
|
+
req,
|
|
1051
|
+
res: response,
|
|
1052
|
+
span,
|
|
1053
|
+
success: true,
|
|
1054
|
+
durationMs: duration * 1000,
|
|
1055
|
+
options
|
|
1056
|
+
});
|
|
1057
|
+
return response;
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
standardMetrics.operationErrors.add(1, { method, path });
|
|
1060
|
+
emitTelemetrySample({
|
|
1061
|
+
req,
|
|
1062
|
+
span,
|
|
1063
|
+
success: false,
|
|
1064
|
+
durationMs: performance.now() - startTime,
|
|
1065
|
+
error,
|
|
1066
|
+
options
|
|
1067
|
+
});
|
|
1068
|
+
throw error;
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function emitTelemetrySample({
|
|
1074
|
+
req,
|
|
1075
|
+
res,
|
|
1076
|
+
span,
|
|
1077
|
+
success,
|
|
1078
|
+
durationMs,
|
|
1079
|
+
error,
|
|
1080
|
+
options
|
|
1081
|
+
}) {
|
|
1082
|
+
if (!options.onSample || !options.resolveOperation)
|
|
1083
|
+
return;
|
|
1084
|
+
const operation = options.resolveOperation({ req, res });
|
|
1085
|
+
if (!operation)
|
|
1086
|
+
return;
|
|
1087
|
+
const sample = {
|
|
1088
|
+
operation,
|
|
1089
|
+
durationMs,
|
|
1090
|
+
success,
|
|
1091
|
+
timestamp: new Date,
|
|
1092
|
+
errorCode: !success && error instanceof Error ? error.name : success ? undefined : "unknown",
|
|
1093
|
+
tenantId: options.tenantResolver?.(req),
|
|
1094
|
+
actorId: options.actorResolver?.(req),
|
|
1095
|
+
traceId: span.spanContext().traceId,
|
|
1096
|
+
metadata: {
|
|
1097
|
+
method: req.method,
|
|
1098
|
+
path: new URL(req.url).pathname,
|
|
1099
|
+
status: res?.status
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
options.onSample(sample);
|
|
1103
|
+
}
|
|
1104
1104
|
export {
|
|
1105
1105
|
traceSync,
|
|
1106
1106
|
traceModelSelection,
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
// src/metrics/index.ts
|
|
2
|
+
import {
|
|
3
|
+
metrics
|
|
4
|
+
} from "@opentelemetry/api";
|
|
5
|
+
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
6
|
+
function getMeter(name = DEFAULT_METER_NAME) {
|
|
7
|
+
return metrics.getMeter(name);
|
|
8
|
+
}
|
|
9
|
+
function createCounter(name, description, meterName) {
|
|
10
|
+
return getMeter(meterName).createCounter(name, { description });
|
|
11
|
+
}
|
|
12
|
+
function createUpDownCounter(name, description, meterName) {
|
|
13
|
+
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
14
|
+
}
|
|
15
|
+
function createHistogram(name, description, meterName) {
|
|
16
|
+
return getMeter(meterName).createHistogram(name, { description });
|
|
17
|
+
}
|
|
18
|
+
var standardMetrics = {
|
|
19
|
+
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
20
|
+
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
21
|
+
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
22
|
+
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
23
|
+
};
|
|
24
|
+
|
|
1
25
|
// src/tracing/core.ts
|
|
2
26
|
import {
|
|
3
27
|
SpanStatusCode,
|
|
@@ -67,30 +91,6 @@ async function traceModelSelection(fn, input) {
|
|
|
67
91
|
return result;
|
|
68
92
|
});
|
|
69
93
|
}
|
|
70
|
-
// src/metrics/index.ts
|
|
71
|
-
import {
|
|
72
|
-
metrics
|
|
73
|
-
} from "@opentelemetry/api";
|
|
74
|
-
var DEFAULT_METER_NAME = "@contractspec/lib.observability";
|
|
75
|
-
function getMeter(name = DEFAULT_METER_NAME) {
|
|
76
|
-
return metrics.getMeter(name);
|
|
77
|
-
}
|
|
78
|
-
function createCounter(name, description, meterName) {
|
|
79
|
-
return getMeter(meterName).createCounter(name, { description });
|
|
80
|
-
}
|
|
81
|
-
function createUpDownCounter(name, description, meterName) {
|
|
82
|
-
return getMeter(meterName).createUpDownCounter(name, { description });
|
|
83
|
-
}
|
|
84
|
-
function createHistogram(name, description, meterName) {
|
|
85
|
-
return getMeter(meterName).createHistogram(name, { description });
|
|
86
|
-
}
|
|
87
|
-
var standardMetrics = {
|
|
88
|
-
httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
|
|
89
|
-
httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
|
|
90
|
-
operationErrors: createCounter("operation_errors_total", "Total operation errors"),
|
|
91
|
-
workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
|
|
92
|
-
};
|
|
93
|
-
|
|
94
94
|
// src/tracing/middleware.ts
|
|
95
95
|
function createTracingMiddleware(options = {}) {
|
|
96
96
|
return async (req, next) => {
|