@equipe-tech/observability 0.2.0 → 0.3.5
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/README.md +11 -9
- package/dist/BrowserEvents.d.ts +89 -12
- package/dist/BrowserEvents.js +67 -13
- package/dist/Correlation.d.ts +51 -0
- package/dist/Correlation.js +95 -0
- package/dist/Metrics.d.ts +19 -3
- package/dist/Metrics.js +16 -0
- package/dist/MetricsRuntime.d.ts +45 -3
- package/dist/MetricsRuntime.js +478 -120
- package/dist/PolicyOtlpLogger.d.ts +18 -0
- package/dist/PolicyOtlpLogger.js +142 -0
- package/dist/RedactionPolicy.d.ts +2 -9
- package/dist/RedactionPolicy.js +2 -246
- package/dist/ResourceIdentity.d.ts +47 -0
- package/dist/ResourceIdentity.js +82 -0
- package/dist/Telemetry.d.ts +18 -3
- package/dist/Telemetry.js +43 -25
- package/dist/TelemetryConfig.d.ts +11 -4
- package/dist/TelemetryConfig.js +38 -10
- package/dist/audit/AuditCommitDocument.d.ts +41 -0
- package/dist/audit/AuditCommitDocument.js +58 -0
- package/dist/audit/AuditDigest.d.ts +11 -0
- package/dist/audit/AuditDigest.js +25 -0
- package/dist/audit/AuditOutbox.d.ts +75 -0
- package/dist/audit/AuditOutbox.js +104 -0
- package/dist/audit/AuditPublisher.d.ts +49 -0
- package/dist/audit/AuditPublisher.js +60 -0
- package/dist/audit/AuditRecord.d.ts +128 -0
- package/dist/audit/AuditRecord.js +172 -0
- package/dist/audit/CommittedAuditRecordInternal.d.ts +11 -0
- package/dist/audit/CommittedAuditRecordInternal.js +13 -0
- package/dist/audit/InvalidAuditRecord.d.ts +12 -0
- package/dist/audit/InvalidAuditRecord.js +16 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/audit/index.js +7 -0
- package/dist/browser/BrowserClient.d.ts +82 -3
- package/dist/browser/BrowserClient.js +322 -28
- package/dist/browser/BrowserEventLimits.d.ts +8 -0
- package/dist/browser/BrowserEventLimits.js +8 -0
- package/dist/browser/BrowserMetricRecorder.d.ts +17 -0
- package/dist/browser/BrowserMetricRecorder.js +35 -0
- package/dist/browser/BrowserTelemetry.d.ts +40 -0
- package/dist/browser/BrowserTelemetry.js +105 -0
- package/dist/browser/ClientPolicy.d.ts +3 -0
- package/dist/browser/ClientPolicy.js +52 -0
- package/dist/browser/client.d.ts +3 -2
- package/dist/browser/client.js +2 -1
- package/dist/browser/index.d.ts +4 -37
- package/dist/browser/index.js +3 -95
- package/dist/contract/ContractCompatibility.d.ts +31 -0
- package/dist/contract/ContractCompatibility.js +274 -0
- package/dist/contract/ContractIndex.d.ts +54 -0
- package/dist/contract/ContractIndex.js +178 -0
- package/dist/contract/ContractSurface.d.ts +111 -0
- package/dist/contract/ContractSurface.js +116 -0
- package/dist/contract/EventName.d.ts +6 -0
- package/dist/contract/EventName.js +55 -0
- package/dist/contract/EventProducer.d.ts +123 -0
- package/dist/contract/EventProducer.js +209 -0
- package/dist/contract/MetricContractError.d.ts +19 -0
- package/dist/contract/MetricContractError.js +19 -0
- package/dist/contract/MetricDefinition.d.ts +61 -0
- package/dist/contract/MetricDefinition.js +91 -0
- package/dist/contract/MetricProducer.d.ts +42 -0
- package/dist/contract/MetricProducer.js +123 -0
- package/dist/contract/OrganizationEvents.d.ts +165 -0
- package/dist/contract/OrganizationEvents.js +97 -0
- package/dist/contract/TelemetryContract.d.ts +95 -0
- package/dist/contract/TelemetryContract.js +451 -0
- package/dist/contract/TelemetryContractError.d.ts +44 -0
- package/dist/contract/TelemetryContractError.js +64 -0
- package/dist/contract/TelemetryEvent.d.ts +59 -0
- package/dist/contract/TelemetryEvent.js +54 -0
- package/dist/contract/index.d.ts +11 -0
- package/dist/contract/index.js +11 -0
- package/dist/effect/WideEvent.d.ts +3 -0
- package/dist/effect/WideEvent.js +20 -0
- package/dist/effect/WideEventSink.d.ts +3 -0
- package/dist/effect/WideEventSink.js +79 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.js +2 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +11 -2
- package/dist/metrics/InstrumentGrammar.d.ts +5 -0
- package/dist/metrics/InstrumentGrammar.js +12 -0
- package/dist/metrics/MetricScalar.d.ts +3 -0
- package/dist/metrics/MetricScalar.js +5 -0
- package/dist/node/AuditDigest.d.ts +3 -0
- package/dist/node/AuditDigest.js +6 -0
- package/dist/node/BrowserEventIngest.d.ts +7 -2
- package/dist/node/BrowserEventIngest.js +111 -19
- package/dist/node/LocalCollector.d.ts +20 -0
- package/dist/node/LocalCollector.js +188 -0
- package/dist/node/Observability.d.ts +49 -0
- package/dist/node/Observability.js +190 -0
- package/dist/node/Runtime.d.ts +4 -1
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.js +3 -0
- package/dist/policy/BrowserFieldPolicy.d.ts +7 -0
- package/dist/policy/BrowserFieldPolicy.js +298 -0
- package/dist/policy/DataPolicy.d.ts +19 -0
- package/dist/policy/DataPolicy.js +254 -0
- package/dist/policy/DataPolicyError.d.ts +22 -0
- package/dist/policy/DataPolicyError.js +25 -0
- package/dist/policy/Deduplication.d.ts +13 -0
- package/dist/policy/Deduplication.js +44 -0
- package/dist/policy/DefectEnvelope.d.ts +25 -0
- package/dist/policy/DefectEnvelope.js +52 -0
- package/dist/policy/MetricLabelPolicy.d.ts +5 -0
- package/dist/policy/MetricLabelPolicy.js +65 -0
- package/dist/policy/PolicyTransform.d.ts +16 -0
- package/dist/policy/PolicyTransform.js +136 -0
- package/dist/policy/PolicyVocabulary.d.ts +10 -0
- package/dist/policy/PolicyVocabulary.js +84 -0
- package/dist/policy/ResourceAttributePolicy.d.ts +9 -0
- package/dist/policy/ResourceAttributePolicy.js +33 -0
- package/dist/policy/entrypoint.d.ts +2 -0
- package/dist/policy/entrypoint.js +2 -0
- package/dist/policy/index.d.ts +17 -0
- package/dist/policy/index.js +9 -0
- package/dist/profile/DataPolicy.d.ts +2 -0
- package/dist/profile/DataPolicy.js +1 -0
- package/dist/profile/EnvironmentPolicy.d.ts +22 -0
- package/dist/profile/EnvironmentPolicy.js +41 -0
- package/dist/profile/LifecycleRegistry.d.ts +24 -0
- package/dist/profile/LifecycleRegistry.js +240 -0
- package/dist/profile/ObservabilityAdapter.d.ts +115 -0
- package/dist/profile/ObservabilityAdapter.js +50 -0
- package/dist/profile/ObservabilityConfig.d.ts +68 -0
- package/dist/profile/ObservabilityConfig.js +125 -0
- package/dist/profile/ObservabilityConfigError.d.ts +23 -0
- package/dist/profile/ObservabilityConfigError.js +39 -0
- package/dist/profile/ObservabilityProfile.d.ts +49 -0
- package/dist/profile/ObservabilityProfile.js +121 -0
- package/dist/profile/ReactWebProfile.d.ts +8 -0
- package/dist/profile/ReactWebProfile.js +7 -0
- package/dist/profile/index.d.ts +5 -0
- package/dist/profile/index.js +5 -0
- package/dist/testing/conformance/ConformanceAssertions.d.ts +5 -0
- package/dist/testing/conformance/ConformanceAssertions.js +31 -0
- package/dist/testing/conformance/ConformanceFailure.d.ts +35 -0
- package/dist/testing/conformance/ConformanceFailure.js +56 -0
- package/dist/testing/conformance/ConformanceModel.d.ts +97 -0
- package/dist/testing/conformance/ConformanceModel.js +8 -0
- package/dist/testing/conformance/ConformanceRegistry.d.ts +13 -0
- package/dist/testing/conformance/ConformanceRegistry.js +190 -0
- package/dist/testing/conformance/ConformanceRunner.d.ts +5 -0
- package/dist/testing/conformance/ConformanceRunner.js +198 -0
- package/dist/testing/conformance/TelemetryEvidence.d.ts +91 -0
- package/dist/testing/conformance/TelemetryEvidence.js +370 -0
- package/dist/testing/conformance/index.d.ts +10 -0
- package/dist/testing/conformance/index.js +6 -0
- package/dist/testing/contract.d.ts +81 -0
- package/dist/testing/contract.js +93 -0
- package/dist/testing/deployedCanary.d.ts +1 -0
- package/dist/testing/deployedCanary.js +1 -0
- package/dist/testing/index.d.ts +40 -3
- package/dist/testing/index.js +122 -5
- package/dist/trace/HttpServerOtlpTracer.d.ts +42 -0
- package/dist/trace/HttpServerOtlpTracer.js +301 -0
- package/package.json +16 -29
- package/dist/WideEvent.d.ts +0 -5
- package/dist/WideEvent.js +0 -6
- package/dist/nestjs/BrowserEventsController.d.ts +0 -21
- package/dist/nestjs/BrowserEventsController.js +0 -52
- package/dist/nestjs/HttpRoutePolicy.d.ts +0 -23
- package/dist/nestjs/HttpRoutePolicy.js +0 -179
- package/dist/nestjs/HttpServerOtlpTracer.d.ts +0 -15
- package/dist/nestjs/HttpServerOtlpTracer.js +0 -154
- package/dist/nestjs/RequestWideEventTraceCorrelation.d.ts +0 -15
- package/dist/nestjs/RequestWideEventTraceCorrelation.js +0 -13
- package/dist/nestjs/TelemetryInterceptor.d.ts +0 -31
- package/dist/nestjs/TelemetryInterceptor.js +0 -268
- package/dist/nestjs/TelemetryModule.d.ts +0 -53
- package/dist/nestjs/TelemetryModule.js +0 -428
- package/dist/nestjs/index.d.ts +0 -5
- package/dist/nestjs/index.js +0 -4
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { assessTelemetryDestination, } from "../testing/conformance/TelemetryEvidence.js";
|
|
7
|
+
import { startOtlpCaptureServer, } from "../testing/index.js";
|
|
8
|
+
export class LocalCollectorError extends Schema.TaggedError()("LocalCollectorError", {
|
|
9
|
+
code: Schema.Literal("OBS_CONFORMANCE_LOCAL_COLLECTOR_FAILED"),
|
|
10
|
+
message: Schema.String,
|
|
11
|
+
cause: Schema.Defect(),
|
|
12
|
+
}) {
|
|
13
|
+
}
|
|
14
|
+
const collectorError = (message, cause) => new LocalCollectorError({
|
|
15
|
+
code: "OBS_CONFORMANCE_LOCAL_COLLECTOR_FAILED",
|
|
16
|
+
message,
|
|
17
|
+
cause,
|
|
18
|
+
});
|
|
19
|
+
const DockerPort = Schema.String.check(Schema.isPattern(/127[.]0[.]0[.]1:[0-9]+/));
|
|
20
|
+
const DockerGateway = Schema.String.check(Schema.isPattern(/^(?:[0-9]{1,3}[.]){3}[0-9]{1,3}$/));
|
|
21
|
+
const decodeDockerPort = Schema.decodeUnknownSync(DockerPort);
|
|
22
|
+
const decodeDockerGateway = Schema.decodeUnknownSync(DockerGateway);
|
|
23
|
+
const collectorTraversalAttribute = "observability.collector.traversal";
|
|
24
|
+
const traversedTelemetry = (telemetry, traversal) => ({
|
|
25
|
+
spans: telemetry.spans.filter((span) => span.resourceAttributes.get(collectorTraversalAttribute) === traversal),
|
|
26
|
+
logs: telemetry.logs.filter((log) => log.resourceAttributes.get(collectorTraversalAttribute) === traversal),
|
|
27
|
+
metrics: telemetry.metrics.filter((metric) => metric.resourceAttributes.get(collectorTraversalAttribute) === traversal),
|
|
28
|
+
});
|
|
29
|
+
const command = (args) => new Promise((resolve, reject) => {
|
|
30
|
+
const process = spawn(args[0] ?? "", args.slice(1));
|
|
31
|
+
let stdout = "";
|
|
32
|
+
let stderr = "";
|
|
33
|
+
process.stdout.on("data", (chunk) => {
|
|
34
|
+
stdout += chunk.toString();
|
|
35
|
+
});
|
|
36
|
+
process.stderr.on("data", (chunk) => {
|
|
37
|
+
stderr += chunk.toString();
|
|
38
|
+
});
|
|
39
|
+
process.on("error", reject);
|
|
40
|
+
process.on("close", (exitCode) => {
|
|
41
|
+
if (exitCode === 0)
|
|
42
|
+
resolve(`${stdout}${stderr}`.trim());
|
|
43
|
+
else {
|
|
44
|
+
reject(collectorError(`${args.join(" ")} failed: ${stderr.trim()}`, {
|
|
45
|
+
exitCode,
|
|
46
|
+
command: args[0] ?? "",
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const waitForCollector = async (endpoint) => {
|
|
52
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
53
|
+
const reachable = await fetch(endpoint, {
|
|
54
|
+
signal: AbortSignal.timeout(1_000),
|
|
55
|
+
}).then(() => true, () => false);
|
|
56
|
+
if (reachable)
|
|
57
|
+
return;
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
59
|
+
}
|
|
60
|
+
throw collectorError(`The isolated Collector did not listen at ${endpoint.origin}.`, endpoint.origin);
|
|
61
|
+
};
|
|
62
|
+
export const startLocalCollectorDestination = async () => {
|
|
63
|
+
const collectorInstance = `observability-conformance-${crypto.randomUUID()}`;
|
|
64
|
+
const collectorTraversal = crypto.randomUUID();
|
|
65
|
+
let destination;
|
|
66
|
+
let directory;
|
|
67
|
+
let running = false;
|
|
68
|
+
try {
|
|
69
|
+
const linuxGateway = process.platform === "linux"
|
|
70
|
+
? decodeDockerGateway(await command([
|
|
71
|
+
"docker",
|
|
72
|
+
"network",
|
|
73
|
+
"inspect",
|
|
74
|
+
"bridge",
|
|
75
|
+
"--format",
|
|
76
|
+
"{{(index .IPAM.Config 0).Gateway}}",
|
|
77
|
+
]))
|
|
78
|
+
: undefined;
|
|
79
|
+
directory = await mkdtemp(join(tmpdir(), "observability-conformance-"));
|
|
80
|
+
destination =
|
|
81
|
+
linuxGateway === undefined
|
|
82
|
+
? await startOtlpCaptureServer()
|
|
83
|
+
: await startOtlpCaptureServer({ host: linuxGateway });
|
|
84
|
+
const destinationHost = linuxGateway ?? "host.docker.internal";
|
|
85
|
+
const configPath = join(directory, "collector.yaml");
|
|
86
|
+
await writeFile(configPath, [
|
|
87
|
+
"receivers:",
|
|
88
|
+
" otlp:",
|
|
89
|
+
" protocols:",
|
|
90
|
+
" http:",
|
|
91
|
+
" endpoint: 0.0.0.0:4318",
|
|
92
|
+
"exporters:",
|
|
93
|
+
" otlphttp/destination:",
|
|
94
|
+
` endpoint: http://${destinationHost}:${destination.endpoint.port}`,
|
|
95
|
+
" compression: none",
|
|
96
|
+
" encoding: json",
|
|
97
|
+
"processors:",
|
|
98
|
+
" resource/local_traversal:",
|
|
99
|
+
" attributes:",
|
|
100
|
+
` - key: ${collectorTraversalAttribute}`,
|
|
101
|
+
` value: ${collectorTraversal}`,
|
|
102
|
+
" action: upsert",
|
|
103
|
+
"service:",
|
|
104
|
+
" pipelines:",
|
|
105
|
+
" traces:",
|
|
106
|
+
" receivers: [otlp]",
|
|
107
|
+
" processors: [resource/local_traversal]",
|
|
108
|
+
" exporters: [otlphttp/destination]",
|
|
109
|
+
" metrics:",
|
|
110
|
+
" receivers: [otlp]",
|
|
111
|
+
" processors: [resource/local_traversal]",
|
|
112
|
+
" exporters: [otlphttp/destination]",
|
|
113
|
+
" logs:",
|
|
114
|
+
" receivers: [otlp]",
|
|
115
|
+
" processors: [resource/local_traversal]",
|
|
116
|
+
" exporters: [otlphttp/destination]",
|
|
117
|
+
"",
|
|
118
|
+
].join("\n"));
|
|
119
|
+
await command([
|
|
120
|
+
"docker",
|
|
121
|
+
"run",
|
|
122
|
+
"--detach",
|
|
123
|
+
"--rm",
|
|
124
|
+
"--name",
|
|
125
|
+
collectorInstance,
|
|
126
|
+
"--add-host",
|
|
127
|
+
"host.docker.internal:host-gateway",
|
|
128
|
+
"--publish",
|
|
129
|
+
"127.0.0.1::4318",
|
|
130
|
+
"--volume",
|
|
131
|
+
`${configPath}:/etc/otelcol/config.yaml:ro`,
|
|
132
|
+
"otel/opentelemetry-collector-contrib:0.159.0",
|
|
133
|
+
"--config=/etc/otelcol/config.yaml",
|
|
134
|
+
]);
|
|
135
|
+
running = true;
|
|
136
|
+
const address = decodeDockerPort(await command(["docker", "port", collectorInstance, "4318/tcp"]));
|
|
137
|
+
const endpoint = new URL(`http://${address}`);
|
|
138
|
+
await waitForCollector(endpoint);
|
|
139
|
+
let stopPromise;
|
|
140
|
+
const acquiredDestination = destination;
|
|
141
|
+
const acquiredDirectory = directory;
|
|
142
|
+
const collectorTelemetry = () => traversedTelemetry(acquiredDestination.telemetry(), collectorTraversal);
|
|
143
|
+
return {
|
|
144
|
+
endpoint,
|
|
145
|
+
telemetry: collectorTelemetry,
|
|
146
|
+
destinationTelemetry: collectorTelemetry,
|
|
147
|
+
destinationEndpoint: acquiredDestination.endpoint,
|
|
148
|
+
collectorInstance,
|
|
149
|
+
awaitDestination: async (runId, eventRunIdAttribute = "run.id") => {
|
|
150
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
151
|
+
if (collectorTelemetry().logs.some((log) => log.attributes.get(eventRunIdAttribute) === runId)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
155
|
+
}
|
|
156
|
+
const logs = await command(["docker", "logs", collectorInstance]);
|
|
157
|
+
throw collectorError(`Collector destination did not observe run ${runId}. Collector logs: ${logs}`, runId);
|
|
158
|
+
},
|
|
159
|
+
destinationReceipt: (runId, binding) => assessTelemetryDestination({
|
|
160
|
+
topology: "local",
|
|
161
|
+
runId,
|
|
162
|
+
identity: binding.identity,
|
|
163
|
+
observationId: collectorInstance,
|
|
164
|
+
readback: collectorTelemetry,
|
|
165
|
+
}),
|
|
166
|
+
stop: () => {
|
|
167
|
+
stopPromise ??= Promise.all([
|
|
168
|
+
running
|
|
169
|
+
? command(["docker", "rm", "--force", collectorInstance]).then(() => undefined)
|
|
170
|
+
: Promise.resolve(),
|
|
171
|
+
acquiredDestination.stop(),
|
|
172
|
+
]).then(() => rm(acquiredDirectory, { recursive: true, force: true }));
|
|
173
|
+
running = false;
|
|
174
|
+
return stopPromise;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (cause) {
|
|
179
|
+
if (running) {
|
|
180
|
+
await command(["docker", "rm", "--force", collectorInstance]).catch(() => undefined);
|
|
181
|
+
}
|
|
182
|
+
if (destination !== undefined)
|
|
183
|
+
await destination.stop();
|
|
184
|
+
if (directory !== undefined)
|
|
185
|
+
await rm(directory, { recursive: true, force: true });
|
|
186
|
+
throw collectorError(`The isolated Collector could not start: ${String(cause)}`, cause);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Context, Effect, Layer, ManagedRuntime } from "effect";
|
|
2
|
+
import { OtlpExporter } from "effect/unstable/observability";
|
|
3
|
+
import { AuditPublisher } from "../audit/AuditPublisher.js";
|
|
4
|
+
import { TelemetryEventSink } from "../contract/EventProducer.js";
|
|
5
|
+
import type { Metrics } from "../Metrics.js";
|
|
6
|
+
import { type EnvBootstrapInput, type NodeObservabilityConfig, type NodeObservabilityConfigEnabled } from "../profile/ObservabilityConfig.js";
|
|
7
|
+
import type { AdapterRegistration, LifecycleReport, TestingAdapterRegistration } from "../profile/ObservabilityAdapter.js";
|
|
8
|
+
import { ObservabilityLifecycleError } from "../profile/LifecycleRegistry.js";
|
|
9
|
+
import type { DuplicateReleaseVariable } from "../profile/ObservabilityConfigError.js";
|
|
10
|
+
import { InvalidObservabilityConfig } from "../profile/ObservabilityConfigError.js";
|
|
11
|
+
export type NodeObservabilityDisabled = {
|
|
12
|
+
readonly enabled: false;
|
|
13
|
+
readonly eventLayer: Layer.Layer<TelemetryEventSink>;
|
|
14
|
+
readonly auditLayer: Layer.Layer<AuditPublisher>;
|
|
15
|
+
readonly metrics: Metrics;
|
|
16
|
+
readonly flush: () => Promise<LifecycleReport>;
|
|
17
|
+
readonly close: () => Promise<LifecycleReport>;
|
|
18
|
+
readonly dispose: () => Promise<LifecycleReport>;
|
|
19
|
+
readonly [Symbol.asyncDispose]: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export type NodeObservabilityEnabled = {
|
|
22
|
+
readonly enabled: true;
|
|
23
|
+
readonly config: NodeObservabilityConfigEnabled;
|
|
24
|
+
readonly runtime: ManagedRuntime.ManagedRuntime<OtlpExporter.Flusher, InvalidObservabilityConfig>;
|
|
25
|
+
readonly eventLayer: Layer.Layer<TelemetryEventSink>;
|
|
26
|
+
readonly auditLayer: Layer.Layer<AuditPublisher>;
|
|
27
|
+
readonly metrics: Metrics;
|
|
28
|
+
readonly flush: () => Promise<LifecycleReport>;
|
|
29
|
+
readonly close: () => Promise<LifecycleReport>;
|
|
30
|
+
readonly dispose: () => Promise<LifecycleReport>;
|
|
31
|
+
readonly [Symbol.asyncDispose]: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export type NodeObservability = NodeObservabilityDisabled | NodeObservabilityEnabled;
|
|
34
|
+
export declare const acquireRuntimeFlusher: (runtime: ManagedRuntime.ManagedRuntime<OtlpExporter.Flusher, InvalidObservabilityConfig>) => Effect.Effect<{
|
|
35
|
+
readonly flush: Effect.Effect<void>;
|
|
36
|
+
readonly register: (run: Effect.Effect<void>) => Effect.Effect<void, never, import("effect/Scope").Scope>;
|
|
37
|
+
}, InvalidObservabilityConfig | ObservabilityLifecycleError, never>;
|
|
38
|
+
export declare const makeNodeObservability: (config: NodeObservabilityConfig, registrations: ReadonlyArray<AdapterRegistration>) => Effect.Effect<NodeObservability, InvalidObservabilityConfig | ObservabilityLifecycleError>;
|
|
39
|
+
export declare const createNodeObservabilityFromConfig: (config: NodeObservabilityConfig, registrations: ReadonlyArray<AdapterRegistration>) => Promise<NodeObservability>;
|
|
40
|
+
export declare const createTestingNodeObservabilityFromConfig: (config: NodeObservabilityConfig, registrations: ReadonlyArray<TestingAdapterRegistration>) => Promise<NodeObservability>;
|
|
41
|
+
export type CreateNodeObservabilityInput = EnvBootstrapInput & {
|
|
42
|
+
readonly adapters: ReadonlyArray<AdapterRegistration>;
|
|
43
|
+
};
|
|
44
|
+
export declare const createNodeObservability: (input: CreateNodeObservabilityInput) => Promise<NodeObservability>;
|
|
45
|
+
declare const NodeObservabilityService_base: Context.ServiceClass<NodeObservabilityService, "@equipe-tech/observability/node/NodeObservability", NodeObservability>;
|
|
46
|
+
export declare class NodeObservabilityService extends NodeObservabilityService_base {
|
|
47
|
+
}
|
|
48
|
+
export declare const layerNodeObservability: (input: CreateNodeObservabilityInput) => Layer.Layer<NodeObservabilityService, InvalidObservabilityConfig | DuplicateReleaseVariable | ObservabilityLifecycleError>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Cause, Context, Duration, Effect, Layer, ManagedRuntime, Option } from "effect";
|
|
2
|
+
import { OtlpExporter } from "effect/unstable/observability";
|
|
3
|
+
import { AuditPublisher, unboundAuditPublisher } from "../audit/AuditPublisher.js";
|
|
4
|
+
import { TelemetryEventSink } from "../contract/EventProducer.js";
|
|
5
|
+
import * as Telemetry from "../Telemetry.js";
|
|
6
|
+
import { createDisabledMetrics, LayerMetricsRuntime, releaseMetricsLease, } from "../MetricsRuntime.js";
|
|
7
|
+
import { nodeObservabilityConfigFromEnv, } from "../profile/ObservabilityConfig.js";
|
|
8
|
+
import { createLifecycleRegistry, ObservabilityLifecycleError, rollbackStartedAdapters, validateAdapterRegistrationKinds, validateAdapterRegistrations, } from "../profile/LifecycleRegistry.js";
|
|
9
|
+
import { profileCapabilityRank } from "../profile/ObservabilityProfile.js";
|
|
10
|
+
const emptyReport = (operation) => ({
|
|
11
|
+
operation,
|
|
12
|
+
outcomes: [],
|
|
13
|
+
durationMillis: 0,
|
|
14
|
+
degraded: false,
|
|
15
|
+
});
|
|
16
|
+
const closedFlush = () => Effect.runPromise(new ObservabilityLifecycleError({
|
|
17
|
+
code: "OBS_OBSERVABILITY_CLOSED",
|
|
18
|
+
message: "Observability is closed. Create a new runtime before flushing again.",
|
|
19
|
+
adapter: Option.none(),
|
|
20
|
+
cause: "flush after close",
|
|
21
|
+
}));
|
|
22
|
+
const noopEventLayer = Layer.succeed(TelemetryEventSink, TelemetryEventSink.of({
|
|
23
|
+
record: () => Effect.void,
|
|
24
|
+
admitBrowserBatch: () => Effect.succeed({ commit: Effect.void }),
|
|
25
|
+
}));
|
|
26
|
+
const noopAuditLayer = () => Layer.succeed(AuditPublisher, unboundAuditPublisher());
|
|
27
|
+
const disabledHandle = () => {
|
|
28
|
+
const report = emptyReport("close");
|
|
29
|
+
const metrics = createDisabledMetrics();
|
|
30
|
+
let closePromise;
|
|
31
|
+
let closed = false;
|
|
32
|
+
const close = () => {
|
|
33
|
+
if (closePromise !== undefined)
|
|
34
|
+
return closePromise;
|
|
35
|
+
closed = true;
|
|
36
|
+
closePromise = metrics.close().then(() => report);
|
|
37
|
+
return closePromise;
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
enabled: false,
|
|
41
|
+
eventLayer: noopEventLayer,
|
|
42
|
+
auditLayer: noopAuditLayer(),
|
|
43
|
+
metrics,
|
|
44
|
+
flush: () => (closed ? closedFlush() : Promise.resolve(emptyReport("flush"))),
|
|
45
|
+
close,
|
|
46
|
+
dispose: close,
|
|
47
|
+
[Symbol.asyncDispose]: async () => {
|
|
48
|
+
await close();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
class LiveNodeObservability {
|
|
53
|
+
config;
|
|
54
|
+
runtime;
|
|
55
|
+
eventLayer;
|
|
56
|
+
auditLayer;
|
|
57
|
+
metrics;
|
|
58
|
+
runLifecycle;
|
|
59
|
+
enabled = true;
|
|
60
|
+
#closePromise;
|
|
61
|
+
#flushPromise;
|
|
62
|
+
#closed = false;
|
|
63
|
+
constructor(config, runtime, eventLayer, auditLayer, metrics, runLifecycle) {
|
|
64
|
+
this.config = config;
|
|
65
|
+
this.runtime = runtime;
|
|
66
|
+
this.eventLayer = eventLayer;
|
|
67
|
+
this.auditLayer = auditLayer;
|
|
68
|
+
this.metrics = metrics;
|
|
69
|
+
this.runLifecycle = runLifecycle;
|
|
70
|
+
}
|
|
71
|
+
flush() {
|
|
72
|
+
if (this.#closed) {
|
|
73
|
+
return closedFlush();
|
|
74
|
+
}
|
|
75
|
+
if (this.#flushPromise !== undefined) {
|
|
76
|
+
return this.#flushPromise;
|
|
77
|
+
}
|
|
78
|
+
const pending = Effect.runPromise(this.runLifecycle("flush"));
|
|
79
|
+
this.#flushPromise = pending;
|
|
80
|
+
const clear = () => {
|
|
81
|
+
if (this.#flushPromise === pending) {
|
|
82
|
+
this.#flushPromise = undefined;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
pending.then(clear, clear);
|
|
86
|
+
return pending;
|
|
87
|
+
}
|
|
88
|
+
close() {
|
|
89
|
+
if (this.#closePromise !== undefined) {
|
|
90
|
+
return this.#closePromise;
|
|
91
|
+
}
|
|
92
|
+
this.#closed = true;
|
|
93
|
+
const close = () => releaseMetricsLease(this.metrics).then(() => Effect.runPromise(this.runLifecycle("close")));
|
|
94
|
+
const pendingFlush = this.#flushPromise;
|
|
95
|
+
this.#closePromise = pendingFlush === undefined ? close() : pendingFlush.then(close, close);
|
|
96
|
+
return this.#closePromise;
|
|
97
|
+
}
|
|
98
|
+
dispose() {
|
|
99
|
+
return this.close();
|
|
100
|
+
}
|
|
101
|
+
async [Symbol.asyncDispose]() {
|
|
102
|
+
await this.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const nodeMetricsFlushTimeoutMilliseconds = 400;
|
|
106
|
+
export const acquireRuntimeFlusher = Effect.fn("acquireRuntimeFlusher")(function* (runtime) {
|
|
107
|
+
const acquisition = yield* Effect.promise(() => runtime.runPromiseExit(OtlpExporter.Flusher));
|
|
108
|
+
if (acquisition._tag === "Failure") {
|
|
109
|
+
yield* runtime.disposeEffect.pipe(Effect.catchCause(() => Effect.void));
|
|
110
|
+
return yield* Option.getOrElse(Cause.findErrorOption(acquisition.cause), () => new ObservabilityLifecycleError({
|
|
111
|
+
code: "OBS_OBSERVABILITY_STARTUP_FAILED",
|
|
112
|
+
message: "The built-in OpenTelemetry runtime failed to start. Verify the endpoint configuration.",
|
|
113
|
+
adapter: Option.none(),
|
|
114
|
+
cause: acquisition.cause,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
return acquisition.value;
|
|
118
|
+
});
|
|
119
|
+
const makeNodeObservabilityWithOptions = Effect.fn("makeNodeObservability")(function* (config, registrations, options) {
|
|
120
|
+
if (!config.enabled) {
|
|
121
|
+
yield* validateAdapterRegistrationKinds(registrations, options);
|
|
122
|
+
return disabledHandle();
|
|
123
|
+
}
|
|
124
|
+
yield* validateAdapterRegistrations(config.profile, config.identity.environment, registrations, options);
|
|
125
|
+
const runtime = ManagedRuntime.make(Telemetry.layer(config.telemetry, {
|
|
126
|
+
shutdownTimeout: Duration.millis(nodeMetricsFlushTimeoutMilliseconds),
|
|
127
|
+
policy: config.evlog.policy,
|
|
128
|
+
contract: config.evlog.contract,
|
|
129
|
+
}));
|
|
130
|
+
const flusher = yield* acquireRuntimeFlusher(runtime);
|
|
131
|
+
const metricsRuntime = yield* Effect.promise(() => runtime.runPromise(Effect.gen(function* () {
|
|
132
|
+
return yield* LayerMetricsRuntime;
|
|
133
|
+
})));
|
|
134
|
+
const metrics = metricsRuntime.acquireMetrics();
|
|
135
|
+
const context = {
|
|
136
|
+
profile: config.profile,
|
|
137
|
+
identity: config.identity,
|
|
138
|
+
telemetryConfig: config.telemetry,
|
|
139
|
+
contract: config.evlog.contract,
|
|
140
|
+
policy: config.evlog.policy,
|
|
141
|
+
sentry: config.sentry,
|
|
142
|
+
runtime,
|
|
143
|
+
};
|
|
144
|
+
const started = [];
|
|
145
|
+
for (const registration of registrations.toSorted((left, right) => {
|
|
146
|
+
const leftStage = config.profile.stages.indexOf(left.adapter.stage);
|
|
147
|
+
const rightStage = config.profile.stages.indexOf(right.adapter.stage);
|
|
148
|
+
return (leftStage - rightStage ||
|
|
149
|
+
profileCapabilityRank(config.profile, left.adapter.stage, left.adapter.capability) -
|
|
150
|
+
profileCapabilityRank(config.profile, right.adapter.stage, right.adapter.capability));
|
|
151
|
+
})) {
|
|
152
|
+
const result = yield* registration.adapter.start(context).pipe(Effect.exit);
|
|
153
|
+
if (result._tag === "Failure") {
|
|
154
|
+
yield* rollbackStartedAdapters(started);
|
|
155
|
+
yield* Effect.promise(() => releaseMetricsLease(metrics));
|
|
156
|
+
yield* runtime.disposeEffect;
|
|
157
|
+
return yield* new ObservabilityLifecycleError({
|
|
158
|
+
code: "OBS_OBSERVABILITY_STARTUP_FAILED",
|
|
159
|
+
message: `Observability adapter "${registration.adapter.name}" failed during startup. Fix its local configuration before retrying.`,
|
|
160
|
+
adapter: Option.some(registration.adapter.name),
|
|
161
|
+
cause: Option.getOrElse(Cause.findErrorOption(result.cause), () => result.cause),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
started.push({ registration, handle: result.value });
|
|
165
|
+
}
|
|
166
|
+
const eventHandle = started.find((entry) => entry.registration.adapter.capability === "events")?.handle;
|
|
167
|
+
const eventLayer = eventHandle?.eventLayer ?? Option.none();
|
|
168
|
+
const auditLayer = Option.getOrElse(eventHandle?.auditLayer ?? Option.none(), noopAuditLayer);
|
|
169
|
+
if (Option.isNone(eventLayer)) {
|
|
170
|
+
yield* rollbackStartedAdapters(started);
|
|
171
|
+
yield* Effect.promise(() => releaseMetricsLease(metrics));
|
|
172
|
+
yield* runtime.disposeEffect;
|
|
173
|
+
return yield* new ObservabilityLifecycleError({
|
|
174
|
+
code: "OBS_OBSERVABILITY_STARTUP_FAILED",
|
|
175
|
+
message: "The events adapter did not provide its TelemetryEventSink layer. Use an official events adapter that owns event delivery.",
|
|
176
|
+
adapter: Option.fromNullishOr(started.find((entry) => entry.registration.adapter.capability === "events")?.registration
|
|
177
|
+
.adapter.name),
|
|
178
|
+
cause: "missing events service layer",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const registry = createLifecycleRegistry(config.profile, started, flusher, runtime.disposeEffect);
|
|
182
|
+
return new LiveNodeObservability(config, runtime, eventLayer.value, auditLayer, metrics, registry.run);
|
|
183
|
+
});
|
|
184
|
+
export const makeNodeObservability = (config, registrations) => makeNodeObservabilityWithOptions(config, registrations, { allowTesting: false });
|
|
185
|
+
export const createNodeObservabilityFromConfig = (config, registrations) => Effect.runPromise(makeNodeObservability(config, registrations));
|
|
186
|
+
export const createTestingNodeObservabilityFromConfig = (config, registrations) => Effect.runPromise(makeNodeObservabilityWithOptions(config, registrations, { allowTesting: true }));
|
|
187
|
+
export const createNodeObservability = (input) => Effect.runPromise(nodeObservabilityConfigFromEnv(input).pipe(Effect.flatMap((config) => makeNodeObservability(config, input.adapters))));
|
|
188
|
+
export class NodeObservabilityService extends Context.Service()("@equipe-tech/observability/node/NodeObservability") {
|
|
189
|
+
}
|
|
190
|
+
export const layerNodeObservability = (input) => Layer.effect(NodeObservabilityService, Effect.acquireRelease(nodeObservabilityConfigFromEnv(input).pipe(Effect.flatMap((config) => makeNodeObservability(config, input.adapters))), (handle) => Effect.promise(() => handle.close()).pipe(Effect.asVoid)));
|
package/dist/node/Runtime.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Effect, Runtime } from "effect";
|
|
2
2
|
import type { Layer } from "effect";
|
|
3
|
+
import type { InvalidResourceIdentity } from "../ResourceIdentity.js";
|
|
4
|
+
import type { InvalidDataPolicy } from "../policy/DataPolicyError.js";
|
|
5
|
+
import type { DuplicateReleaseVariable } from "../profile/ObservabilityConfigError.js";
|
|
3
6
|
import type { EnvironmentVariables, InvalidTelemetryEnvironment } from "../TelemetryConfig.js";
|
|
4
|
-
export declare const layer: (env?: EnvironmentVariables) => Layer.Layer<never, InvalidTelemetryEnvironment>;
|
|
7
|
+
export declare const layer: (env?: EnvironmentVariables) => Layer.Layer<never, InvalidTelemetryEnvironment | InvalidResourceIdentity | DuplicateReleaseVariable | InvalidDataPolicy>;
|
|
5
8
|
export type RunMainOptions = {
|
|
6
9
|
readonly env?: EnvironmentVariables;
|
|
7
10
|
readonly disableErrorReporting?: boolean;
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
export { layerNodeAuditDigest } from "./AuditDigest.js";
|
|
1
2
|
export { ingestBrowserEventBatch, ingestBrowserEvents, InvalidBrowserEventBatch, parseBrowserEventBatch, type BrowserEventIngestReceipt, } from "./BrowserEventIngest.js";
|
|
3
|
+
export { createNodeObservability, createNodeObservabilityFromConfig, layerNodeObservability, makeNodeObservability, NodeObservabilityService, type CreateNodeObservabilityInput, type NodeObservability, type NodeObservabilityDisabled, type NodeObservabilityEnabled, } from "./Observability.js";
|
|
4
|
+
export { ObservabilityLifecycleError } from "../profile/LifecycleRegistry.js";
|
|
5
|
+
export type { AdapterOutcome, AdapterRegistration, LifecycleCleanupResult, LifecycleOutcome, LifecycleOutcomeResult, LifecycleReport, OfficialAdapterRegistration, RuntimeDisposalOutcome, } from "../profile/ObservabilityAdapter.js";
|
|
2
6
|
export { layer, runMain, type RunMainOptions } from "./Runtime.js";
|
package/dist/node/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
+
export { layerNodeAuditDigest } from "./AuditDigest.js";
|
|
1
2
|
export { ingestBrowserEventBatch, ingestBrowserEvents, InvalidBrowserEventBatch, parseBrowserEventBatch, } from "./BrowserEventIngest.js";
|
|
3
|
+
export { createNodeObservability, createNodeObservabilityFromConfig, layerNodeObservability, makeNodeObservability, NodeObservabilityService, } from "./Observability.js";
|
|
4
|
+
export { ObservabilityLifecycleError } from "../profile/LifecycleRegistry.js";
|
|
2
5
|
export { layer, runMain } from "./Runtime.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BrowserEventFields } from "../BrowserEvents.js";
|
|
2
|
+
import type { EventAttributes } from "../contract/TelemetryEvent.js";
|
|
3
|
+
export declare const maxOriginalStringLength = 16384;
|
|
4
|
+
export declare const replaceStructuredAssignments: (value: string) => string;
|
|
5
|
+
export declare const replaceStructuredText: (value: string) => string;
|
|
6
|
+
export declare const sanitizeBrowserFields: (fields: EventAttributes) => BrowserEventFields;
|
|
7
|
+
export declare const sanitizeEventName: (name: string) => string;
|