@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.
Files changed (177) hide show
  1. package/README.md +11 -9
  2. package/dist/BrowserEvents.d.ts +89 -12
  3. package/dist/BrowserEvents.js +67 -13
  4. package/dist/Correlation.d.ts +51 -0
  5. package/dist/Correlation.js +95 -0
  6. package/dist/Metrics.d.ts +19 -3
  7. package/dist/Metrics.js +16 -0
  8. package/dist/MetricsRuntime.d.ts +45 -3
  9. package/dist/MetricsRuntime.js +478 -120
  10. package/dist/PolicyOtlpLogger.d.ts +18 -0
  11. package/dist/PolicyOtlpLogger.js +142 -0
  12. package/dist/RedactionPolicy.d.ts +2 -9
  13. package/dist/RedactionPolicy.js +2 -246
  14. package/dist/ResourceIdentity.d.ts +47 -0
  15. package/dist/ResourceIdentity.js +82 -0
  16. package/dist/Telemetry.d.ts +18 -3
  17. package/dist/Telemetry.js +43 -25
  18. package/dist/TelemetryConfig.d.ts +11 -4
  19. package/dist/TelemetryConfig.js +38 -10
  20. package/dist/audit/AuditCommitDocument.d.ts +41 -0
  21. package/dist/audit/AuditCommitDocument.js +58 -0
  22. package/dist/audit/AuditDigest.d.ts +11 -0
  23. package/dist/audit/AuditDigest.js +25 -0
  24. package/dist/audit/AuditOutbox.d.ts +75 -0
  25. package/dist/audit/AuditOutbox.js +104 -0
  26. package/dist/audit/AuditPublisher.d.ts +49 -0
  27. package/dist/audit/AuditPublisher.js +60 -0
  28. package/dist/audit/AuditRecord.d.ts +128 -0
  29. package/dist/audit/AuditRecord.js +172 -0
  30. package/dist/audit/CommittedAuditRecordInternal.d.ts +11 -0
  31. package/dist/audit/CommittedAuditRecordInternal.js +13 -0
  32. package/dist/audit/InvalidAuditRecord.d.ts +12 -0
  33. package/dist/audit/InvalidAuditRecord.js +16 -0
  34. package/dist/audit/index.d.ts +7 -0
  35. package/dist/audit/index.js +7 -0
  36. package/dist/browser/BrowserClient.d.ts +82 -3
  37. package/dist/browser/BrowserClient.js +322 -28
  38. package/dist/browser/BrowserEventLimits.d.ts +8 -0
  39. package/dist/browser/BrowserEventLimits.js +8 -0
  40. package/dist/browser/BrowserMetricRecorder.d.ts +17 -0
  41. package/dist/browser/BrowserMetricRecorder.js +35 -0
  42. package/dist/browser/BrowserTelemetry.d.ts +40 -0
  43. package/dist/browser/BrowserTelemetry.js +105 -0
  44. package/dist/browser/ClientPolicy.d.ts +3 -0
  45. package/dist/browser/ClientPolicy.js +52 -0
  46. package/dist/browser/client.d.ts +3 -2
  47. package/dist/browser/client.js +2 -1
  48. package/dist/browser/index.d.ts +4 -37
  49. package/dist/browser/index.js +3 -95
  50. package/dist/contract/ContractCompatibility.d.ts +31 -0
  51. package/dist/contract/ContractCompatibility.js +274 -0
  52. package/dist/contract/ContractIndex.d.ts +54 -0
  53. package/dist/contract/ContractIndex.js +178 -0
  54. package/dist/contract/ContractSurface.d.ts +111 -0
  55. package/dist/contract/ContractSurface.js +116 -0
  56. package/dist/contract/EventName.d.ts +6 -0
  57. package/dist/contract/EventName.js +55 -0
  58. package/dist/contract/EventProducer.d.ts +123 -0
  59. package/dist/contract/EventProducer.js +209 -0
  60. package/dist/contract/MetricContractError.d.ts +19 -0
  61. package/dist/contract/MetricContractError.js +19 -0
  62. package/dist/contract/MetricDefinition.d.ts +61 -0
  63. package/dist/contract/MetricDefinition.js +91 -0
  64. package/dist/contract/MetricProducer.d.ts +42 -0
  65. package/dist/contract/MetricProducer.js +123 -0
  66. package/dist/contract/OrganizationEvents.d.ts +165 -0
  67. package/dist/contract/OrganizationEvents.js +97 -0
  68. package/dist/contract/TelemetryContract.d.ts +95 -0
  69. package/dist/contract/TelemetryContract.js +451 -0
  70. package/dist/contract/TelemetryContractError.d.ts +44 -0
  71. package/dist/contract/TelemetryContractError.js +64 -0
  72. package/dist/contract/TelemetryEvent.d.ts +59 -0
  73. package/dist/contract/TelemetryEvent.js +54 -0
  74. package/dist/contract/index.d.ts +11 -0
  75. package/dist/contract/index.js +11 -0
  76. package/dist/effect/WideEvent.d.ts +3 -0
  77. package/dist/effect/WideEvent.js +20 -0
  78. package/dist/effect/WideEventSink.d.ts +3 -0
  79. package/dist/effect/WideEventSink.js +79 -0
  80. package/dist/effect/index.d.ts +2 -0
  81. package/dist/effect/index.js +2 -0
  82. package/dist/index.d.ts +17 -2
  83. package/dist/index.js +11 -2
  84. package/dist/metrics/InstrumentGrammar.d.ts +5 -0
  85. package/dist/metrics/InstrumentGrammar.js +12 -0
  86. package/dist/metrics/MetricScalar.d.ts +3 -0
  87. package/dist/metrics/MetricScalar.js +5 -0
  88. package/dist/node/AuditDigest.d.ts +3 -0
  89. package/dist/node/AuditDigest.js +6 -0
  90. package/dist/node/BrowserEventIngest.d.ts +7 -2
  91. package/dist/node/BrowserEventIngest.js +111 -19
  92. package/dist/node/LocalCollector.d.ts +20 -0
  93. package/dist/node/LocalCollector.js +188 -0
  94. package/dist/node/Observability.d.ts +49 -0
  95. package/dist/node/Observability.js +190 -0
  96. package/dist/node/Runtime.d.ts +4 -1
  97. package/dist/node/index.d.ts +4 -0
  98. package/dist/node/index.js +3 -0
  99. package/dist/policy/BrowserFieldPolicy.d.ts +7 -0
  100. package/dist/policy/BrowserFieldPolicy.js +298 -0
  101. package/dist/policy/DataPolicy.d.ts +19 -0
  102. package/dist/policy/DataPolicy.js +254 -0
  103. package/dist/policy/DataPolicyError.d.ts +22 -0
  104. package/dist/policy/DataPolicyError.js +25 -0
  105. package/dist/policy/Deduplication.d.ts +13 -0
  106. package/dist/policy/Deduplication.js +44 -0
  107. package/dist/policy/DefectEnvelope.d.ts +25 -0
  108. package/dist/policy/DefectEnvelope.js +52 -0
  109. package/dist/policy/MetricLabelPolicy.d.ts +5 -0
  110. package/dist/policy/MetricLabelPolicy.js +65 -0
  111. package/dist/policy/PolicyTransform.d.ts +16 -0
  112. package/dist/policy/PolicyTransform.js +136 -0
  113. package/dist/policy/PolicyVocabulary.d.ts +10 -0
  114. package/dist/policy/PolicyVocabulary.js +84 -0
  115. package/dist/policy/ResourceAttributePolicy.d.ts +9 -0
  116. package/dist/policy/ResourceAttributePolicy.js +33 -0
  117. package/dist/policy/entrypoint.d.ts +2 -0
  118. package/dist/policy/entrypoint.js +2 -0
  119. package/dist/policy/index.d.ts +17 -0
  120. package/dist/policy/index.js +9 -0
  121. package/dist/profile/DataPolicy.d.ts +2 -0
  122. package/dist/profile/DataPolicy.js +1 -0
  123. package/dist/profile/EnvironmentPolicy.d.ts +22 -0
  124. package/dist/profile/EnvironmentPolicy.js +41 -0
  125. package/dist/profile/LifecycleRegistry.d.ts +24 -0
  126. package/dist/profile/LifecycleRegistry.js +240 -0
  127. package/dist/profile/ObservabilityAdapter.d.ts +115 -0
  128. package/dist/profile/ObservabilityAdapter.js +50 -0
  129. package/dist/profile/ObservabilityConfig.d.ts +68 -0
  130. package/dist/profile/ObservabilityConfig.js +125 -0
  131. package/dist/profile/ObservabilityConfigError.d.ts +23 -0
  132. package/dist/profile/ObservabilityConfigError.js +39 -0
  133. package/dist/profile/ObservabilityProfile.d.ts +49 -0
  134. package/dist/profile/ObservabilityProfile.js +121 -0
  135. package/dist/profile/ReactWebProfile.d.ts +8 -0
  136. package/dist/profile/ReactWebProfile.js +7 -0
  137. package/dist/profile/index.d.ts +5 -0
  138. package/dist/profile/index.js +5 -0
  139. package/dist/testing/conformance/ConformanceAssertions.d.ts +5 -0
  140. package/dist/testing/conformance/ConformanceAssertions.js +31 -0
  141. package/dist/testing/conformance/ConformanceFailure.d.ts +35 -0
  142. package/dist/testing/conformance/ConformanceFailure.js +56 -0
  143. package/dist/testing/conformance/ConformanceModel.d.ts +97 -0
  144. package/dist/testing/conformance/ConformanceModel.js +8 -0
  145. package/dist/testing/conformance/ConformanceRegistry.d.ts +13 -0
  146. package/dist/testing/conformance/ConformanceRegistry.js +190 -0
  147. package/dist/testing/conformance/ConformanceRunner.d.ts +5 -0
  148. package/dist/testing/conformance/ConformanceRunner.js +198 -0
  149. package/dist/testing/conformance/TelemetryEvidence.d.ts +91 -0
  150. package/dist/testing/conformance/TelemetryEvidence.js +370 -0
  151. package/dist/testing/conformance/index.d.ts +10 -0
  152. package/dist/testing/conformance/index.js +6 -0
  153. package/dist/testing/contract.d.ts +81 -0
  154. package/dist/testing/contract.js +93 -0
  155. package/dist/testing/deployedCanary.d.ts +1 -0
  156. package/dist/testing/deployedCanary.js +1 -0
  157. package/dist/testing/index.d.ts +40 -3
  158. package/dist/testing/index.js +122 -5
  159. package/dist/trace/HttpServerOtlpTracer.d.ts +42 -0
  160. package/dist/trace/HttpServerOtlpTracer.js +301 -0
  161. package/package.json +16 -29
  162. package/dist/WideEvent.d.ts +0 -5
  163. package/dist/WideEvent.js +0 -6
  164. package/dist/nestjs/BrowserEventsController.d.ts +0 -21
  165. package/dist/nestjs/BrowserEventsController.js +0 -52
  166. package/dist/nestjs/HttpRoutePolicy.d.ts +0 -23
  167. package/dist/nestjs/HttpRoutePolicy.js +0 -179
  168. package/dist/nestjs/HttpServerOtlpTracer.d.ts +0 -15
  169. package/dist/nestjs/HttpServerOtlpTracer.js +0 -154
  170. package/dist/nestjs/RequestWideEventTraceCorrelation.d.ts +0 -15
  171. package/dist/nestjs/RequestWideEventTraceCorrelation.js +0 -13
  172. package/dist/nestjs/TelemetryInterceptor.d.ts +0 -31
  173. package/dist/nestjs/TelemetryInterceptor.js +0 -268
  174. package/dist/nestjs/TelemetryModule.d.ts +0 -53
  175. package/dist/nestjs/TelemetryModule.js +0 -428
  176. package/dist/nestjs/index.d.ts +0 -5
  177. package/dist/nestjs/index.js +0 -4
