@equipe-tech/observability 0.2.1 → 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,18 @@
1
+ import { Duration, Effect, Layer, Logger } from "effect";
2
+ import type * as LogLevel from "effect/LogLevel";
3
+ import type { HttpClient } from "effect/unstable/http";
4
+ import { OtlpExporter, OtlpSerialization } from "effect/unstable/observability";
5
+ export declare const logLevelSeverityNumber: (level: LogLevel.LogLevel) => number;
6
+ export type PolicyOtlpLoggerOptions = {
7
+ readonly url: string;
8
+ readonly resource?: {
9
+ readonly serviceName?: string | undefined;
10
+ readonly serviceVersion?: string | undefined;
11
+ readonly attributes?: {
12
+ readonly [key: string]: string;
13
+ } | undefined;
14
+ } | undefined;
15
+ readonly shutdownTimeout?: Duration.Input | undefined;
16
+ };
17
+ export declare const makePolicyOtlpLogger: (options: PolicyOtlpLoggerOptions) => Effect.Effect<Logger.Logger<unknown, void>, never, OtlpExporter.Flusher | HttpClient.HttpClient | OtlpSerialization.OtlpSerialization | import("effect/Scope").Scope>;
18
+ export declare const layerPolicyOtlpLogger: (options: PolicyOtlpLoggerOptions) => Layer.Layer<OtlpExporter.Flusher, never, HttpClient.HttpClient | OtlpSerialization.OtlpSerialization>;
@@ -0,0 +1,142 @@
1
+ import { Array as Arr, Cause, Clock, Duration, Effect, Layer, Logger, Option, Predicate, References, Schema, } from "effect";
2
+ import { OtlpExporter, OtlpResource, OtlpSerialization } from "effect/unstable/observability";
3
+ import { CurrentDataPolicy } from "./policy/DataPolicy.js";
4
+ import { sanitizeText, transformSignalFields } from "./policy/PolicyTransform.js";
5
+ import { effectDroppedAttributesKey } from "./policy/PolicyVocabulary.js";
6
+ const Scalar = Schema.Union([
7
+ Schema.String,
8
+ Schema.Number.check(Schema.isFinite()),
9
+ Schema.Boolean,
10
+ ]);
11
+ const decodeScalar = Schema.decodeUnknownOption(Scalar);
12
+ export const logLevelSeverityNumber = (level) => {
13
+ switch (level) {
14
+ case "Trace":
15
+ return 1;
16
+ case "Debug":
17
+ return 5;
18
+ case "Info":
19
+ return 9;
20
+ case "Warn":
21
+ return 13;
22
+ case "Error":
23
+ return 17;
24
+ case "Fatal":
25
+ return 21;
26
+ case "All":
27
+ case "None":
28
+ return 0;
29
+ }
30
+ };
31
+ export const makePolicyOtlpLogger = Effect.fn("makePolicyOtlpLogger")(function* (options) {
32
+ const serialization = yield* OtlpSerialization.OtlpSerialization;
33
+ const resource = yield* OtlpResource.fromConfig(options.resource);
34
+ const clock = yield* Clock.Clock;
35
+ const existingLoggers = yield* Logger.CurrentLoggers;
36
+ const exporter = yield* OtlpExporter.make({
37
+ label: "PolicyOtlpLogger",
38
+ url: options.url,
39
+ headers: undefined,
40
+ maxBatchSize: 1000,
41
+ exportInterval: Duration.seconds(1),
42
+ body: (records) => [
43
+ serialization.logs({
44
+ resourceLogs: [
45
+ {
46
+ resource,
47
+ scopeLogs: [
48
+ {
49
+ scope: { name: OtlpResource.serviceNameUnsafe(resource) },
50
+ logRecords: records,
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ }),
56
+ Effect.void,
57
+ ],
58
+ shutdownTimeout: options.shutdownTimeout ?? Duration.seconds(3),
59
+ });
60
+ return Logger.make((entry) => {
61
+ const policy = entry.fiber.getRef(CurrentDataPolicy);
62
+ const raw = {};
63
+ let unsupportedDropped = 0;
64
+ for (const [key, value] of Object.entries(entry.fiber.getRef(References.CurrentLogAnnotations))) {
65
+ const decoded = decodeScalar(value);
66
+ if (key === effectDroppedAttributesKey && Option.isSome(decoded)) {
67
+ if (Predicate.isNumber(decoded.value))
68
+ unsupportedDropped += decoded.value;
69
+ continue;
70
+ }
71
+ if (Option.isSome(decoded)) {
72
+ raw[key] = decoded.value;
73
+ }
74
+ else {
75
+ unsupportedDropped += 1;
76
+ }
77
+ }
78
+ const packageGeneratedKeys = new Set();
79
+ raw["effect.fiber.id"] = entry.fiber.id;
80
+ packageGeneratedKeys.add("effect.fiber.id");
81
+ const nowMillis = entry.date.getTime();
82
+ for (const [label, startTime] of entry.fiber.getRef(References.CurrentLogSpans)) {
83
+ const key = `effect.log_span.${label}`;
84
+ raw[key] = `${nowMillis - startTime}ms`;
85
+ packageGeneratedKeys.add(key);
86
+ }
87
+ const sanitizedCause = entry.cause.reasons.length > 0
88
+ ? sanitizeText(policy, Cause.pretty(entry.cause), "log")
89
+ : undefined;
90
+ if (sanitizedCause !== undefined) {
91
+ raw["log.error"] = sanitizedCause;
92
+ packageGeneratedKeys.add("log.error");
93
+ }
94
+ const decision = transformSignalFields(policy, "log", raw);
95
+ const delegatedAnnotations = Object.fromEntries(Object.entries(decision.value).filter(([key]) => !packageGeneratedKeys.has(key)));
96
+ const droppedPackageGenerated = [...packageGeneratedKeys].filter((key) => decision.value[key] === undefined).length;
97
+ const delegatedDropped = decision.dropped - droppedPackageGenerated + unsupportedDropped;
98
+ const now = clock.currentTimeNanosUnsafe().toString();
99
+ const attributes = OtlpResource.entriesToAttributes(Object.entries(decision.value));
100
+ const messages = Arr.ensure(entry.message).map((message) => {
101
+ const decoded = decodeScalar(message);
102
+ if (Option.isSome(decoded)) {
103
+ return Predicate.isString(decoded.value)
104
+ ? sanitizeText(policy, decoded.value, "log")
105
+ : decoded.value;
106
+ }
107
+ return "[REDACTED]";
108
+ });
109
+ const sanitizedFiber = Object.create(entry.fiber);
110
+ Object.defineProperty(sanitizedFiber, "getRef", {
111
+ value: (reference) => reference === References.CurrentLogAnnotations
112
+ ? {
113
+ ...delegatedAnnotations,
114
+ [effectDroppedAttributesKey]: delegatedDropped,
115
+ }
116
+ : entry.fiber.getRef(reference),
117
+ });
118
+ const sanitizedEntry = {
119
+ ...entry,
120
+ message: messages,
121
+ fiber: sanitizedFiber,
122
+ cause: sanitizedCause === undefined ? Cause.empty : Cause.fail(sanitizedCause),
123
+ };
124
+ for (const logger of existingLoggers)
125
+ logger.log(sanitizedEntry);
126
+ const record = {
127
+ severityNumber: logLevelSeverityNumber(entry.logLevel),
128
+ severityText: entry.logLevel,
129
+ timeUnixNano: now,
130
+ observedTimeUnixNano: now,
131
+ attributes,
132
+ body: OtlpResource.unknownToAttributeValue(messages.length === 1 ? messages[0] : messages),
133
+ droppedAttributesCount: decision.dropped + unsupportedDropped,
134
+ };
135
+ if (entry.fiber.currentSpan) {
136
+ record.traceId = entry.fiber.currentSpan.traceId;
137
+ record.spanId = entry.fiber.currentSpan.spanId;
138
+ }
139
+ exporter.push(record);
140
+ });
141
+ });
142
+ export const layerPolicyOtlpLogger = (options) => Logger.layer([makePolicyOtlpLogger(options)], { mergeWithExisting: false }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
@@ -1,9 +1,2 @@
1
- import { type BrowserEventFields } from "./BrowserEvents.js";
2
- import type { WideEventFields } from "./WideEvent.js";
3
- export declare const sensitiveFieldReplacement = "****";
4
- export declare const sensitiveTextReplacement = "[REDACTED]";
5
- export declare const collectorBlockedKeyPattern: string;
6
- export declare const collectorBlockedValuePatterns: string[];
7
- export declare const isSensitiveFieldKey: (key: string) => boolean;
8
- export declare const sanitizeBrowserFields: (fields: WideEventFields) => BrowserEventFields;
9
- export declare const sanitizeBrowserEventName: (name: string) => string;
1
+ export { sanitizeBrowserFields, sanitizeEventName } from "./policy/BrowserFieldPolicy.js";
2
+ export { baseBlockedKeys, collectorBlockedKeyPattern, collectorBlockedValuePatterns, isSensitiveFieldKey, sensitiveFieldReplacement, sensitiveTextReplacement, } from "./policy/PolicyVocabulary.js";
@@ -1,246 +1,2 @@
1
- import { Option, Predicate, Schema } from "effect";
2
- import { maxEventNameLength, maxFieldKeyLength, maxFieldsPerEvent, maxFieldValueLength, } from "./BrowserEvents.js";
3
- export const sensitiveFieldReplacement = "****";
4
- export const sensitiveTextReplacement = "[REDACTED]";
5
- const sensitiveTermSources = [
6
- "authorization",
7
- "proxy[._-]?authorization",
8
- "cookie",
9
- "set[._-]?cookie",
10
- "password",
11
- "passwd",
12
- "secret",
13
- "token",
14
- "api[._-]?key",
15
- "apikey",
16
- "access[._-]?key",
17
- "client[._-]?secret",
18
- "private[._-]?key",
19
- "email",
20
- "phone",
21
- "cpf",
22
- "cnpj",
23
- "document",
24
- ];
25
- export const collectorBlockedKeyPattern = `(?i:${sensitiveTermSources.join("|")})(?:[._-]|[A-Z0-9]|$)`;
26
- export const collectorBlockedValuePatterns = [
27
- "(?i)Bearer[[:space:]]+[A-Za-z0-9._~+/=-]+",
28
- "(?:sk|rk)[_-][A-Za-z0-9_*.-]{3,}",
29
- "eyJ[A-Za-z0-9_-]+[.]eyJ[A-Za-z0-9_-]+[.][A-Za-z0-9_-]+",
30
- "(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+[.][A-Z]{2,}",
31
- "(?s)-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----.*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
32
- ];
33
- const maxOriginalFieldKeyLength = 2_048;
34
- const maxOriginalStringLength = 16_384;
35
- const maxJsonDepth = 32;
36
- const maxJsonValues = 1_024;
37
- const SensitiveScalar = Schema.Union([
38
- Schema.String,
39
- Schema.Number.check(Schema.isFinite()),
40
- Schema.Boolean,
41
- ]);
42
- const decodeSensitiveScalar = Schema.decodeUnknownOption(SensitiveScalar);
43
- const decodeJsonText = Schema.decodeOption(Schema.fromJsonString(Schema.Json));
44
- const decodeJsonObject = Schema.decodeUnknownOption(Schema.JsonObject);
45
- const asciiCaseInsensitive = (source) => source.replace(/[A-Za-z]/g, (letter) => `[${letter.toLowerCase()}${letter.toUpperCase()}]`);
46
- const sensitiveTerms = sensitiveTermSources.map(asciiCaseInsensitive).join("|");
47
- const sensitiveKeyPattern = new RegExp(`(?:${sensitiveTerms})(?=[._-]|[A-Z0-9]|$)`);
48
- const sensitiveTextTermPattern = new RegExp(`(?:${sensitiveTerms})(?=[._-]|[A-Z0-9]|[^A-Za-z0-9._-]|$)`);
49
- const coreValuePatterns = [
50
- /Bearer\s+[A-Za-z0-9._~+/=-]+/gi,
51
- /(?:sk|rk)[_-][A-Za-z0-9_*.-]{3,}/g,
52
- /eyJ[A-Za-z0-9_-]+[.]eyJ[A-Za-z0-9_-]+[.][A-Za-z0-9_-]+/g,
53
- /[A-Z0-9._%+-]+@[A-Z0-9.-]+[.][A-Z]{2,}/gi,
54
- /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
55
- ];
56
- const structuredAssignmentPattern = /([A-Za-z0-9_.-]+)(\s*[=:]\s*)(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^,;\s]+))/g;
57
- export const isSensitiveFieldKey = (key) => sensitiveKeyPattern.test(key);
58
- const replaceCoreValues = (value) => {
59
- let sanitized = value;
60
- for (const pattern of coreValuePatterns) {
61
- pattern.lastIndex = 0;
62
- sanitized = sanitized.replace(pattern, sensitiveTextReplacement);
63
- }
64
- return sanitized;
65
- };
66
- const containsCoreValue = (value) => {
67
- for (const pattern of coreValuePatterns) {
68
- pattern.lastIndex = 0;
69
- if (pattern.test(value)) {
70
- pattern.lastIndex = 0;
71
- return true;
72
- }
73
- }
74
- return false;
75
- };
76
- const replaceStructuredAssignments = (value) => {
77
- structuredAssignmentPattern.lastIndex = 0;
78
- let sanitized = "";
79
- let offset = 0;
80
- for (const match of value.matchAll(structuredAssignmentPattern)) {
81
- const index = match.index;
82
- const full = match[0];
83
- const key = match[1];
84
- const separator = match[2];
85
- if (!Predicate.isNumber(index) || !Predicate.isString(full) || !Predicate.isString(key)) {
86
- continue;
87
- }
88
- sanitized += value.slice(offset, index);
89
- if (!isSensitiveFieldKey(key) || !Predicate.isString(separator)) {
90
- sanitized += full;
91
- offset = index + full.length;
92
- continue;
93
- }
94
- if (Predicate.isString(match[3])) {
95
- sanitized += `${key}${separator}"${sensitiveTextReplacement}"`;
96
- }
97
- else if (Predicate.isString(match[4])) {
98
- sanitized += `${key}${separator}'${sensitiveTextReplacement}'`;
99
- }
100
- else {
101
- sanitized += `${key}${separator}${sensitiveTextReplacement}`;
102
- }
103
- offset = index + full.length;
104
- }
105
- return sanitized + value.slice(offset);
106
- };
107
- const containsStructuredAssignment = (value) => replaceStructuredAssignments(value) !== value;
108
- const sanitizeJson = (source) => {
109
- const root = { value: null };
110
- const stack = [
111
- {
112
- source,
113
- depth: 0,
114
- sensitive: false,
115
- assign: (value) => {
116
- root.value = value;
117
- },
118
- },
119
- ];
120
- let visited = 0;
121
- while (stack.length > 0) {
122
- const current = stack.pop();
123
- if (!Predicate.isNotUndefined(current)) {
124
- continue;
125
- }
126
- visited += 1;
127
- if (visited > maxJsonValues || current.depth > maxJsonDepth) {
128
- return Option.none();
129
- }
130
- if (current.sensitive) {
131
- current.assign(sensitiveTextReplacement);
132
- continue;
133
- }
134
- if (current.source === null ||
135
- Predicate.isNumber(current.source) ||
136
- Predicate.isBoolean(current.source)) {
137
- current.assign(current.source);
138
- continue;
139
- }
140
- if (Predicate.isString(current.source)) {
141
- current.assign(replaceCoreValues(replaceStructuredAssignments(current.source)));
142
- continue;
143
- }
144
- if (Array.isArray(current.source)) {
145
- const output = current.source.map(() => null);
146
- current.assign(output);
147
- for (let index = current.source.length - 1; index >= 0; index -= 1) {
148
- const child = current.source[index];
149
- if (!Predicate.isNotUndefined(child)) {
150
- continue;
151
- }
152
- stack.push({
153
- source: child,
154
- depth: current.depth + 1,
155
- sensitive: false,
156
- assign: (value) => {
157
- output[index] = value;
158
- },
159
- });
160
- }
161
- continue;
162
- }
163
- const sourceObject = decodeJsonObject(current.source);
164
- if (Option.isNone(sourceObject)) {
165
- return Option.none();
166
- }
167
- const output = Object.create(null);
168
- const outputKeys = new Set();
169
- current.assign(output);
170
- const keys = Object.keys(sourceObject.value);
171
- for (const key of keys) {
172
- if (key.length > maxOriginalFieldKeyLength ||
173
- containsCoreValue(key) ||
174
- containsStructuredAssignment(key)) {
175
- continue;
176
- }
177
- output[key] = null;
178
- outputKeys.add(key);
179
- }
180
- for (let index = keys.length - 1; index >= 0; index -= 1) {
181
- const key = keys[index];
182
- if (!Predicate.isString(key) || !outputKeys.has(key)) {
183
- continue;
184
- }
185
- const child = sourceObject.value[key];
186
- if (!Predicate.isNotUndefined(child)) {
187
- continue;
188
- }
189
- stack.push({
190
- source: child,
191
- depth: current.depth + 1,
192
- sensitive: isSensitiveFieldKey(key),
193
- assign: (value) => {
194
- output[key] = value;
195
- },
196
- });
197
- }
198
- }
199
- return Option.some(JSON.stringify(root.value));
200
- };
201
- const sanitizeString = (value, outputLimit) => {
202
- if (value.length > maxOriginalStringLength) {
203
- return sensitiveTextReplacement;
204
- }
205
- const trimmed = value.trimStart();
206
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
207
- const parsed = decodeJsonText(value);
208
- if (Option.isSome(parsed)) {
209
- const sanitizedJson = Option.getOrElse(sanitizeJson(parsed.value), () => sensitiveTextReplacement);
210
- return sanitizedJson.length <= outputLimit ? sanitizedJson : sensitiveTextReplacement;
211
- }
212
- if (sensitiveTextTermPattern.test(value)) {
213
- return sensitiveTextReplacement;
214
- }
215
- }
216
- return replaceCoreValues(replaceStructuredAssignments(value));
217
- };
218
- const shouldDropKey = (key) => key.length > maxOriginalFieldKeyLength ||
219
- containsCoreValue(key) ||
220
- containsStructuredAssignment(key);
221
- export const sanitizeBrowserFields = (fields) => {
222
- const sanitized = [];
223
- const boundedKeys = new Set();
224
- for (const [key, runtimeValue] of Object.entries(fields)) {
225
- const decoded = decodeSensitiveScalar(runtimeValue);
226
- if (Option.isNone(decoded) || key === "" || shouldDropKey(key)) {
227
- continue;
228
- }
229
- const boundedKey = key.slice(0, maxFieldKeyLength);
230
- if (boundedKeys.has(boundedKey)) {
231
- continue;
232
- }
233
- const value = isSensitiveFieldKey(key)
234
- ? sensitiveFieldReplacement
235
- : Predicate.isString(decoded.value)
236
- ? sanitizeString(decoded.value, maxFieldValueLength).slice(0, maxFieldValueLength)
237
- : decoded.value;
238
- sanitized.push([boundedKey, value]);
239
- boundedKeys.add(boundedKey);
240
- if (boundedKeys.size >= maxFieldsPerEvent) {
241
- break;
242
- }
243
- }
244
- return Object.fromEntries(sanitized);
245
- };
246
- export const sanitizeBrowserEventName = (name) => sanitizeString(name, maxEventNameLength).slice(0, maxEventNameLength);
1
+ export { sanitizeBrowserFields, sanitizeEventName } from "./policy/BrowserFieldPolicy.js";
2
+ export { baseBlockedKeys, collectorBlockedKeyPattern, collectorBlockedValuePatterns, isSensitiveFieldKey, sensitiveFieldReplacement, sensitiveTextReplacement, } from "./policy/PolicyVocabulary.js";
@@ -0,0 +1,47 @@
1
+ import { Effect, Option, Schema } from "effect";
2
+ export declare const serviceNamespace = "equipe-tech";
3
+ export declare const resourceNamePattern: RegExp;
4
+ export declare const serviceNameMaxLength = 63;
5
+ export declare const environmentNameMaxLength = 32;
6
+ export declare const ServiceName: Schema.brand<Schema.String, "ServiceName">;
7
+ export type ServiceName = typeof ServiceName.Type;
8
+ export declare const EnvironmentName: Schema.brand<Schema.String, "EnvironmentName">;
9
+ export type EnvironmentName = typeof EnvironmentName.Type;
10
+ export declare const ServiceVersion: Schema.brand<Schema.String, "ServiceVersion">;
11
+ export type ServiceVersion = typeof ServiceVersion.Type;
12
+ export declare const ServiceInstanceId: Schema.brand<Schema.NonEmptyString, "ServiceInstanceId">;
13
+ export type ServiceInstanceId = typeof ServiceInstanceId.Type;
14
+ export declare const EnvironmentAliasPolicy: Schema.Literals<readonly ["emitted", "omitted"]>;
15
+ export type EnvironmentAliasPolicy = typeof EnvironmentAliasPolicy.Type;
16
+ declare const ResourceIdentity_base: Schema.Class<ResourceIdentity, Schema.Struct<{
17
+ serviceName: Schema.brand<Schema.String, "ServiceName">;
18
+ serviceVersion: Schema.brand<Schema.String, "ServiceVersion">;
19
+ environment: Schema.brand<Schema.String, "EnvironmentName">;
20
+ instance: Schema.withConstructorDefault<Schema.Option<Schema.brand<Schema.NonEmptyString, "ServiceInstanceId">>>;
21
+ }>, {}>;
22
+ export declare class ResourceIdentity extends ResourceIdentity_base {
23
+ }
24
+ export declare const ResourceIdentityField: Schema.Literals<readonly ["service.name", "service.version", "deployment.environment.name", "service.instance.id"]>;
25
+ export type ResourceIdentityField = typeof ResourceIdentityField.Type;
26
+ declare const InvalidResourceIdentity_base: Schema.Class<InvalidResourceIdentity, Schema.TaggedStruct<"InvalidResourceIdentity", {
27
+ readonly code: Schema.Literal<"OBS_RESOURCE_IDENTITY_INVALID">;
28
+ readonly message: Schema.String;
29
+ readonly field: Schema.Literals<readonly ["service.name", "service.version", "deployment.environment.name", "service.instance.id"]>;
30
+ readonly value: Schema.String;
31
+ readonly rule: Schema.String;
32
+ }>, import("effect/Cause").YieldableError>;
33
+ export declare class InvalidResourceIdentity extends InvalidResourceIdentity_base {
34
+ }
35
+ export interface ResourceIdentityInput {
36
+ readonly serviceName: string;
37
+ readonly serviceVersion: string;
38
+ readonly environment: string;
39
+ readonly instance?: Option.Option<string> | undefined;
40
+ }
41
+ export declare const parseResourceIdentity: (input: ResourceIdentityInput) => Effect.Effect<ResourceIdentity, InvalidResourceIdentity, never>;
42
+ export type ResourceAttributes = {
43
+ readonly [attributeName: string]: string;
44
+ };
45
+ export declare const instanceResourceAttributes: (identity: ResourceIdentity, alias: EnvironmentAliasPolicy) => ResourceAttributes;
46
+ export declare const serviceResourceAttributes: (identity: ResourceIdentity, alias: EnvironmentAliasPolicy) => ResourceAttributes;
47
+ export {};
@@ -0,0 +1,82 @@
1
+ import { Effect, Option, Schema } from "effect";
2
+ export const serviceNamespace = "equipe-tech";
3
+ export const resourceNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4
+ export const serviceNameMaxLength = 63;
5
+ export const environmentNameMaxLength = 32;
6
+ const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
7
+ const immutableReleasePattern = /^[0-9a-f]{7,64}$/;
8
+ export const ServiceName = Schema.String.check(Schema.isPattern(resourceNamePattern), Schema.isMaxLength(serviceNameMaxLength)).pipe(Schema.brand("ServiceName"));
9
+ export const EnvironmentName = Schema.String.check(Schema.isPattern(resourceNamePattern), Schema.isMaxLength(environmentNameMaxLength)).pipe(Schema.brand("EnvironmentName"));
10
+ export const ServiceVersion = Schema.String.check(Schema.makeFilter((value) => semverPattern.test(value) || immutableReleasePattern.test(value), {
11
+ expected: "SemVer 2.0.0 or a 7 to 64 character lowercase hexadecimal release identifier",
12
+ })).pipe(Schema.brand("ServiceVersion"));
13
+ export const ServiceInstanceId = Schema.NonEmptyString.check(Schema.isMaxLength(128)).pipe(Schema.brand("ServiceInstanceId"));
14
+ export const EnvironmentAliasPolicy = Schema.Literals(["emitted", "omitted"]);
15
+ const identityFields = {
16
+ serviceName: ServiceName,
17
+ serviceVersion: ServiceVersion,
18
+ environment: EnvironmentName,
19
+ instance: Schema.Option(ServiceInstanceId).pipe(Schema.withConstructorDefault(Effect.succeed(Option.none()))),
20
+ };
21
+ export class ResourceIdentity extends Schema.Class("@equipe-tech/observability/ResourceIdentity")(identityFields) {
22
+ }
23
+ export const ResourceIdentityField = Schema.Literals([
24
+ "service.name",
25
+ "service.version",
26
+ "deployment.environment.name",
27
+ "service.instance.id",
28
+ ]);
29
+ export class InvalidResourceIdentity extends Schema.TaggedError()("InvalidResourceIdentity", {
30
+ code: Schema.Literal("OBS_RESOURCE_IDENTITY_INVALID"),
31
+ message: Schema.String,
32
+ field: ResourceIdentityField,
33
+ value: Schema.String,
34
+ rule: Schema.String,
35
+ }) {
36
+ }
37
+ const serviceNameRule = "lowercase letters, numbers, and hyphens with at most 63 characters";
38
+ const environmentNameRule = "lowercase letters, numbers, and hyphens with at most 32 characters";
39
+ const serviceVersionRule = "SemVer 2.0.0 or a 7 to 64 character lowercase hexadecimal immutable release identifier";
40
+ const serviceInstanceRule = "a non-empty value with at most 128 characters";
41
+ const invalidIdentity = (field, value, rule, label) => new InvalidResourceIdentity({
42
+ code: "OBS_RESOURCE_IDENTITY_INVALID",
43
+ field,
44
+ value,
45
+ rule,
46
+ message: `${label} ${JSON.stringify(value)} is invalid. Use ${rule}.`,
47
+ });
48
+ const decodeServiceName = Schema.decodeUnknownEffect(ServiceName);
49
+ const decodeServiceVersion = Schema.decodeUnknownEffect(ServiceVersion);
50
+ const decodeEnvironmentName = Schema.decodeUnknownEffect(EnvironmentName);
51
+ const decodeServiceInstanceId = Schema.decodeUnknownEffect(ServiceInstanceId);
52
+ export const parseResourceIdentity = Effect.fn("parseResourceIdentity")(function* (input) {
53
+ const serviceName = yield* decodeServiceName(input.serviceName).pipe(Effect.mapError(() => invalidIdentity("service.name", input.serviceName, serviceNameRule, "Service name")));
54
+ const serviceVersion = yield* decodeServiceVersion(input.serviceVersion).pipe(Effect.mapError(() => invalidIdentity("service.version", input.serviceVersion, serviceVersionRule, "Service version")));
55
+ const environment = yield* decodeEnvironmentName(input.environment).pipe(Effect.mapError(() => invalidIdentity("deployment.environment.name", input.environment, environmentNameRule, "Environment name")));
56
+ const rawInstance = input.instance ?? Option.none();
57
+ const instance = yield* Option.match(rawInstance, {
58
+ onNone: () => Effect.succeed(Option.none()),
59
+ onSome: (value) => decodeServiceInstanceId(value).pipe(Effect.map(Option.some), Effect.mapError(() => invalidIdentity("service.instance.id", value, serviceInstanceRule, "Service instance ID"))),
60
+ });
61
+ return new ResourceIdentity({ serviceName, serviceVersion, environment, instance });
62
+ });
63
+ const projectedAttributes = (identity, alias) => {
64
+ const attributes = {
65
+ "service.namespace": serviceNamespace,
66
+ "service.name": identity.serviceName,
67
+ "service.version": identity.serviceVersion,
68
+ "deployment.environment.name": identity.environment,
69
+ };
70
+ if (alias === "emitted") {
71
+ attributes["deployment.environment"] = identity.environment;
72
+ }
73
+ return attributes;
74
+ };
75
+ export const instanceResourceAttributes = (identity, alias) => {
76
+ const attributes = projectedAttributes(identity, alias);
77
+ if (Option.isSome(identity.instance)) {
78
+ attributes["service.instance.id"] = identity.instance.value;
79
+ }
80
+ return attributes;
81
+ };
82
+ export const serviceResourceAttributes = (identity, alias) => projectedAttributes(identity, alias);
@@ -1,11 +1,26 @@
1
1
  import { Duration, Layer } from "effect";
2
2
  import { type HttpClient } from "effect/unstable/http";
3
3
  import { OtlpExporter } from "effect/unstable/observability";
4
+ import type { InvalidResourceIdentity } from "./ResourceIdentity.js";
5
+ import type { DuplicateReleaseVariable } from "./profile/ObservabilityConfigError.js";
4
6
  import type { EnvironmentVariables, InvalidTelemetryEnvironment } from "./TelemetryConfig.js";
5
7
  import { type TelemetryConfig } from "./TelemetryConfig.js";
8
+ import { LayerMetricsRuntime } from "./MetricsRuntime.js";
9
+ import { type DataPolicy } from "./policy/DataPolicy.js";
10
+ import type { InvalidDataPolicy } from "./policy/DataPolicyError.js";
11
+ import type { ContractRegistry } from "./profile/ObservabilityAdapter.js";
12
+ import { type ResourceAttribute } from "./policy/ResourceAttributePolicy.js";
6
13
  export type OtlpLayerOptions = {
7
14
  readonly shutdownTimeout?: Duration.Input | undefined;
15
+ readonly policy?: DataPolicy | undefined;
16
+ readonly resourceAttributes?: ReadonlyArray<ResourceAttribute> | undefined;
17
+ readonly contract?: ContractRegistry | undefined;
8
18
  };
9
- export declare const layerOtlp: (config: TelemetryConfig, options?: OtlpLayerOptions) => Layer.Layer<OtlpExporter.Flusher, never, HttpClient.HttpClient>;
10
- export declare const layer: (config: TelemetryConfig, options?: OtlpLayerOptions) => Layer.Layer<OtlpExporter.Flusher>;
11
- export declare const layerFromEnv: (env: EnvironmentVariables, options?: OtlpLayerOptions) => Layer.Layer<OtlpExporter.Flusher, InvalidTelemetryEnvironment>;
19
+ export declare const layerOtlp: (config: TelemetryConfig, options?: OtlpLayerOptions) => Layer.Layer<OtlpExporter.Flusher | LayerMetricsRuntime, InvalidDataPolicy, HttpClient.HttpClient>;
20
+ type OtlpLayerOptionsWithoutResourceAttributes = Omit<OtlpLayerOptions, "resourceAttributes"> & {
21
+ readonly resourceAttributes?: undefined;
22
+ };
23
+ export declare function layer(config: TelemetryConfig, options?: OtlpLayerOptionsWithoutResourceAttributes): Layer.Layer<OtlpExporter.Flusher | LayerMetricsRuntime, never>;
24
+ export declare function layer(config: TelemetryConfig, options: OtlpLayerOptions): Layer.Layer<OtlpExporter.Flusher | LayerMetricsRuntime, InvalidDataPolicy>;
25
+ export declare const layerFromEnv: (env: EnvironmentVariables, options?: OtlpLayerOptions) => Layer.Layer<OtlpExporter.Flusher | LayerMetricsRuntime, InvalidTelemetryEnvironment | InvalidResourceIdentity | DuplicateReleaseVariable | InvalidDataPolicy>;
26
+ export {};
package/dist/Telemetry.js CHANGED
@@ -1,31 +1,49 @@
1
- import { Duration, Effect, Layer } from "effect";
1
+ import { ConfigProvider, Duration, Effect, Layer } from "effect";
2
2
  import { FetchHttpClient, HttpClientRequest } from "effect/unstable/http";
3
- import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability";
3
+ import { OtlpSerialization } from "effect/unstable/observability";
4
+ import { layerPolicyOtlpLogger } from "./PolicyOtlpLogger.js";
5
+ import { instanceResourceAttributes } from "./ResourceIdentity.js";
4
6
  import { telemetryConfigFromEnv } from "./TelemetryConfig.js";
5
7
  import { layerMetricsRuntime } from "./MetricsRuntime.js";
6
- import { layerHttpServerOtlpTracer } from "./nestjs/HttpServerOtlpTracer.js";
8
+ import { layerBrowserSignalExporter, layerHttpServerOtlpTracer, } from "./trace/HttpServerOtlpTracer.js";
9
+ import { baseDataPolicy, CurrentDataPolicy } from "./policy/DataPolicy.js";
10
+ import { layerBrowserMetricRecorder } from "./browser/BrowserMetricRecorder.js";
11
+ import { parseResourceAttributes, } from "./policy/ResourceAttributePolicy.js";
12
+ const packageResourceConfig = ConfigProvider.layer(ConfigProvider.fromUnknown({}));
7
13
  export const layerOtlp = (config, options = {}) => {
8
- const base = HttpClientRequest.get(config.otlpEndpoint.toString());
9
- const url = (path) => HttpClientRequest.appendUrl(base, path).url;
10
- const resource = {
11
- serviceName: config.serviceName,
12
- serviceVersion: config.serviceVersion,
13
- attributes: {
14
- "deployment.environment.name": config.environment,
15
- },
16
- };
17
- const metrics = layerMetricsRuntime(config, {
18
- shutdownTimeoutMilliseconds: Duration.toMillis(options.shutdownTimeout ?? "3 seconds"),
19
- });
20
- return Layer.mergeAll(OtlpLogger.layer({
21
- url: url("/v1/logs"),
22
- resource,
23
- shutdownTimeout: options.shutdownTimeout,
24
- }), metrics, layerHttpServerOtlpTracer({
25
- url: url("/v1/traces"),
26
- resource,
27
- shutdownTimeout: options.shutdownTimeout,
28
- })).pipe(Layer.provide(OtlpSerialization.layerJson));
14
+ const policy = options.policy ?? baseDataPolicy;
15
+ const canonical = instanceResourceAttributes(config.identity, config.environmentAlias);
16
+ return Layer.unwrap(Effect.map(parseResourceAttributes(policy, canonical, options.resourceAttributes ?? []), (parsed) => {
17
+ const base = HttpClientRequest.get(config.otlpEndpoint.toString());
18
+ const url = (path) => HttpClientRequest.appendUrl(base, path).url;
19
+ const resource = {
20
+ serviceName: config.identity.serviceName,
21
+ serviceVersion: config.identity.serviceVersion,
22
+ attributes: Object.fromEntries(parsed),
23
+ };
24
+ const metrics = layerMetricsRuntime(config, {
25
+ shutdownTimeoutMilliseconds: Duration.toMillis(options.shutdownTimeout ?? "3 seconds"),
26
+ policy,
27
+ resourceAttributes: parsed,
28
+ });
29
+ return Layer.mergeAll(Layer.succeed(CurrentDataPolicy, policy), layerPolicyOtlpLogger({
30
+ url: url("/v1/logs"),
31
+ resource,
32
+ shutdownTimeout: options.shutdownTimeout,
33
+ }), layerBrowserMetricRecorder(options.contract).pipe(Layer.provideMerge(metrics)), layerHttpServerOtlpTracer({
34
+ url: url("/v1/traces"),
35
+ policy,
36
+ resource,
37
+ shutdownTimeout: options.shutdownTimeout,
38
+ }), layerBrowserSignalExporter({
39
+ tracesUrl: url("/v1/traces"),
40
+ policy,
41
+ resource,
42
+ shutdownTimeout: options.shutdownTimeout,
43
+ })).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(packageResourceConfig));
44
+ }));
29
45
  };
30
- export const layer = (config, options = {}) => layerOtlp(config, options).pipe(Layer.provide(FetchHttpClient.layer));
46
+ export function layer(config, options = {}) {
47
+ return layerOtlp(config, options).pipe(Layer.provide(FetchHttpClient.layer));
48
+ }
31
49
  export const layerFromEnv = (env, options = {}) => Layer.unwrap(Effect.map(telemetryConfigFromEnv(env), (config) => layer(config, options)));