@lssm/lib.observability 0.2.2 → 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/CHANGELOG.md +22 -0
- package/README.md +11 -0
- package/package.json +9 -6
- package/.turbo/turbo-build.log +0 -92
- package/.turbo/turbo-lint.log +0 -20
- package/dist/anomaly/alert-manager.d.mts.map +0 -1
- package/dist/anomaly/alert-manager.mjs.map +0 -1
- package/dist/anomaly/anomaly-detector.d.mts.map +0 -1
- package/dist/anomaly/anomaly-detector.mjs.map +0 -1
- package/dist/anomaly/baseline-calculator.d.mts.map +0 -1
- package/dist/anomaly/baseline-calculator.mjs.map +0 -1
- package/dist/anomaly/root-cause-analyzer.d.mts.map +0 -1
- package/dist/anomaly/root-cause-analyzer.mjs.map +0 -1
- package/dist/intent/aggregator.d.mts.map +0 -1
- package/dist/intent/aggregator.mjs.map +0 -1
- package/dist/intent/detector.d.mts.map +0 -1
- package/dist/intent/detector.mjs.map +0 -1
- package/dist/lifecycle/dist/types/axes.mjs.map +0 -1
- package/dist/lifecycle/dist/types/stages.mjs.map +0 -1
- package/dist/lifecycle/dist/utils/formatters.mjs.map +0 -1
- package/dist/logging/index.d.mts.map +0 -1
- package/dist/logging/index.mjs.map +0 -1
- package/dist/metrics/index.d.mts.map +0 -1
- package/dist/metrics/index.mjs.map +0 -1
- package/dist/pipeline/evolution-pipeline.d.mts.map +0 -1
- package/dist/pipeline/evolution-pipeline.mjs.map +0 -1
- package/dist/pipeline/lifecycle-pipeline.d.mts.map +0 -1
- package/dist/pipeline/lifecycle-pipeline.mjs.map +0 -1
- package/dist/tracing/index.d.mts.map +0 -1
- package/dist/tracing/index.mjs.map +0 -1
- package/dist/tracing/middleware.d.mts.map +0 -1
- package/dist/tracing/middleware.mjs.map +0 -1
- package/src/anomaly/alert-manager.ts +0 -31
- package/src/anomaly/anomaly-detector.ts +0 -94
- package/src/anomaly/baseline-calculator.ts +0 -54
- package/src/anomaly/root-cause-analyzer.ts +0 -55
- package/src/index.ts +0 -47
- package/src/intent/aggregator.ts +0 -161
- package/src/intent/detector.ts +0 -187
- package/src/logging/index.ts +0 -56
- package/src/metrics/index.ts +0 -53
- package/src/pipeline/evolution-pipeline.ts +0 -90
- package/src/pipeline/lifecycle-pipeline.ts +0 -105
- package/src/tracing/index.ts +0 -61
- package/src/tracing/middleware.ts +0 -111
- package/tsconfig.json +0 -16
- package/tsconfig.tsbuildinfo +0 -1
- package/tsdown.config.js +0 -6
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { LifecycleAssessment, LifecycleStage } from '@lssm/lib.lifecycle';
|
|
3
|
-
import { getStageLabel } from '@lssm/lib.lifecycle';
|
|
4
|
-
import {
|
|
5
|
-
createCounter,
|
|
6
|
-
createHistogram,
|
|
7
|
-
createUpDownCounter,
|
|
8
|
-
} from '../metrics';
|
|
9
|
-
|
|
10
|
-
export type LifecyclePipelineEvent =
|
|
11
|
-
| {
|
|
12
|
-
type: 'assessment.recorded';
|
|
13
|
-
payload: { tenantId?: string; stage: LifecycleStage };
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
type: 'stage.changed';
|
|
17
|
-
payload: {
|
|
18
|
-
tenantId?: string;
|
|
19
|
-
previousStage?: LifecycleStage;
|
|
20
|
-
nextStage: LifecycleStage;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
type: 'confidence.low';
|
|
25
|
-
payload: { tenantId?: string; confidence: number };
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export interface LifecycleKpiPipelineOptions {
|
|
29
|
-
meterName?: string;
|
|
30
|
-
emitter?: EventEmitter;
|
|
31
|
-
lowConfidenceThreshold?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class LifecycleKpiPipeline {
|
|
35
|
-
private readonly assessmentCounter;
|
|
36
|
-
private readonly confidenceHistogram;
|
|
37
|
-
private readonly stageUpDownCounter;
|
|
38
|
-
private readonly emitter: EventEmitter;
|
|
39
|
-
private readonly lowConfidenceThreshold: number;
|
|
40
|
-
private readonly currentStageByTenant = new Map<string, LifecycleStage>();
|
|
41
|
-
|
|
42
|
-
constructor(options: LifecycleKpiPipelineOptions = {}) {
|
|
43
|
-
const meterName = options.meterName ?? '@lssm/lib.lifecycle-kpi';
|
|
44
|
-
this.assessmentCounter = createCounter(
|
|
45
|
-
'lifecycle_assessments_total',
|
|
46
|
-
'Total lifecycle assessments',
|
|
47
|
-
meterName
|
|
48
|
-
);
|
|
49
|
-
this.confidenceHistogram = createHistogram(
|
|
50
|
-
'lifecycle_assessment_confidence',
|
|
51
|
-
'Lifecycle assessment confidence distribution',
|
|
52
|
-
meterName
|
|
53
|
-
);
|
|
54
|
-
this.stageUpDownCounter = createUpDownCounter(
|
|
55
|
-
'lifecycle_stage_tenants',
|
|
56
|
-
'Current tenants per lifecycle stage',
|
|
57
|
-
meterName
|
|
58
|
-
);
|
|
59
|
-
this.emitter = options.emitter ?? new EventEmitter();
|
|
60
|
-
this.lowConfidenceThreshold = options.lowConfidenceThreshold ?? 0.4;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
recordAssessment(assessment: LifecycleAssessment, tenantId?: string) {
|
|
64
|
-
const stageLabel = getStageLabel(assessment.stage);
|
|
65
|
-
const attributes = { stage: stageLabel, tenantId };
|
|
66
|
-
this.assessmentCounter.add(1, attributes);
|
|
67
|
-
this.confidenceHistogram.record(assessment.confidence, attributes);
|
|
68
|
-
|
|
69
|
-
this.ensureStageCounters(assessment.stage, tenantId);
|
|
70
|
-
this.emitter.emit('event', {
|
|
71
|
-
type: 'assessment.recorded',
|
|
72
|
-
payload: { tenantId, stage: assessment.stage },
|
|
73
|
-
} satisfies LifecyclePipelineEvent);
|
|
74
|
-
|
|
75
|
-
if (assessment.confidence < this.lowConfidenceThreshold) {
|
|
76
|
-
this.emitter.emit('event', {
|
|
77
|
-
type: 'confidence.low',
|
|
78
|
-
payload: { tenantId, confidence: assessment.confidence },
|
|
79
|
-
} satisfies LifecyclePipelineEvent);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
on(listener: (event: LifecyclePipelineEvent) => void) {
|
|
84
|
-
this.emitter.on('event', listener);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private ensureStageCounters(stage: LifecycleStage, tenantId?: string) {
|
|
88
|
-
if (!tenantId) return;
|
|
89
|
-
const previous = this.currentStageByTenant.get(tenantId);
|
|
90
|
-
if (previous === stage) return;
|
|
91
|
-
|
|
92
|
-
if (previous !== undefined) {
|
|
93
|
-
this.stageUpDownCounter.add(-1, {
|
|
94
|
-
stage: getStageLabel(previous),
|
|
95
|
-
tenantId,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });
|
|
99
|
-
this.currentStageByTenant.set(tenantId, stage);
|
|
100
|
-
this.emitter.emit('event', {
|
|
101
|
-
type: 'stage.changed',
|
|
102
|
-
payload: { tenantId, previousStage: previous, nextStage: stage },
|
|
103
|
-
} satisfies LifecyclePipelineEvent);
|
|
104
|
-
}
|
|
105
|
-
}
|
package/src/tracing/index.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
trace,
|
|
3
|
-
context,
|
|
4
|
-
SpanStatusCode,
|
|
5
|
-
type Span,
|
|
6
|
-
type Tracer,
|
|
7
|
-
} from '@opentelemetry/api';
|
|
8
|
-
|
|
9
|
-
const DEFAULT_TRACER_NAME = '@lssm/lib.observability';
|
|
10
|
-
|
|
11
|
-
export function getTracer(name: string = DEFAULT_TRACER_NAME): Tracer {
|
|
12
|
-
return trace.getTracer(name);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function traceAsync<T>(
|
|
16
|
-
name: string,
|
|
17
|
-
fn: (span: Span) => Promise<T>,
|
|
18
|
-
tracerName?: string
|
|
19
|
-
): Promise<T> {
|
|
20
|
-
const tracer = getTracer(tracerName);
|
|
21
|
-
return tracer.startActiveSpan(name, async (span) => {
|
|
22
|
-
try {
|
|
23
|
-
const result = await fn(span);
|
|
24
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
25
|
-
return result;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
span.recordException(error as Error);
|
|
28
|
-
span.setStatus({
|
|
29
|
-
code: SpanStatusCode.ERROR,
|
|
30
|
-
message: error instanceof Error ? error.message : String(error),
|
|
31
|
-
});
|
|
32
|
-
throw error;
|
|
33
|
-
} finally {
|
|
34
|
-
span.end();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function traceSync<T>(
|
|
40
|
-
name: string,
|
|
41
|
-
fn: (span: Span) => T,
|
|
42
|
-
tracerName?: string
|
|
43
|
-
): T {
|
|
44
|
-
const tracer = getTracer(tracerName);
|
|
45
|
-
return tracer.startActiveSpan(name, (span) => {
|
|
46
|
-
try {
|
|
47
|
-
const result = fn(span);
|
|
48
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
49
|
-
return result;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
span.recordException(error as Error);
|
|
52
|
-
span.setStatus({
|
|
53
|
-
code: SpanStatusCode.ERROR,
|
|
54
|
-
message: error instanceof Error ? error.message : String(error),
|
|
55
|
-
});
|
|
56
|
-
throw error;
|
|
57
|
-
} finally {
|
|
58
|
-
span.end();
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import type { Span } from '@opentelemetry/api';
|
|
2
|
-
import { traceAsync } from './index';
|
|
3
|
-
import { standardMetrics } from '../metrics';
|
|
4
|
-
import type { TelemetrySample } from '../intent/aggregator';
|
|
5
|
-
|
|
6
|
-
export interface TracingMiddlewareOptions {
|
|
7
|
-
resolveOperation?: (input: {
|
|
8
|
-
req: Request;
|
|
9
|
-
res?: Response;
|
|
10
|
-
}) => { name: string; version: number } | undefined;
|
|
11
|
-
onSample?: (sample: TelemetrySample) => void;
|
|
12
|
-
tenantResolver?: (req: Request) => string | undefined;
|
|
13
|
-
actorResolver?: (req: Request) => string | undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createTracingMiddleware(
|
|
17
|
-
options: TracingMiddlewareOptions = {}
|
|
18
|
-
) {
|
|
19
|
-
return async (req: Request, next: () => Promise<Response>) => {
|
|
20
|
-
const method = req.method;
|
|
21
|
-
const url = new URL(req.url);
|
|
22
|
-
const path = url.pathname;
|
|
23
|
-
|
|
24
|
-
standardMetrics.httpRequests.add(1, { method, path });
|
|
25
|
-
const startTime = performance.now();
|
|
26
|
-
|
|
27
|
-
return traceAsync(`HTTP ${method} ${path}`, async (span) => {
|
|
28
|
-
span.setAttribute('http.method', method);
|
|
29
|
-
span.setAttribute('http.url', req.url);
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const response = await next();
|
|
33
|
-
span.setAttribute('http.status_code', response.status);
|
|
34
|
-
|
|
35
|
-
const duration = (performance.now() - startTime) / 1000;
|
|
36
|
-
standardMetrics.httpDuration.record(duration, {
|
|
37
|
-
method,
|
|
38
|
-
path,
|
|
39
|
-
status: response.status.toString(),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
emitTelemetrySample({
|
|
43
|
-
req,
|
|
44
|
-
res: response,
|
|
45
|
-
span,
|
|
46
|
-
success: true,
|
|
47
|
-
durationMs: duration * 1000,
|
|
48
|
-
options,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return response;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
standardMetrics.operationErrors.add(1, { method, path });
|
|
54
|
-
emitTelemetrySample({
|
|
55
|
-
req,
|
|
56
|
-
span,
|
|
57
|
-
success: false,
|
|
58
|
-
durationMs: performance.now() - startTime,
|
|
59
|
-
error,
|
|
60
|
-
options,
|
|
61
|
-
});
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface EmitTelemetryArgs {
|
|
69
|
-
req: Request;
|
|
70
|
-
res?: Response;
|
|
71
|
-
span: Span;
|
|
72
|
-
success: boolean;
|
|
73
|
-
durationMs: number;
|
|
74
|
-
error?: unknown;
|
|
75
|
-
options: TracingMiddlewareOptions;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function emitTelemetrySample({
|
|
79
|
-
req,
|
|
80
|
-
res,
|
|
81
|
-
span,
|
|
82
|
-
success,
|
|
83
|
-
durationMs,
|
|
84
|
-
error,
|
|
85
|
-
options,
|
|
86
|
-
}: EmitTelemetryArgs) {
|
|
87
|
-
if (!options.onSample || !options.resolveOperation) return;
|
|
88
|
-
const operation = options.resolveOperation({ req, res });
|
|
89
|
-
if (!operation) return;
|
|
90
|
-
const sample: TelemetrySample = {
|
|
91
|
-
operation,
|
|
92
|
-
durationMs,
|
|
93
|
-
success,
|
|
94
|
-
timestamp: new Date(),
|
|
95
|
-
errorCode:
|
|
96
|
-
!success && error instanceof Error
|
|
97
|
-
? error.name
|
|
98
|
-
: success
|
|
99
|
-
? undefined
|
|
100
|
-
: 'unknown',
|
|
101
|
-
tenantId: options.tenantResolver?.(req),
|
|
102
|
-
actorId: options.actorResolver?.(req),
|
|
103
|
-
traceId: span.spanContext().traceId,
|
|
104
|
-
metadata: {
|
|
105
|
-
method: req.method,
|
|
106
|
-
path: new URL(req.url).pathname,
|
|
107
|
-
status: res?.status,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
options.onSample(sample);
|
|
111
|
-
}
|