@@ -0,0 +1,301 @@
1
+ import { Cause, Context, Duration, Effect, Layer, Option, Predicate, Schema, Tracer } from "effect";
2
+ import { OtlpExporter, OtlpResource, OtlpSerialization } from "effect/unstable/observability";
3
+ import { sanitizeText, transformSignalFields } from "../policy/PolicyTransform.js";
4
+ import { effectDroppedAttributesKey } from "../policy/PolicyVocabulary.js";
5
+ const HttpStatusCode = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 100, maximum: 599 }));
6
+ const decodeHttpStatusCode = Schema.decodeUnknownOption(HttpStatusCode);
7
+ const SpanScalar = Schema.Union([
8
+ Schema.String,
9
+ Schema.Number.check(Schema.isFinite()),
10
+ Schema.Boolean,
11
+ ]);
12
+ const decodeSpanScalar = Schema.decodeUnknownOption(SpanScalar);
13
+ const maxSpanEvents = 128;
14
+ const maxSpanLinks = 128;
15
+ const sanitizeEntries = (policy, fields, unsupported = 0) => {
16
+ const decision = transformSignalFields(policy, "span", fields);
17
+ return {
18
+ attributes: OtlpResource.entriesToAttributes(Object.entries(decision.value)),
19
+ dropped: decision.dropped + unsupported,
20
+ };
21
+ };
22
+ const scalarFields = (entries, canonicalizeEffectKeys = false) => {
23
+ const fields = {};
24
+ let unsupported = 0;
25
+ for (const [key, value] of entries) {
26
+ const decoded = decodeSpanScalar(value);
27
+ if (key === effectDroppedAttributesKey && Option.isSome(decoded)) {
28
+ if (Predicate.isNumber(decoded.value))
29
+ unsupported += decoded.value;
30
+ continue;
31
+ }
32
+ if (Option.isNone(decoded)) {
33
+ unsupported += 1;
34
+ continue;
35
+ }
36
+ const canonicalKey = canonicalizeEffectKeys
37
+ ? key === "effect.fiberId"
38
+ ? "effect.fiber.id"
39
+ : key === "effect.logLevel"
40
+ ? "effect.log.level"
41
+ : key
42
+ : key;
43
+ fields[canonicalKey] = decoded.value;
44
+ }
45
+ return { fields, unsupported };
46
+ };
47
+ const statusCodeUnset = 0;
48
+ const statusCodeOk = 1;
49
+ const statusCodeError = 2;
50
+ const spanKindCode = (kind) => {
51
+ switch (kind) {
52
+ case "internal":
53
+ return 1;
54
+ case "server":
55
+ return 2;
56
+ case "client":
57
+ return 3;
58
+ case "producer":
59
+ return 4;
60
+ case "consumer":
61
+ return 5;
62
+ }
63
+ };
64
+ class ExportingSpan extends Tracer.NativeSpan {
65
+ #exportSpan;
66
+ #ended = false;
67
+ constructor(options, exportSpan) {
68
+ super(options);
69
+ this.#exportSpan = exportSpan;
70
+ }
71
+ end(endTime, exit) {
72
+ if (this.#ended)
73
+ return;
74
+ this.#ended = true;
75
+ super.end(endTime, exit);
76
+ if (this.sampled)
77
+ this.#exportSpan(this);
78
+ }
79
+ }
80
+ const makeEvents = (policy, span) => span.events.map(([name, startTime, attributes]) => {
81
+ const canonicalizeEffectKeys = Object.hasOwn(attributes, "effect.fiberId") && Object.hasOwn(attributes, "effect.logLevel");
82
+ const input = scalarFields(Object.entries(attributes), canonicalizeEffectKeys);
83
+ const decision = sanitizeEntries(policy, input.fields, input.unsupported);
84
+ return {
85
+ name: sanitizeText(policy, name, "span"),
86
+ timeUnixNano: String(startTime),
87
+ attributes: decision.attributes,
88
+ droppedAttributesCount: decision.dropped,
89
+ };
90
+ });
91
+ const makeNonHttpStatus = (policy, span) => {
92
+ if (span.status._tag !== "Ended") {
93
+ return { status: { code: statusCodeUnset }, attributes: [], events: [] };
94
+ }
95
+ if (span.status.exit._tag === "Success") {
96
+ return { status: { code: statusCodeOk }, attributes: [], events: [] };
97
+ }
98
+ if (Cause.hasInterruptsOnly(span.status.exit.cause)) {
99
+ return {
100
+ status: { code: statusCodeOk, message: "Interrupted" },
101
+ attributes: [
102
+ ["span.label", "⚠︎ Interrupted"],
103
+ ["status.interrupted", true],
104
+ ],
105
+ events: [],
106
+ };
107
+ }
108
+ const errors = Cause.prettyErrors(span.status.exit.cause, { includeCauseInStack: true });
109
+ const endTime = span.status.endTime;
110
+ const events = errors.map((error) => {
111
+ const decision = sanitizeEntries(policy, {
112
+ "exception.type": error.name,
113
+ "exception.message": error.message,
114
+ "exception.stacktrace": error.stack ?? "No stack trace available",
115
+ });
116
+ return {
117
+ name: "exception",
118
+ timeUnixNano: String(endTime),
119
+ attributes: decision.attributes,
120
+ droppedAttributesCount: decision.dropped,
121
+ };
122
+ });
123
+ return {
124
+ status: errors.length === 0
125
+ ? { code: statusCodeError }
126
+ : {
127
+ code: statusCodeError,
128
+ message: sanitizeText(policy, errors[0]?.message ?? "Error", "span"),
129
+ },
130
+ attributes: [],
131
+ events,
132
+ };
133
+ };
134
+ const makeHttpStatus = (span) => {
135
+ if (span.attributes.has("error.type"))
136
+ return { code: statusCodeError };
137
+ return decodeHttpStatusCode(span.attributes.get("http.response.status_code")).pipe(Option.match({
138
+ onNone: () => ({ code: statusCodeUnset }),
139
+ onSome: (status) => ({ code: status >= 500 ? statusCodeError : statusCodeUnset }),
140
+ }));
141
+ };
142
+ const makeOtlpSpan = (policy, span) => {
143
+ if (span.status._tag !== "Ended")
144
+ return Option.none();
145
+ const isHttpServer = span.kind === "server" && span.attributes.has("http.request.method");
146
+ const nonHttp = isHttpServer
147
+ ? { status: makeHttpStatus(span), attributes: [], events: [] }
148
+ : makeNonHttpStatus(policy, span);
149
+ const spanInput = scalarFields(span.attributes);
150
+ for (const [key, value] of nonHttp.attributes)
151
+ spanInput.fields[key] = value;
152
+ const attributeDecision = sanitizeEntries(policy, spanInput.fields, spanInput.unsupported);
153
+ const allEvents = [...makeEvents(policy, span), ...nonHttp.events];
154
+ const events = allEvents.slice(0, maxSpanEvents);
155
+ const allLinks = span.links.map((link) => {
156
+ const input = scalarFields(Object.entries(link.attributes));
157
+ const decision = sanitizeEntries(policy, input.fields, input.unsupported);
158
+ return {
159
+ traceId: link.span.traceId,
160
+ spanId: link.span.spanId,
161
+ attributes: decision.attributes,
162
+ droppedAttributesCount: decision.dropped,
163
+ };
164
+ });
165
+ const links = allLinks.slice(0, maxSpanLinks);
166
+ return Option.some({
167
+ traceId: span.traceId,
168
+ spanId: span.spanId,
169
+ parentSpanId: Option.getOrUndefined(Option.map(span.parent, (parent) => parent.spanId)),
170
+ name: sanitizeText(policy, span.name, "span"),
171
+ kind: spanKindCode(span.kind),
172
+ startTimeUnixNano: String(span.status.startTime),
173
+ endTimeUnixNano: String(span.status.endTime),
174
+ attributes: attributeDecision.attributes,
175
+ droppedAttributesCount: attributeDecision.dropped,
176
+ events,
177
+ droppedEventsCount: allEvents.length - events.length,
178
+ status: nonHttp.status,
179
+ links,
180
+ droppedLinksCount: allLinks.length - links.length,
181
+ });
182
+ };
183
+ const makeHttpServerOtlpTracer = Effect.fn("makeHttpServerOtlpTracer")(function* (options) {
184
+ const resource = yield* OtlpResource.fromConfig(options.resource);
185
+ const serialization = yield* OtlpSerialization.OtlpSerialization;
186
+ const exporter = yield* OtlpExporter.make({
187
+ label: "HttpServerOtlpTracer",
188
+ url: options.url,
189
+ headers: undefined,
190
+ exportInterval: Duration.seconds(5),
191
+ maxBatchSize: 1000,
192
+ body: (spans) => [
193
+ serialization.traces({
194
+ resourceSpans: [
195
+ {
196
+ resource,
197
+ scopeSpans: [
198
+ {
199
+ scope: { name: OtlpResource.serviceNameUnsafe(resource) },
200
+ spans,
201
+ },
202
+ ],
203
+ },
204
+ ],
205
+ }),
206
+ Effect.void,
207
+ ],
208
+ shutdownTimeout: options.shutdownTimeout ?? Duration.seconds(3),
209
+ });
210
+ return Tracer.make({
211
+ span: (spanOptions) => new ExportingSpan(spanOptions, (span) => {
212
+ const exported = makeOtlpSpan(options.policy, span);
213
+ if (Option.isSome(exported))
214
+ exporter.push(exported.value);
215
+ }),
216
+ });
217
+ });
218
+ export const layerHttpServerOtlpTracer = (options) => Layer.effect(Tracer.Tracer, makeHttpServerOtlpTracer(options)).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
219
+ export class BrowserSignalExporter extends Context.Reference("@equipe-tech/observability/BrowserSignalExporter", {
220
+ defaultValue: () => ({
221
+ export: () => Effect.void,
222
+ }),
223
+ }) {
224
+ }
225
+ const unixNanos = (millis) => String(BigInt(Math.trunc(millis)) * 1000000n);
226
+ const browserResource = (base, policy, identity) => {
227
+ if (identity === undefined)
228
+ return base;
229
+ const replaced = new Set([
230
+ "service.name",
231
+ "service.version",
232
+ "deployment.environment.name",
233
+ "deployment.environment",
234
+ ]);
235
+ const decision = transformSignalFields(policy, "resource", {
236
+ "service.name": identity.serviceName,
237
+ "service.version": identity.serviceVersion,
238
+ "deployment.environment.name": identity.environment,
239
+ "deployment.environment": identity.environment,
240
+ });
241
+ return {
242
+ attributes: [
243
+ ...base.attributes.filter((attribute) => !replaced.has(attribute.key)),
244
+ ...OtlpResource.entriesToAttributes(Object.entries(decision.value)),
245
+ ],
246
+ droppedAttributesCount: base.droppedAttributesCount + decision.dropped,
247
+ };
248
+ };
249
+ export const layerBrowserSignalExporter = (options) => Layer.effect(BrowserSignalExporter, Effect.gen(function* () {
250
+ const resource = yield* OtlpResource.fromConfig(options.resource);
251
+ const serialization = yield* OtlpSerialization.OtlpSerialization;
252
+ const traces = yield* OtlpExporter.make({
253
+ label: "BrowserSignalTracer",
254
+ url: options.tracesUrl,
255
+ headers: undefined,
256
+ exportInterval: Duration.seconds(5),
257
+ maxBatchSize: 1_000,
258
+ body: (exports) => [
259
+ serialization.traces({
260
+ resourceSpans: exports.map((entry) => ({
261
+ resource: entry.resource,
262
+ scopeSpans: [
263
+ {
264
+ scope: { name: OtlpResource.serviceNameUnsafe(entry.resource) },
265
+ spans: [entry.span],
266
+ },
267
+ ],
268
+ })),
269
+ }),
270
+ Effect.void,
271
+ ],
272
+ shutdownTimeout: options.shutdownTimeout ?? Duration.seconds(3),
273
+ });
274
+ return BrowserSignalExporter.of({
275
+ export: (signals) => Effect.sync(() => {
276
+ const selectedResource = browserResource(resource, options.policy, signals.resource);
277
+ for (const span of signals.spans) {
278
+ const decision = transformSignalFields(options.policy, "span", span.fields);
279
+ traces.push({
280
+ resource: selectedResource,
281
+ span: {
282
+ traceId: span.traceId,
283
+ spanId: span.spanId,
284
+ parentSpanId: span.parentSpanId,
285
+ name: sanitizeText(options.policy, span.name, "span"),
286
+ kind: 1,
287
+ startTimeUnixNano: unixNanos(span.startedAt),
288
+ endTimeUnixNano: unixNanos(span.endedAt),
289
+ attributes: OtlpResource.entriesToAttributes(Object.entries(decision.value)),
290
+ droppedAttributesCount: decision.dropped,
291
+ events: [],
292
+ droppedEventsCount: 0,
293
+ status: { code: 1 },
294
+ links: [],
295
+ droppedLinksCount: 0,
296
+ },
297
+ });
298
+ }
299
+ }),
300
+ });
301
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@equipe-tech/observability",
3
- "version": "0.2.0",
4
- "description": "Contrato OpenTelemetry da Equipe Tech: configuracao, layer OTLP e wide events sobre Effect",
3
+ "version": "0.3.5",
4
+ "description": "Nucleo neutro de observabilidade da Equipe Tech para OpenTelemetry, contratos, politicas e ciclo de vida",
5
5
  "homepage": "https://github.com/equipe-tech/observability#readme",
6
6
  "bugs": {
7
7
  "url": "https://github.com/equipe-tech/observability/issues"
@@ -21,6 +21,14 @@
21
21
  "types": "./dist/index.d.ts",
22
22
  "import": "./dist/index.js"
23
23
  },
24
+ "./effect": {
25
+ "types": "./dist/effect/index.d.ts",
26
+ "import": "./dist/effect/index.js"
27
+ },
28
+ "./policy": {
29
+ "types": "./dist/policy/entrypoint.d.ts",
30
+ "import": "./dist/policy/entrypoint.js"
31
+ },
24
32
  "./metrics": {
25
33
  "types": "./dist/Metrics.d.ts",
26
34
  "import": "./dist/Metrics.js"
@@ -29,10 +37,6 @@
29
37
  "types": "./dist/node/index.d.ts",
30
38
  "import": "./dist/node/index.js"
31
39
  },
32
- "./nestjs": {
33
- "types": "./dist/nestjs/index.d.ts",
34
- "import": "./dist/nestjs/index.js"
35
- },
36
40
  "./browser": {
37
41
  "types": "./dist/browser/index.d.ts",
38
42
  "import": "./dist/browser/index.js"
@@ -41,6 +45,10 @@
41
45
  "types": "./dist/browser/client.d.ts",
42
46
  "import": "./dist/browser/client.js"
43
47
  },
48
+ "./react-web-profile": {
49
+ "types": "./dist/profile/ReactWebProfile.d.ts",
50
+ "import": "./dist/profile/ReactWebProfile.js"
51
+ },
44
52
  "./testing": {
45
53
  "types": "./dist/testing/index.d.ts",
46
54
  "import": "./dist/testing/index.js"
@@ -50,31 +58,10 @@
50
58
  "build": "bun ../../scripts/build-packages.ts",
51
59
  "prepack": "bun run build"
52
60
  },
53
- "dependencies": {
54
- "effect": "4.0.0-rc.111"
55
- },
56
61
  "devDependencies": {
57
- "@nestjs/common": "^11.2.1",
58
- "@nestjs/core": "^11.2.1",
59
- "@nestjs/platform-express": "^11.2.1",
60
- "evlog": "2.27.1",
61
- "reflect-metadata": "^0.2.2",
62
- "rxjs": "^7.8.2"
62
+ "effect": "4.0.0-rc.111"
63
63
  },
64
64
  "peerDependencies": {
65
- "@nestjs/common": "^10.0.0 || ^11.0.0",
66
- "@nestjs/core": "^10.0.0 || ^11.0.0",
67
- "rxjs": "^7.2.0"
68
- },
69
- "peerDependenciesMeta": {
70
- "@nestjs/common": {
71
- "optional": true
72
- },
73
- "@nestjs/core": {
74
- "optional": true
75
- },
76
- "rxjs": {
77
- "optional": true
78
- }
65
+ "effect": "4.0.0-rc.111"
79
66
  }
80
67
  }
@@ -1,5 +0,0 @@
1
- import { Effect } from "effect";
2
- export type WideEventFields = {
3
- readonly [attribute: string]: string | number | boolean;
4
- };
5
- export declare const emit: (name: string, fields: WideEventFields) => Effect.Effect<void>;
package/dist/WideEvent.js DELETED
@@ -1,6 +0,0 @@
1
- import { Effect } from "effect";
2
- export const emit = (name, fields) => Effect.logInfo(name).pipe(Effect.annotateLogs({
3
- ...fields,
4
- "event.name": name,
5
- "event.kind": "wide",
6
- }));
@@ -1,21 +0,0 @@
1
- import { Schema } from "effect";
2
- import type { ManagedRuntime } from "effect";
3
- import { type BrowserEventIngestReceipt } from "../node/BrowserEventIngest.js";
4
- import type { RequestReference } from "./RequestWideEventTraceCorrelation.js";
5
- export declare const defaultBrowserEventsPath = "_telemetry/events";
6
- declare const BrowserEventsRejection_base: Schema.Class<BrowserEventsRejection, Schema.Struct<{
7
- readonly code: Schema.Literal<"OBS_BROWSER_EVENTS_INVALID_BATCH">;
8
- readonly message: Schema.String;
9
- readonly correlationId: Schema.String;
10
- }>, {}>;
11
- export declare class BrowserEventsRejection extends BrowserEventsRejection_base {
12
- }
13
- export type BrowserEventsControllerOptions = {
14
- readonly path?: string;
15
- };
16
- export declare const createBrowserEventsController: <RuntimeError>(runtime: ManagedRuntime.ManagedRuntime<never, RuntimeError>, options?: BrowserEventsControllerOptions) => {
17
- new (): {
18
- events(request: RequestReference): Promise<BrowserEventIngestReceipt>;
19
- };
20
- };
21
- export {};
@@ -1,52 +0,0 @@
1
- import { Controller, HttpCode, HttpException, Post, Req } from "@nestjs/common";
2
- import { Cause, Exit, Option, Schema } from "effect";
3
- import { ingestBrowserEvents, InvalidBrowserEventBatch, } from "../node/BrowserEventIngest.js";
4
- import { requestSpan, withRequestSpan } from "./TelemetryInterceptor.js";
5
- export const defaultBrowserEventsPath = "_telemetry/events";
6
- export class BrowserEventsRejection extends Schema.Class("@equipe-tech/observability/BrowserEventsRejection")({
7
- code: Schema.Literal("OBS_BROWSER_EVENTS_INVALID_BATCH"),
8
- message: Schema.String,
9
- correlationId: Schema.String,
10
- }) {
11
- }
12
- class BrowserEventsWiringDefect extends Schema.TaggedError()("BrowserEventsWiringDefect", {
13
- code: Schema.Literal("OBS_BROWSER_EVENTS_WIRING_FAILED"),
14
- message: Schema.String,
15
- }) {
16
- }
17
- const RequestWithBody = Schema.Struct({ body: Schema.Unknown });
18
- const decodeRequestWithBody = Schema.decodeUnknownOption(RequestWithBody);
19
- const correlationId = (request) => requestSpan(request).pipe(Option.map((span) => span.traceId), Option.getOrElse(() => crypto.randomUUID()));
20
- export const createBrowserEventsController = (runtime, options) => {
21
- class BrowserEventsController {
22
- async events(request) {
23
- const body = decodeRequestWithBody(request).pipe(Option.map((parsed) => parsed.body), Option.getOrUndefined);
24
- const exit = await runtime.runPromiseExit(ingestBrowserEvents(body).pipe(withRequestSpan(request)));
25
- if (Exit.isSuccess(exit)) {
26
- return exit.value;
27
- }
28
- const error = Cause.findErrorOption(exit.cause);
29
- if (Option.isSome(error) && error.value instanceof InvalidBrowserEventBatch) {
30
- throw new HttpException(new BrowserEventsRejection({
31
- code: error.value.code,
32
- message: error.value.message,
33
- correlationId: correlationId(request),
34
- }), 400);
35
- }
36
- throw Cause.squash(exit.cause);
37
- }
38
- }
39
- const prototype = BrowserEventsController.prototype;
40
- const descriptor = Object.getOwnPropertyDescriptor(prototype, "events");
41
- if (descriptor === undefined) {
42
- throw new BrowserEventsWiringDefect({
43
- code: "OBS_BROWSER_EVENTS_WIRING_FAILED",
44
- message: "The events handler is missing on the controller prototype.",
45
- });
46
- }
47
- Controller()(BrowserEventsController);
48
- Post(options?.path ?? defaultBrowserEventsPath)(prototype, "events", descriptor);
49
- HttpCode(202)(prototype, "events", descriptor);
50
- Req()(prototype, "events", 0);
51
- return BrowserEventsController;
52
- };
@@ -1,23 +0,0 @@
1
- import { Option } from "effect";
2
- export type ProxyPolicy = "direct" | "framework";
3
- export type TelemetryRoutePolicyOptions = {
4
- readonly healthRouteTemplates?: ReadonlyArray<string> | undefined;
5
- readonly proxyPolicy?: ProxyPolicy | undefined;
6
- };
7
- export type HttpServerRequest = {
8
- readonly method: string;
9
- readonly methodOriginal: Option.Option<string>;
10
- readonly route: Option.Option<string>;
11
- readonly spanName: string;
12
- readonly urlPath: Option.Option<string>;
13
- readonly urlScheme: Option.Option<string>;
14
- readonly clientAddress: Option.Option<string>;
15
- readonly networkPeerAddress: Option.Option<string>;
16
- readonly networkPeerPort: Option.Option<number>;
17
- readonly serverAddress: Option.Option<string>;
18
- };
19
- export type TelemetryRoutePolicy = {
20
- readonly inspect: (request: WeakKey) => Option.Option<HttpServerRequest>;
21
- };
22
- export declare const telemetryRoutePolicy: (options?: TelemetryRoutePolicyOptions) => TelemetryRoutePolicy;
23
- export declare const inspectHttpServerRequest: (request: WeakKey, options?: TelemetryRoutePolicyOptions) => Option.Option<HttpServerRequest>;
@@ -1,179 +0,0 @@
1
- import { Option, Predicate, Schema } from "effect";
2
- import { isIP } from "node:net";
3
- const maxMethodLength = 32;
4
- const maxRouteLength = 256;
5
- const maxTargetLength = 2048;
6
- const maxAddressLength = 128;
7
- const HttpMethod = Schema.NonEmptyString.check(Schema.isMaxLength(maxMethodLength), Schema.isPattern(/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/));
8
- const RouteTemplate = Schema.NonEmptyString.check(Schema.isMaxLength(maxRouteLength), Schema.isPattern(/^\/[\x21-\x7e]*$/), Schema.makeFilter((route) => !route.includes("//") &&
9
- !route.includes("\\") &&
10
- !route.includes("?") &&
11
- !route.includes("#") &&
12
- !route.includes("@") &&
13
- !route.includes("://"), { expected: "a bounded absolute route template without a URL authority or query" }));
14
- const RequestTarget = Schema.NonEmptyString.check(Schema.isMaxLength(maxTargetLength));
15
- const NetworkAddress = Schema.NonEmptyString.check(Schema.isMaxLength(maxAddressLength), Schema.makeFilter((address) => isIP(address) !== 0, { expected: "an IP address" }));
16
- const ServerAddress = Schema.NonEmptyString.check(Schema.isMaxLength(maxAddressLength), Schema.isPattern(/^[A-Za-z0-9.:[\]_-]+$/));
17
- const NetworkPort = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 65_535 }));
18
- const HttpRequestBoundary = Schema.Struct({ method: HttpMethod });
19
- const ExpressRouteBoundary = Schema.Struct({ route: Schema.Struct({ path: RouteTemplate }) });
20
- const FastifyRouteBoundary = Schema.Struct({
21
- routeOptions: Schema.Struct({ url: RouteTemplate }),
22
- });
23
- const HttpTargetBoundary = Schema.Struct({
24
- originalUrl: RequestTarget.pipe(Schema.optionalKey),
25
- url: RequestTarget.pipe(Schema.optionalKey),
26
- });
27
- const FrameworkProtocol = Schema.Literals(["http", "https"]);
28
- const decodeHttpRequestBoundary = Schema.decodeUnknownOption(HttpRequestBoundary);
29
- const decodeExpressRouteBoundary = Schema.decodeUnknownOption(ExpressRouteBoundary);
30
- const decodeFastifyRouteBoundary = Schema.decodeUnknownOption(FastifyRouteBoundary);
31
- const decodeHttpTargetBoundary = Schema.decodeUnknownOption(HttpTargetBoundary);
32
- const decodeNetworkAddress = Schema.decodeUnknownOption(NetworkAddress);
33
- const decodeServerAddress = Schema.decodeUnknownOption(ServerAddress);
34
- const decodeNetworkPort = Schema.decodeUnknownOption(NetworkPort);
35
- const decodeEncryptedSocket = Schema.decodeUnknownOption(Schema.Boolean);
36
- const decodeFrameworkProtocol = Schema.decodeUnknownOption(FrameworkProtocol);
37
- const decodeRouteTemplates = Schema.decodeUnknownOption(Schema.Array(RouteTemplate));
38
- const knownMethods = new Set([
39
- "CONNECT",
40
- "DELETE",
41
- "GET",
42
- "HEAD",
43
- "OPTIONS",
44
- "PATCH",
45
- "POST",
46
- "PUT",
47
- "TRACE",
48
- ]);
49
- const defaultExcludedRoutes = ["/health", "/_telemetry/events"];
50
- const staticSegmentPattern = /^[A-Za-z0-9._~-]+$/;
51
- const parameterSegmentPattern = /^:[A-Za-z_][A-Za-z0-9_]*$/;
52
- const wildcardSegmentPattern = /^(?:\*[A-Za-z_][A-Za-z0-9_]*|\{\*[A-Za-z_][A-Za-z0-9_]*\})$/;
53
- const normalizeRoute = (route) => route.length > 1 && route.endsWith("/") ? route.slice(0, -1) : route;
54
- const normalizedRoute = (request) => decodeExpressRouteBoundary(request).pipe(Option.map((boundary) => normalizeRoute(boundary.route.path)), Option.orElse(() => decodeFastifyRouteBoundary(request).pipe(Option.map((boundary) => normalizeRoute(boundary.routeOptions.url)))));
55
- const normalizedMethod = (request) => decodeHttpRequestBoundary(request).pipe(Option.match({
56
- onNone: () => ({ method: "_OTHER", original: Option.none() }),
57
- onSome: (boundary) => {
58
- const normalized = boundary.method.toUpperCase();
59
- const method = knownMethods.has(normalized) ? normalized : "_OTHER";
60
- return {
61
- method,
62
- original: boundary.method === method ? Option.none() : Option.some(boundary.method),
63
- };
64
- },
65
- }));
66
- const rawPath = (request) => decodeHttpTargetBoundary(request).pipe(Option.flatMap((boundary) => Option.fromNullishOr(boundary.originalUrl).pipe(Option.orElse(() => Option.fromNullishOr(boundary.url)))), Option.filter((target) => target.startsWith("/") &&
67
- !target.startsWith("//") &&
68
- !target.includes("#") &&
69
- !target.includes("@") &&
70
- !target.includes("://")), Option.map((target) => target.split("?", 1)[0] ?? "/"), Option.map(normalizeRoute));
71
- const scrubPath = (request, route) => rawPath(request).pipe(Option.flatMap((path) => {
72
- if (route === "/") {
73
- return path === "/" ? Option.some("/") : Option.none();
74
- }
75
- const routeSegments = route.split("/").slice(1);
76
- const pathSegments = path.split("/").slice(1);
77
- const scrubbed = [];
78
- for (let index = 0; index < routeSegments.length; index++) {
79
- const routeSegment = routeSegments[index] ?? "";
80
- const pathSegment = pathSegments[index];
81
- if (index === routeSegments.length - 1 &&
82
- wildcardSegmentPattern.test(routeSegment) &&
83
- pathSegment !== undefined) {
84
- scrubbed.push("REDACTED");
85
- return Option.some(`/${scrubbed.join("/")}`);
86
- }
87
- if (pathSegment === undefined) {
88
- return Option.none();
89
- }
90
- if (parameterSegmentPattern.test(routeSegment)) {
91
- scrubbed.push("REDACTED");
92
- }
93
- else if (staticSegmentPattern.test(routeSegment) && routeSegment === pathSegment) {
94
- scrubbed.push(routeSegment);
95
- }
96
- else {
97
- return Option.none();
98
- }
99
- }
100
- if (pathSegments.length !== routeSegments.length) {
101
- return Option.none();
102
- }
103
- return Option.some(scrubbed.length === 0 ? "/" : `/${scrubbed.join("/")}`);
104
- }));
105
- const directNetwork = (request) => {
106
- if (!Predicate.hasProperty(request, "socket")) {
107
- return {
108
- urlScheme: Option.some("http"),
109
- clientAddress: Option.none(),
110
- networkPeerAddress: Option.none(),
111
- networkPeerPort: Option.none(),
112
- serverAddress: Option.none(),
113
- };
114
- }
115
- const socket = request.socket;
116
- const address = Predicate.hasProperty(socket, "remoteAddress")
117
- ? decodeNetworkAddress(socket.remoteAddress)
118
- : Option.none();
119
- const port = Predicate.hasProperty(socket, "remotePort")
120
- ? decodeNetworkPort(socket.remotePort)
121
- : Option.none();
122
- const encrypted = Predicate.hasProperty(socket, "encrypted")
123
- ? decodeEncryptedSocket(socket.encrypted)
124
- : Option.none();
125
- return {
126
- urlScheme: Option.some(Option.getOrElse(encrypted, () => false) ? "https" : "http"),
127
- clientAddress: address,
128
- networkPeerAddress: address,
129
- networkPeerPort: port,
130
- serverAddress: Option.none(),
131
- };
132
- };
133
- const frameworkNetwork = (request) => {
134
- const direct = directNetwork(request);
135
- return {
136
- urlScheme: Predicate.hasProperty(request, "protocol")
137
- ? decodeFrameworkProtocol(request.protocol)
138
- : Option.none(),
139
- clientAddress: Predicate.hasProperty(request, "ip")
140
- ? decodeNetworkAddress(request.ip)
141
- : Option.none(),
142
- networkPeerAddress: direct.networkPeerAddress,
143
- networkPeerPort: direct.networkPeerPort,
144
- serverAddress: Predicate.hasProperty(request, "hostname")
145
- ? decodeServerAddress(request.hostname)
146
- : Option.none(),
147
- };
148
- };
149
- export const telemetryRoutePolicy = (options = {}) => {
150
- const additionalExclusions = decodeRouteTemplates(options.healthRouteTemplates ?? []).pipe(Option.getOrThrowWith(() => new TypeError("Telemetry health route templates must be bounded absolute paths.")));
151
- const exclusions = new Set([...defaultExcludedRoutes, ...additionalExclusions].map(normalizeRoute));
152
- const proxyPolicy = options.proxyPolicy ?? "direct";
153
- if (proxyPolicy !== "direct" && proxyPolicy !== "framework") {
154
- throw new TypeError("Telemetry proxy policy must be direct or framework.");
155
- }
156
- return {
157
- inspect: (request) => {
158
- const route = normalizedRoute(request);
159
- if (Option.isSome(route) && exclusions.has(route.value)) {
160
- return Option.none();
161
- }
162
- const requestMethod = normalizedMethod(request);
163
- const network = proxyPolicy === "framework" ? frameworkNetwork(request) : directNetwork(request);
164
- const spanPrefix = requestMethod.method === "_OTHER" ? "HTTP" : requestMethod.method;
165
- return Option.some({
166
- method: requestMethod.method,
167
- methodOriginal: requestMethod.original,
168
- route,
169
- spanName: Option.match(route, {
170
- onNone: () => spanPrefix,
171
- onSome: (routeTemplate) => `${spanPrefix} ${routeTemplate}`,
172
- }),
173
- urlPath: Option.flatMap(route, (routeTemplate) => scrubPath(request, routeTemplate)),
174
- ...network,
175
- });
176
- },
177
- };
178
- };
179
- export const inspectHttpServerRequest = (request, options = {}) => telemetryRoutePolicy(options).inspect(request);