@kb-labs/shared 1.1.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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createCorrelatedLogger,
|
|
4
|
+
createServiceLogBindings,
|
|
5
|
+
resolveObservabilityInstanceId,
|
|
6
|
+
} from '../log-correlation.js';
|
|
7
|
+
|
|
8
|
+
describe('createServiceLogBindings', () => {
|
|
9
|
+
it('builds canonical correlation fields for request-scoped logs', () => {
|
|
10
|
+
const bindings = createServiceLogBindings({
|
|
11
|
+
serviceId: 'rest',
|
|
12
|
+
logsSource: 'rest',
|
|
13
|
+
layer: 'rest',
|
|
14
|
+
service: 'request',
|
|
15
|
+
requestId: 'req-123',
|
|
16
|
+
traceId: 'trace-123',
|
|
17
|
+
method: 'get',
|
|
18
|
+
url: '/api/v1/plugins/abc123/routes/550e8400-e29b-41d4-a716-446655440000',
|
|
19
|
+
operation: 'http.request',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(bindings.serviceId).toBe('rest');
|
|
23
|
+
expect(bindings.logsSource).toBe('rest');
|
|
24
|
+
expect(bindings.requestId).toBe('req-123');
|
|
25
|
+
expect(bindings.reqId).toBe('req-123');
|
|
26
|
+
expect(bindings.traceId).toBe('trace-123');
|
|
27
|
+
expect(bindings.method).toBe('GET');
|
|
28
|
+
expect(bindings.route).toBe('GET /api/v1/plugins/:id/routes/:id');
|
|
29
|
+
expect(bindings.operation).toBe('http.request');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('uses explicit route when provided and resolves instanceId by default', () => {
|
|
33
|
+
const bindings = createServiceLogBindings({
|
|
34
|
+
serviceId: 'workflow',
|
|
35
|
+
route: 'POST /api/v1/jobs/:jobId',
|
|
36
|
+
bindings: {
|
|
37
|
+
jobId: 'job-1',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(bindings.instanceId).toBe(resolveObservabilityInstanceId());
|
|
42
|
+
expect(bindings.route).toBe('POST /api/v1/jobs/:jobId');
|
|
43
|
+
expect(bindings.jobId).toBe('job-1');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('merges canonical correlation fields into every log call', () => {
|
|
47
|
+
const calls: Array<Record<string, unknown> | undefined> = [];
|
|
48
|
+
const logger = createCorrelatedLogger({
|
|
49
|
+
info(message: string, meta?: Record<string, unknown>) {
|
|
50
|
+
expect(message).toBe('hello');
|
|
51
|
+
calls.push(meta);
|
|
52
|
+
},
|
|
53
|
+
warn() {},
|
|
54
|
+
error() {},
|
|
55
|
+
fatal() {},
|
|
56
|
+
debug() {},
|
|
57
|
+
trace() {},
|
|
58
|
+
child() {
|
|
59
|
+
throw new Error('not needed');
|
|
60
|
+
},
|
|
61
|
+
}, {
|
|
62
|
+
serviceId: 'workflow',
|
|
63
|
+
logsSource: 'workflow',
|
|
64
|
+
requestId: 'run-1',
|
|
65
|
+
traceId: 'trace-1',
|
|
66
|
+
operation: 'workflow.job',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
logger.info('hello', { jobId: 'job-1' });
|
|
70
|
+
|
|
71
|
+
expect(calls[0]).toMatchObject({
|
|
72
|
+
serviceId: 'workflow',
|
|
73
|
+
logsSource: 'workflow',
|
|
74
|
+
requestId: 'run-1',
|
|
75
|
+
reqId: 'run-1',
|
|
76
|
+
traceId: 'trace-1',
|
|
77
|
+
operation: 'workflow.job',
|
|
78
|
+
jobId: 'job-1',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { OperationMetricsTracker } from '../operation-metrics-tracker.js';
|
|
3
|
+
|
|
4
|
+
describe('OperationMetricsTracker', () => {
|
|
5
|
+
it('tracks operations and exposes top operations', () => {
|
|
6
|
+
const tracker = new OperationMetricsTracker();
|
|
7
|
+
|
|
8
|
+
tracker.recordOperation('registry.init', 12, 'ok');
|
|
9
|
+
tracker.recordOperation('plugin.routes.mount', 50, 'ok');
|
|
10
|
+
tracker.recordOperation('plugin.routes.mount', 10, 'error');
|
|
11
|
+
|
|
12
|
+
expect(tracker.getTopOperations(5)).toEqual([
|
|
13
|
+
expect.objectContaining({
|
|
14
|
+
operation: 'plugin.routes.mount',
|
|
15
|
+
count: 2,
|
|
16
|
+
errorCount: 1,
|
|
17
|
+
}),
|
|
18
|
+
expect.objectContaining({
|
|
19
|
+
operation: 'registry.init',
|
|
20
|
+
count: 1,
|
|
21
|
+
errorCount: 0,
|
|
22
|
+
}),
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders canonical metric lines grouped by status', () => {
|
|
27
|
+
const tracker = new OperationMetricsTracker();
|
|
28
|
+
|
|
29
|
+
tracker.recordOperation('plugin.routes.mount', 40, 'ok');
|
|
30
|
+
tracker.recordOperation('plugin.routes.mount', 15, 'error');
|
|
31
|
+
|
|
32
|
+
const lines = tracker.getMetricLines();
|
|
33
|
+
|
|
34
|
+
expect(lines).toContain('service_operation_total{operation="plugin.routes.mount",status="ok"} 1');
|
|
35
|
+
expect(lines).toContain('service_operation_total{operation="plugin.routes.mount",status="error"} 1');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('observes async work and records failures', async () => {
|
|
39
|
+
const tracker = new OperationMetricsTracker();
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
tracker.observeOperation('marketplace.sync', async () => {
|
|
43
|
+
throw new Error('boom');
|
|
44
|
+
}),
|
|
45
|
+
).rejects.toThrow('boom');
|
|
46
|
+
|
|
47
|
+
expect(tracker.getTopOperations(1)).toEqual([
|
|
48
|
+
expect.objectContaining({
|
|
49
|
+
operation: 'marketplace.sync',
|
|
50
|
+
count: 1,
|
|
51
|
+
errorCount: 1,
|
|
52
|
+
}),
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
3
|
+
import type { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from 'fastify';
|
|
4
|
+
import {
|
|
5
|
+
OBSERVABILITY_CONTRACT_VERSION,
|
|
6
|
+
OBSERVABILITY_SCHEMA,
|
|
7
|
+
CANONICAL_OBSERVABILITY_METRICS,
|
|
8
|
+
type ObservabilityCapability,
|
|
9
|
+
type ObservabilityCheck,
|
|
10
|
+
type ServiceDependencyDescriptor,
|
|
11
|
+
type ServiceHealthStatus,
|
|
12
|
+
type ServiceObservabilityDescribe,
|
|
13
|
+
type ServiceObservabilityHealth,
|
|
14
|
+
type ServiceObservabilityState,
|
|
15
|
+
type ServiceOperationSample,
|
|
16
|
+
} from '@kb-labs/core-contracts';
|
|
17
|
+
import {
|
|
18
|
+
createServiceObservabilityDescribe,
|
|
19
|
+
createServiceObservabilityHealth,
|
|
20
|
+
} from './service-observability.js';
|
|
21
|
+
import { OperationMetricsTracker, type OperationStatus } from './operation-metrics-tracker.js';
|
|
22
|
+
|
|
23
|
+
declare module 'fastify' {
|
|
24
|
+
interface FastifyRequest {
|
|
25
|
+
kbObservabilityStart?: number;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type RouteStats = {
|
|
30
|
+
count: number;
|
|
31
|
+
totalDurationMs: number;
|
|
32
|
+
maxDurationMs: number;
|
|
33
|
+
errorCount: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type HookableFastifyServer = {
|
|
37
|
+
addHook: any;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface HttpObservabilityCollectorOptions {
|
|
41
|
+
serviceId: string;
|
|
42
|
+
serviceType: string;
|
|
43
|
+
version: string;
|
|
44
|
+
metricsEndpoint?: string;
|
|
45
|
+
healthEndpoint?: string;
|
|
46
|
+
logsSource?: string;
|
|
47
|
+
environment?: string;
|
|
48
|
+
dependencies?: ServiceDependencyDescriptor[];
|
|
49
|
+
capabilities?: ObservabilityCapability[];
|
|
50
|
+
startedAtMs?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function normalizeObservabilityRoute(route: string | undefined): string {
|
|
54
|
+
if (!route) {
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return route
|
|
59
|
+
.split('?')[0]!
|
|
60
|
+
.replace(/\/[0-9a-fA-F-]{6,}/g, '/:id');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function metricLine(name: string, value: number, labels?: Record<string, string>): string {
|
|
64
|
+
if (!labels || Object.keys(labels).length === 0) {
|
|
65
|
+
return `${name} ${value}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const pairs = Object.entries(labels).map(([key, labelValue]) => `${key}="${labelValue.replace(/"/g, '\\"')}"`);
|
|
69
|
+
return `${name}{${pairs.join(',')}} ${value}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function deriveState(status: ServiceHealthStatus): ServiceObservabilityState {
|
|
73
|
+
if (status === 'healthy') {
|
|
74
|
+
return 'active';
|
|
75
|
+
}
|
|
76
|
+
if (status === 'degraded') {
|
|
77
|
+
return 'partial_observability';
|
|
78
|
+
}
|
|
79
|
+
return 'insufficient_data';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class HttpObservabilityCollector {
|
|
83
|
+
private readonly instanceId = `${hostname()}:${process.pid}`;
|
|
84
|
+
private readonly startedAtMs: number;
|
|
85
|
+
private readonly metricsEndpoint: string;
|
|
86
|
+
private readonly healthEndpoint: string;
|
|
87
|
+
private readonly logsSource: string;
|
|
88
|
+
private readonly environment: string;
|
|
89
|
+
private readonly dependencies: ServiceDependencyDescriptor[];
|
|
90
|
+
private readonly capabilities: ObservabilityCapability[];
|
|
91
|
+
private readonly eventLoop = monitorEventLoopDelay({ resolution: 20 });
|
|
92
|
+
private readonly routeStats = new Map<string, RouteStats>();
|
|
93
|
+
private readonly operationMetrics = new OperationMetricsTracker();
|
|
94
|
+
private lastCpuUsage = process.cpuUsage();
|
|
95
|
+
private lastCpuTime = Date.now();
|
|
96
|
+
private intervalId: NodeJS.Timeout | null = null;
|
|
97
|
+
private activeOperations = 0;
|
|
98
|
+
private requestsTotal = 0;
|
|
99
|
+
private errorsTotal = 0;
|
|
100
|
+
private runtimeSnapshotCollectedAt = new Date().toISOString();
|
|
101
|
+
private lastSnapshot = {
|
|
102
|
+
cpuPercent: 0,
|
|
103
|
+
rssBytes: process.memoryUsage().rss,
|
|
104
|
+
heapUsedBytes: process.memoryUsage().heapUsed,
|
|
105
|
+
eventLoopLagMs: 0,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
constructor(private readonly options: HttpObservabilityCollectorOptions) {
|
|
109
|
+
this.startedAtMs = options.startedAtMs ?? Date.now();
|
|
110
|
+
this.metricsEndpoint = options.metricsEndpoint ?? '/metrics';
|
|
111
|
+
this.healthEndpoint = options.healthEndpoint ?? '/observability/health';
|
|
112
|
+
this.logsSource = options.logsSource ?? options.serviceId;
|
|
113
|
+
this.environment = options.environment ?? process.env.NODE_ENV ?? 'development';
|
|
114
|
+
this.dependencies = options.dependencies ?? [];
|
|
115
|
+
this.capabilities = options.capabilities ?? ['httpMetrics', 'eventLoopMetrics', 'operationMetrics', 'logCorrelation'];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
register(server: HookableFastifyServer): void {
|
|
119
|
+
this.eventLoop.enable();
|
|
120
|
+
this.intervalId = setInterval(() => this.captureRuntimeSnapshot(), 10_000);
|
|
121
|
+
this.captureRuntimeSnapshot();
|
|
122
|
+
|
|
123
|
+
server.addHook('onRequest', (request: FastifyRequest, _reply: FastifyReply, done: HookHandlerDoneFunction) => {
|
|
124
|
+
request.kbObservabilityStart = performance.now();
|
|
125
|
+
this.activeOperations += 1;
|
|
126
|
+
done();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
server.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => {
|
|
130
|
+
const started = request.kbObservabilityStart ?? performance.now();
|
|
131
|
+
const durationMs = Math.max(performance.now() - started, 0);
|
|
132
|
+
const route = `${request.method.toUpperCase()} ${normalizeObservabilityRoute(request.routeOptions?.url ?? request.url)}`;
|
|
133
|
+
const stats = this.routeStats.get(route) ?? {
|
|
134
|
+
count: 0,
|
|
135
|
+
totalDurationMs: 0,
|
|
136
|
+
maxDurationMs: 0,
|
|
137
|
+
errorCount: 0,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
stats.count += 1;
|
|
141
|
+
stats.totalDurationMs += durationMs;
|
|
142
|
+
stats.maxDurationMs = Math.max(stats.maxDurationMs, durationMs);
|
|
143
|
+
if (reply.statusCode >= 400) {
|
|
144
|
+
stats.errorCount += 1;
|
|
145
|
+
this.errorsTotal += 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.routeStats.set(route, stats);
|
|
149
|
+
this.requestsTotal += 1;
|
|
150
|
+
this.activeOperations = Math.max(0, this.activeOperations - 1);
|
|
151
|
+
done();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
server.addHook('onClose', (_instance: unknown, done: HookHandlerDoneFunction) => {
|
|
155
|
+
if (this.intervalId) {
|
|
156
|
+
clearInterval(this.intervalId);
|
|
157
|
+
this.intervalId = null;
|
|
158
|
+
}
|
|
159
|
+
this.eventLoop.disable();
|
|
160
|
+
done();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
buildDescribe(): ServiceObservabilityDescribe {
|
|
165
|
+
return createServiceObservabilityDescribe({
|
|
166
|
+
schema: OBSERVABILITY_SCHEMA,
|
|
167
|
+
contractVersion: OBSERVABILITY_CONTRACT_VERSION,
|
|
168
|
+
serviceId: this.options.serviceId,
|
|
169
|
+
instanceId: this.instanceId,
|
|
170
|
+
serviceType: this.options.serviceType,
|
|
171
|
+
version: this.options.version,
|
|
172
|
+
environment: this.environment,
|
|
173
|
+
startedAt: new Date(this.startedAtMs).toISOString(),
|
|
174
|
+
dependencies: this.dependencies,
|
|
175
|
+
metricsEndpoint: this.metricsEndpoint,
|
|
176
|
+
healthEndpoint: this.healthEndpoint,
|
|
177
|
+
logsSource: this.logsSource,
|
|
178
|
+
capabilities: this.capabilities,
|
|
179
|
+
metricFamilies: [...CANONICAL_OBSERVABILITY_METRICS],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
buildHealth(input: {
|
|
184
|
+
status: ServiceHealthStatus;
|
|
185
|
+
checks: ObservabilityCheck[];
|
|
186
|
+
observedAt?: string;
|
|
187
|
+
state?: ServiceObservabilityState;
|
|
188
|
+
topOperations?: ServiceOperationSample[];
|
|
189
|
+
meta?: Record<string, unknown>;
|
|
190
|
+
}): ServiceObservabilityHealth {
|
|
191
|
+
const topOperations = input.topOperations
|
|
192
|
+
? mergeTopOperations(input.topOperations, this.operationMetrics.getTopOperations())
|
|
193
|
+
: mergeTopOperations(this.getTopOperations(), this.operationMetrics.getTopOperations());
|
|
194
|
+
|
|
195
|
+
return createServiceObservabilityHealth({
|
|
196
|
+
schema: OBSERVABILITY_SCHEMA,
|
|
197
|
+
contractVersion: OBSERVABILITY_CONTRACT_VERSION,
|
|
198
|
+
serviceId: this.options.serviceId,
|
|
199
|
+
instanceId: this.instanceId,
|
|
200
|
+
observedAt: input.observedAt ?? new Date().toISOString(),
|
|
201
|
+
status: input.status,
|
|
202
|
+
uptimeSec: Math.floor((Date.now() - this.startedAtMs) / 1000),
|
|
203
|
+
metricsEndpoint: this.metricsEndpoint,
|
|
204
|
+
logsSource: this.logsSource,
|
|
205
|
+
capabilities: this.capabilities,
|
|
206
|
+
checks: input.checks,
|
|
207
|
+
snapshot: {
|
|
208
|
+
cpuPercent: this.lastSnapshot.cpuPercent,
|
|
209
|
+
rssBytes: this.lastSnapshot.rssBytes,
|
|
210
|
+
heapUsedBytes: this.lastSnapshot.heapUsedBytes,
|
|
211
|
+
eventLoopLagMs: this.lastSnapshot.eventLoopLagMs,
|
|
212
|
+
activeOperations: this.activeOperations,
|
|
213
|
+
},
|
|
214
|
+
topOperations,
|
|
215
|
+
state: input.state ?? deriveState(input.status),
|
|
216
|
+
meta: {
|
|
217
|
+
requestsTotal: this.requestsTotal,
|
|
218
|
+
errorsTotal: this.errorsTotal,
|
|
219
|
+
runtimeMetricsCollectedAt: this.runtimeSnapshotCollectedAt,
|
|
220
|
+
...(input.meta ?? {}),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getTopOperations(limit = 5): ServiceOperationSample[] {
|
|
226
|
+
return Array.from(this.routeStats.entries())
|
|
227
|
+
.sort((a, b) => b[1].count - a[1].count || b[1].maxDurationMs - a[1].maxDurationMs)
|
|
228
|
+
.slice(0, limit)
|
|
229
|
+
.map(([operation, stats]) => ({
|
|
230
|
+
operation: `http.${operation}`,
|
|
231
|
+
count: stats.count,
|
|
232
|
+
avgDurationMs: stats.count > 0 ? stats.totalDurationMs / stats.count : 0,
|
|
233
|
+
maxDurationMs: stats.maxDurationMs,
|
|
234
|
+
errorCount: stats.errorCount,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
recordOperation(operation: string, durationMs = 0, status: OperationStatus = 'ok', count = 1): void {
|
|
239
|
+
this.operationMetrics.recordOperation(operation, durationMs, status, count);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
observeOperation<T>(operation: string, work: () => T | Promise<T>): Promise<T> {
|
|
243
|
+
return this.operationMetrics.observeOperation(operation, work);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
renderPrometheusMetrics(healthStatus: ServiceHealthStatus, extraLines: string[] = []): string {
|
|
247
|
+
this.captureRuntimeSnapshot();
|
|
248
|
+
|
|
249
|
+
const lines = [
|
|
250
|
+
'# HELP process_cpu_percent Current process CPU usage percentage',
|
|
251
|
+
'# TYPE process_cpu_percent gauge',
|
|
252
|
+
metricLine('process_cpu_percent', this.lastSnapshot.cpuPercent),
|
|
253
|
+
'# HELP process_rss_bytes Current process resident set size in bytes',
|
|
254
|
+
'# TYPE process_rss_bytes gauge',
|
|
255
|
+
metricLine('process_rss_bytes', this.lastSnapshot.rssBytes),
|
|
256
|
+
'# HELP process_heap_used_bytes Current process heap used in bytes',
|
|
257
|
+
'# TYPE process_heap_used_bytes gauge',
|
|
258
|
+
metricLine('process_heap_used_bytes', this.lastSnapshot.heapUsedBytes),
|
|
259
|
+
'# HELP process_event_loop_lag_ms Current event loop lag in milliseconds',
|
|
260
|
+
'# TYPE process_event_loop_lag_ms gauge',
|
|
261
|
+
metricLine('process_event_loop_lag_ms', this.lastSnapshot.eventLoopLagMs),
|
|
262
|
+
'# HELP service_health_status Service health status (2=healthy, 1=degraded, 0=unhealthy)',
|
|
263
|
+
'# TYPE service_health_status gauge',
|
|
264
|
+
metricLine('service_health_status', healthStatus === 'healthy' ? 2 : healthStatus === 'degraded' ? 1 : 0),
|
|
265
|
+
'# HELP service_restarts_total Service restart counter within current process lifetime',
|
|
266
|
+
'# TYPE service_restarts_total gauge',
|
|
267
|
+
metricLine('service_restarts_total', 0),
|
|
268
|
+
'# HELP service_active_operations Current number of active operations',
|
|
269
|
+
'# TYPE service_active_operations gauge',
|
|
270
|
+
metricLine('service_active_operations', this.activeOperations),
|
|
271
|
+
'# HELP http_requests_total Total number of HTTP requests',
|
|
272
|
+
'# TYPE http_requests_total counter',
|
|
273
|
+
'# HELP http_errors_total Total number of HTTP errors (4xx, 5xx)',
|
|
274
|
+
'# TYPE http_errors_total counter',
|
|
275
|
+
'# HELP http_request_duration_ms Total duration of HTTP requests grouped by route',
|
|
276
|
+
'# TYPE http_request_duration_ms summary',
|
|
277
|
+
'# HELP service_operation_total Total number of service operations',
|
|
278
|
+
'# TYPE service_operation_total counter',
|
|
279
|
+
'# HELP service_operation_duration_ms Total duration of service operations grouped by route',
|
|
280
|
+
'# TYPE service_operation_duration_ms summary',
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
for (const [route, stats] of this.routeStats.entries()) {
|
|
284
|
+
const status = stats.errorCount > 0 ? 'error' : 'ok';
|
|
285
|
+
lines.push(metricLine('http_requests_total', stats.count, { route }));
|
|
286
|
+
lines.push(metricLine('http_errors_total', stats.errorCount, { route }));
|
|
287
|
+
lines.push(metricLine('http_request_duration_ms', Number(stats.totalDurationMs.toFixed(2)), { route }));
|
|
288
|
+
lines.push(metricLine('service_operation_total', stats.count, { operation: `http.${route}`, status }));
|
|
289
|
+
lines.push(metricLine('service_operation_duration_ms', Number(stats.totalDurationMs.toFixed(2)), { operation: `http.${route}`, status }));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push(...this.operationMetrics.getMetricLines(), ...extraLines);
|
|
293
|
+
return `${lines.join('\n')}\n`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private captureRuntimeSnapshot(): void {
|
|
297
|
+
const currentUsage = process.cpuUsage(this.lastCpuUsage);
|
|
298
|
+
const currentTime = Date.now();
|
|
299
|
+
const deltaTime = Math.max(currentTime - this.lastCpuTime, 1);
|
|
300
|
+
|
|
301
|
+
this.lastCpuUsage = process.cpuUsage();
|
|
302
|
+
this.lastCpuTime = currentTime;
|
|
303
|
+
|
|
304
|
+
const cpuTimeMs = (currentUsage.user + currentUsage.system) / 1000;
|
|
305
|
+
const memory = process.memoryUsage();
|
|
306
|
+
const eventLoopLagMs = this.eventLoop.exceeds === 0 && !Number.isFinite(this.eventLoop.mean / 1_000_000)
|
|
307
|
+
? 0
|
|
308
|
+
: Number((this.eventLoop.mean / 1_000_000).toFixed(2));
|
|
309
|
+
|
|
310
|
+
this.lastSnapshot = {
|
|
311
|
+
cpuPercent: Number(Math.min((cpuTimeMs / deltaTime) * 100, 100).toFixed(2)),
|
|
312
|
+
rssBytes: memory.rss,
|
|
313
|
+
heapUsedBytes: memory.heapUsed,
|
|
314
|
+
eventLoopLagMs: Number.isFinite(eventLoopLagMs) ? eventLoopLagMs : 0,
|
|
315
|
+
};
|
|
316
|
+
this.runtimeSnapshotCollectedAt = new Date(currentTime).toISOString();
|
|
317
|
+
this.eventLoop.reset();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function mergeTopOperations(
|
|
322
|
+
baseOperations: ServiceOperationSample[],
|
|
323
|
+
domainOperations: ServiceOperationSample[],
|
|
324
|
+
limit = 5,
|
|
325
|
+
): ServiceOperationSample[] {
|
|
326
|
+
const merged = new Map<string, ServiceOperationSample>();
|
|
327
|
+
|
|
328
|
+
for (const item of [...baseOperations, ...domainOperations]) {
|
|
329
|
+
const existing = merged.get(item.operation);
|
|
330
|
+
if (!existing) {
|
|
331
|
+
merged.set(item.operation, { ...item });
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const count = (existing.count ?? 0) + (item.count ?? 0);
|
|
336
|
+
const totalDurationMs =
|
|
337
|
+
(existing.avgDurationMs ?? 0) * (existing.count ?? 0) +
|
|
338
|
+
(item.avgDurationMs ?? 0) * (item.count ?? 0);
|
|
339
|
+
|
|
340
|
+
merged.set(item.operation, {
|
|
341
|
+
operation: item.operation,
|
|
342
|
+
count,
|
|
343
|
+
avgDurationMs: count > 0 ? totalDurationMs / count : 0,
|
|
344
|
+
maxDurationMs: Math.max(existing.maxDurationMs ?? 0, item.maxDurationMs ?? 0),
|
|
345
|
+
errorCount: (existing.errorCount ?? 0) + (item.errorCount ?? 0),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const ranked = Array.from(merged.values())
|
|
350
|
+
.sort((a, b) => (b.count ?? 0) - (a.count ?? 0) || (b.maxDurationMs ?? 0) - (a.maxDurationMs ?? 0))
|
|
351
|
+
const sliced = ranked.slice(0, limit);
|
|
352
|
+
|
|
353
|
+
if (domainOperations.length === 0 || sliced.some((item) => !item.operation.startsWith('http.'))) {
|
|
354
|
+
return sliced;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const firstDomainOperation = ranked.find((item) => !item.operation.startsWith('http.'));
|
|
358
|
+
if (!firstDomainOperation) {
|
|
359
|
+
return sliced;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return [...sliced.slice(0, Math.max(0, limit - 1)), firstDomainOperation];
|
|
363
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { registerOpenAPI, type OpenAPIOptions } from './register-openapi.js';
|
|
2
|
+
export { resolveSchemaRef, type SchemaRef, type ZodSchemaRef, type JsonSchemaRef } from './resolve-schema-ref.js';
|
|
3
|
+
export { ErrorResponseSchema, OkResponseSchema, type ErrorResponse } from './schemas.js';
|
|
4
|
+
export {
|
|
5
|
+
createServiceReadyResponse,
|
|
6
|
+
createServiceObservabilityDescribe,
|
|
7
|
+
createServiceObservabilityHealth,
|
|
8
|
+
type ServiceReadyResponse,
|
|
9
|
+
type VersionedObservabilityShape,
|
|
10
|
+
} from './service-observability.js';
|
|
11
|
+
export {
|
|
12
|
+
HttpObservabilityCollector,
|
|
13
|
+
metricLine,
|
|
14
|
+
normalizeObservabilityRoute,
|
|
15
|
+
type HttpObservabilityCollectorOptions,
|
|
16
|
+
} from './http-observability-collector.js';
|
|
17
|
+
export {
|
|
18
|
+
createCorrelatedLogger,
|
|
19
|
+
createServiceLogBindings,
|
|
20
|
+
resolveObservabilityInstanceId,
|
|
21
|
+
type ServiceLogBindingInput,
|
|
22
|
+
} from './log-correlation.js';
|
|
23
|
+
export {
|
|
24
|
+
OperationMetricsTracker,
|
|
25
|
+
type OperationObserver,
|
|
26
|
+
type OperationStatus,
|
|
27
|
+
} from './operation-metrics-tracker.js';
|
|
28
|
+
|
|
29
|
+
// Re-export fastify-type-provider-zod utilities so services don't need
|
|
30
|
+
// a direct dependency on fastify-type-provider-zod for the common cases.
|
|
31
|
+
export {
|
|
32
|
+
serializerCompiler,
|
|
33
|
+
validatorCompiler,
|
|
34
|
+
jsonSchemaTransform,
|
|
35
|
+
type ZodTypeProvider,
|
|
36
|
+
} from 'fastify-type-provider-zod';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
import type { ServiceLogCorrelationContext } from '@kb-labs/core-contracts';
|
|
3
|
+
import { normalizeObservabilityRoute } from './http-observability-collector.js';
|
|
4
|
+
|
|
5
|
+
type CorrelationLogger = {
|
|
6
|
+
trace(message: string, meta?: Record<string, unknown>): void;
|
|
7
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
8
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
9
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
10
|
+
error(message: string, error?: Error, meta?: Record<string, unknown>): void;
|
|
11
|
+
fatal(message: string, error?: Error, meta?: Record<string, unknown>): void;
|
|
12
|
+
child(bindings: Record<string, unknown>): CorrelationLogger;
|
|
13
|
+
getLogBuffer?: () => unknown;
|
|
14
|
+
onLog?: (callback: (record: unknown) => void) => () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface ServiceLogBindingInput extends Omit<ServiceLogCorrelationContext, 'instanceId' | 'logsSource'> {
|
|
18
|
+
instanceId?: string;
|
|
19
|
+
logsSource?: string;
|
|
20
|
+
layer?: string;
|
|
21
|
+
service?: string;
|
|
22
|
+
bindings?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveObservabilityInstanceId(): string {
|
|
26
|
+
return `${hostname()}:${process.pid}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createServiceLogBindings(input: ServiceLogBindingInput): Record<string, unknown> {
|
|
30
|
+
const route = input.route
|
|
31
|
+
? normalizeObservabilityRoute(input.route)
|
|
32
|
+
: input.method && input.url
|
|
33
|
+
? `${input.method.toUpperCase()} ${normalizeObservabilityRoute(input.url)}`
|
|
34
|
+
: undefined;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
serviceId: input.serviceId,
|
|
38
|
+
instanceId: input.instanceId ?? resolveObservabilityInstanceId(),
|
|
39
|
+
logsSource: input.logsSource ?? input.serviceId,
|
|
40
|
+
...(input.layer ? { layer: input.layer } : {}),
|
|
41
|
+
...(input.service ? { service: input.service } : {}),
|
|
42
|
+
...(input.requestId ? { requestId: input.requestId, reqId: input.requestId } : {}),
|
|
43
|
+
...(input.traceId ? { traceId: input.traceId } : {}),
|
|
44
|
+
...(input.operation ? { operation: input.operation } : {}),
|
|
45
|
+
...(route ? { route } : {}),
|
|
46
|
+
...(input.method ? { method: input.method.toUpperCase() } : {}),
|
|
47
|
+
...(input.url ? { url: input.url } : {}),
|
|
48
|
+
...(input.bindings ?? {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createCorrelatedLogger(
|
|
53
|
+
baseLogger: CorrelationLogger,
|
|
54
|
+
input: ServiceLogBindingInput
|
|
55
|
+
): CorrelationLogger {
|
|
56
|
+
const baseMeta = createServiceLogBindings(input);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
trace(message: string, meta?: Record<string, unknown>) {
|
|
60
|
+
baseLogger.trace(message, { ...baseMeta, ...(meta ?? {}) });
|
|
61
|
+
},
|
|
62
|
+
debug(message: string, meta?: Record<string, unknown>) {
|
|
63
|
+
baseLogger.debug(message, { ...baseMeta, ...(meta ?? {}) });
|
|
64
|
+
},
|
|
65
|
+
info(message: string, meta?: Record<string, unknown>) {
|
|
66
|
+
baseLogger.info(message, { ...baseMeta, ...(meta ?? {}) });
|
|
67
|
+
},
|
|
68
|
+
warn(message: string, meta?: Record<string, unknown>) {
|
|
69
|
+
baseLogger.warn(message, { ...baseMeta, ...(meta ?? {}) });
|
|
70
|
+
},
|
|
71
|
+
error(message: string, error?: Error, meta?: Record<string, unknown>) {
|
|
72
|
+
baseLogger.error(message, error, { ...baseMeta, ...(meta ?? {}) });
|
|
73
|
+
},
|
|
74
|
+
fatal(message: string, error?: Error, meta?: Record<string, unknown>) {
|
|
75
|
+
baseLogger.fatal(message, error, { ...baseMeta, ...(meta ?? {}) });
|
|
76
|
+
},
|
|
77
|
+
child(bindings: Record<string, unknown>) {
|
|
78
|
+
return createCorrelatedLogger(baseLogger, {
|
|
79
|
+
...input,
|
|
80
|
+
bindings: {
|
|
81
|
+
...(input.bindings ?? {}),
|
|
82
|
+
...bindings,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
...(baseLogger.getLogBuffer ? { getLogBuffer: baseLogger.getLogBuffer.bind(baseLogger) } : {}),
|
|
87
|
+
...(baseLogger.onLog ? { onLog: baseLogger.onLog.bind(baseLogger) } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|