@logbrew/sdk 0.1.37 → 0.1.39

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 (103) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +35 -2984
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-breadcrumbs.cjs +143 -0
  33. package/issue-diagnostics.cjs +94 -582
  34. package/issue-evidence.cjs +256 -0
  35. package/issue-exception.cjs +268 -0
  36. package/issue-stack-frames.cjs +220 -0
  37. package/issue-stack-text.cjs +101 -0
  38. package/issue-stack.cjs +188 -225
  39. package/issue-text.cjs +72 -0
  40. package/log-context.cjs +87 -67
  41. package/metric-validation.cjs +95 -0
  42. package/network-milestone.cjs +155 -0
  43. package/null-value.cjs +4 -0
  44. package/opentelemetry-bridge.cjs +330 -0
  45. package/opentelemetry-context.cjs +245 -0
  46. package/opentelemetry-exceptions.cjs +69 -0
  47. package/opentelemetry-keys.cjs +183 -0
  48. package/opentelemetry-span.cjs +286 -0
  49. package/opentelemetry-summary.cjs +213 -0
  50. package/opentelemetry-time.cjs +131 -0
  51. package/opentelemetry.cjs +99 -1068
  52. package/package.json +64 -1
  53. package/pino-destination.cjs +108 -0
  54. package/pino-record.cjs +237 -0
  55. package/product-action.cjs +163 -0
  56. package/provided-breadcrumbs.cjs +117 -0
  57. package/random-hex.cjs +29 -0
  58. package/react-native.js +1 -1
  59. package/recording-transport.cjs +68 -0
  60. package/release-artifacts-build.cjs +263 -84
  61. package/release-artifacts-common.js +347 -66
  62. package/release-artifacts-debug-id.js +178 -0
  63. package/release-artifacts-inspect.js +213 -0
  64. package/release-artifacts-issue-event.js +99 -0
  65. package/release-artifacts-manifest-artifact.js +107 -0
  66. package/release-artifacts-manifest.js +77 -0
  67. package/release-artifacts-mappings.js +265 -0
  68. package/release-artifacts-prepare.js +297 -0
  69. package/release-artifacts-source-context.js +210 -0
  70. package/release-artifacts-symbolication.js +272 -421
  71. package/release-artifacts-upload-files.js +122 -0
  72. package/release-artifacts-upload-request.js +246 -0
  73. package/release-artifacts-upload.js +155 -300
  74. package/release-artifacts-urls.js +49 -0
  75. package/release-artifacts.js +189 -662
  76. package/retry-delay.cjs +32 -0
  77. package/sdk-error.cjs +36 -0
  78. package/span-validation.cjs +191 -0
  79. package/stored-events.cjs +243 -0
  80. package/support-ticket.cjs +232 -122
  81. package/telemetry-context.cjs +343 -251
  82. package/trace-context.cjs +376 -179
  83. package/transport-error.cjs +23 -0
  84. package/types/client.d.ts +67 -0
  85. package/types/delivery.d.ts +125 -0
  86. package/types/issues.d.ts +249 -0
  87. package/types/logging.d.ts +175 -0
  88. package/types/opentelemetry.d.ts +205 -0
  89. package/types/product.d.ts +128 -0
  90. package/types/recording-transport.d.ts +15 -0
  91. package/types/sdk-error.d.ts +9 -0
  92. package/types/telemetry.d.ts +171 -0
  93. package/types/trace-context.d.ts +110 -0
  94. package/types/transport-error.d.ts +10 -0
  95. package/utf8.cjs +37 -0
  96. package/validation.cjs +327 -0
  97. package/vite-release-artifacts-config.cjs +120 -0
  98. package/vite-release-artifacts.cjs +199 -99
  99. package/vite-release-artifacts.d.cts +65 -40
  100. package/vite-release-artifacts.d.ts +65 -40
  101. package/vite-release-artifacts.js +2 -2
  102. package/winston-info.cjs +205 -0
  103. package/winston.cjs +189 -290
@@ -0,0 +1,330 @@
1
+ const { callableOrUndefined, errorHandler, settledFlush } = require("./capture-support.cjs");
2
+ const { LogBrewClient } = require("./client.cjs");
3
+ const { NULL_VALUE } = require("./null-value.cjs");
4
+ const { resolveOpenTelemetryReadableSpanOptions } = require("./opentelemetry-keys.cjs");
5
+ const { readableSpanAttributes } = require("./opentelemetry-span.cjs");
6
+ const { OpenTelemetryTraceSummary } = require("./opentelemetry-summary.cjs");
7
+ const { timestampFromOpenTelemetryReadableSpan } = require("./opentelemetry-time.cjs");
8
+ const { SdkError } = require("./sdk-error.cjs");
9
+ const { isList, isRecord, isTruthy, requireNonEmpty } = require("./validation.cjs");
10
+
11
+ /**
12
+ * @import { SpanAttributes } from "./index.cjs"
13
+ * @import { DeepReadonly } from "./validation.cjs"
14
+ * @typedef {InstanceType<typeof OpenTelemetryTraceSummary>} TraceSummary
15
+ * @import { ErrorHandler } from "./capture-support.cjs"
16
+ * @import { ResolvedSpanOptions } from "./opentelemetry-keys.cjs"
17
+ * @typedef {Readonly<{
18
+ * span: (id: string, timestamp: unknown, attributes: Readonly<Record<string, unknown>>) => unknown,
19
+ * flush: (transport: unknown) => unknown,
20
+ * pendingEvents: () => number
21
+ * }>} SpanClient
22
+ * @typedef {Readonly<{
23
+ * client: SpanClient,
24
+ * eventIdPrefix: string,
25
+ * flushEnabled: boolean,
26
+ * includeTraceSummary: boolean,
27
+ * onError: ErrorHandler,
28
+ * resolvedOptions: ResolvedSpanOptions,
29
+ * spanFilter: ((span: unknown) => unknown) | undefined,
30
+ * timestamp: (() => unknown) | undefined,
31
+ * transport: unknown
32
+ * }>} BridgeSettings
33
+ */
34
+
35
+ const NO_PENDING_EVENTS = 0;
36
+ const NO_TRACE_SUMMARIES = 0;
37
+ const NO_SPANS = 0;
38
+ const OTEL_EXPORT_RESULT_SUCCESS = 0;
39
+ const OTEL_EXPORT_RESULT_FAILED = 1;
40
+
41
+ /**
42
+ * @param {unknown} error
43
+ * @returns {{ code: number, error?: Error }}
44
+ */
45
+ function openTelemetryExportFailure(error) {
46
+ return {
47
+ code: OTEL_EXPORT_RESULT_FAILED,
48
+ ...(error instanceof Error ? { error } : {})
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @param {unknown} filter
54
+ * @returns {((span: unknown) => unknown) | undefined}
55
+ */
56
+ function spanFilterOrUndefined(filter) {
57
+ if (typeof filter !== "function") {
58
+ return undefined;
59
+ }
60
+ return (span) => {
61
+ /** @type {unknown} */
62
+ const keep = Reflect.apply(filter, undefined, [span]);
63
+ return keep;
64
+ };
65
+ }
66
+
67
+ /**
68
+ * @param {Readonly<Record<string, unknown>>} config
69
+ * @param {string} kind
70
+ * @returns {SpanClient}
71
+ */
72
+ function bridgeClient(config, kind) {
73
+ const { client } = config;
74
+ if (!(client instanceof LogBrewClient)) {
75
+ throw new SdkError("validation_error", `OpenTelemetry span ${kind} client must be a LogBrewClient`);
76
+ }
77
+ return client;
78
+ }
79
+
80
+ /**
81
+ * @param {Readonly<Record<string, unknown>>} config
82
+ * @param {string} kind
83
+ * @param {string} flushKey
84
+ * @returns {BridgeSettings}
85
+ */
86
+ function bridgeSettings(config, kind, flushKey) {
87
+ const client = bridgeClient(config, kind);
88
+ const eventIdPrefix = requireNonEmpty("OpenTelemetry eventIdPrefix", config["eventIdPrefix"] ?? "otel");
89
+ const { transport } = config;
90
+ const timestamp = callableOrUndefined(config["timestamp"]);
91
+ const onError = errorHandler(config["onError"]);
92
+ const spanFilter = spanFilterOrUndefined(config["spanFilter"]);
93
+ const flushEnabled = config[flushKey] !== false;
94
+ const includeTraceSummary = config["includeTraceSummary"] === true;
95
+ const resolvedOptions = resolveOpenTelemetryReadableSpanOptions(config);
96
+ return { client, eventIdPrefix, flushEnabled, includeTraceSummary, onError, resolvedOptions, spanFilter, timestamp, transport };
97
+ }
98
+
99
+ class OpenTelemetryBridge {
100
+ captured = NO_SPANS;
101
+ closed = false;
102
+ flushInFlight = false;
103
+ /** @type {Promise<unknown>} */
104
+ pendingFlush = Promise.resolve(NULL_VALUE);
105
+ currentFlush = this.pendingFlush;
106
+ requestedFlush = this.pendingFlush;
107
+ /** @type {Promise<unknown> | undefined} */
108
+ queuedFlush = undefined;
109
+ traceSummaryCount = NO_TRACE_SUMMARIES;
110
+ /** @type {Map<string, TraceSummary>} */
111
+ traceSummaries = new Map();
112
+
113
+ /**
114
+ * @param {BridgeSettings} settings
115
+ * @param {boolean} swallowErrors
116
+ */
117
+ constructor(settings, swallowErrors) {
118
+ this.settings = settings;
119
+ this.swallowErrors = swallowErrors;
120
+ }
121
+
122
+ /**
123
+ * @param {unknown} span
124
+ * @returns {void}
125
+ */
126
+ enqueue(span) {
127
+ const { settings } = this;
128
+ if (settings.spanFilter?.(span) === false || !isRecord(span)) {
129
+ return;
130
+ }
131
+ const attributes = readableSpanAttributes(span, settings.resolvedOptions);
132
+ if (attributes === undefined) {
133
+ return;
134
+ }
135
+ this.recordTraceSummary(attributes, span);
136
+ this.captured += 1;
137
+ settings.client.span(
138
+ `${settings.eventIdPrefix}_${this.captured}`,
139
+ timestampFromOpenTelemetryReadableSpan(span, settings.timestamp),
140
+ attributes
141
+ );
142
+ }
143
+
144
+ /**
145
+ * @param {unknown} span
146
+ * @returns {void}
147
+ */
148
+ end(span) {
149
+ if (this.closed) {
150
+ return;
151
+ }
152
+ try {
153
+ this.enqueue(span);
154
+ } catch (error) {
155
+ this.settings.onError(error);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * @param {DeepReadonly<SpanAttributes>} attributes
161
+ * @param {Readonly<Record<string, unknown>>} span
162
+ * @returns {void}
163
+ */
164
+ recordTraceSummary(attributes, span) {
165
+ if (!this.settings.includeTraceSummary) {
166
+ return;
167
+ }
168
+ let summary = this.traceSummaries.get(attributes.traceId);
169
+ if (summary === undefined) {
170
+ summary = new OpenTelemetryTraceSummary(attributes.traceId);
171
+ this.traceSummaries.set(attributes.traceId, summary);
172
+ }
173
+ summary.record(attributes, span);
174
+ }
175
+
176
+ /**
177
+ * @returns {void}
178
+ */
179
+ enqueueTraceSummaries() {
180
+ if (this.traceSummaries.size === NO_TRACE_SUMMARIES) {
181
+ return;
182
+ }
183
+ const { settings } = this;
184
+ const summaries = [...this.traceSummaries.values()];
185
+ this.traceSummaries.clear();
186
+ for (const summary of summaries) {
187
+ try {
188
+ this.traceSummaryCount += 1;
189
+ settings.client.span(
190
+ `${settings.eventIdPrefix}_trace_${this.traceSummaryCount}`,
191
+ summary.timestamp(settings.timestamp),
192
+ summary.attributes()
193
+ );
194
+ } catch (error) {
195
+ settings.onError(error);
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @param {boolean} flushEnabled
202
+ * @returns {Promise<void>}
203
+ */
204
+ async flushQueue(flushEnabled) {
205
+ const { settings } = this;
206
+ if (settings.includeTraceSummary) {
207
+ this.enqueueTraceSummaries();
208
+ }
209
+ if (!isTruthy(settings.transport) || !flushEnabled || settings.client.pendingEvents() === NO_PENDING_EVENTS) {
210
+ await this.pendingFlush;
211
+ return;
212
+ }
213
+ this.requestFlush();
214
+ await this.requestedFlush;
215
+ }
216
+
217
+ /**
218
+ * @returns {void}
219
+ */
220
+ requestFlush() {
221
+ if (this.flushInFlight) {
222
+ this.queuedFlush ??= this.flushAfter(this.currentFlush);
223
+ this.requestedFlush = this.queuedFlush;
224
+ return;
225
+ }
226
+ this.startFlush();
227
+ }
228
+
229
+ /**
230
+ * @returns {void}
231
+ */
232
+ startFlush() {
233
+ const { settings } = this;
234
+ this.flushInFlight = true;
235
+ const rawFlush = Promise.resolve(settings.client.flush(settings.transport));
236
+ this.currentFlush = rawFlush;
237
+ this.pendingFlush = this.swallowErrors ? settledFlush(rawFlush, settings.onError) : rawFlush;
238
+ this.requestedFlush = this.pendingFlush;
239
+ void this.settle(rawFlush);
240
+ }
241
+
242
+ /**
243
+ * @param {unknown} rawFlush
244
+ * @returns {Promise<void>}
245
+ */
246
+ async settle(rawFlush) {
247
+ try {
248
+ await rawFlush;
249
+ } catch {
250
+ this.finishFlush();
251
+ return;
252
+ }
253
+ this.finishFlush();
254
+ }
255
+
256
+ /**
257
+ * @returns {void}
258
+ */
259
+ finishFlush() {
260
+ this.flushInFlight = false;
261
+ this.queuedFlush = undefined;
262
+ }
263
+
264
+ /**
265
+ * @param {unknown} inFlight
266
+ * @returns {Promise<unknown>}
267
+ */
268
+ async flushAfter(inFlight) {
269
+ try {
270
+ await inFlight;
271
+ } catch (error) {
272
+ if (this.swallowErrors) {
273
+ return NULL_VALUE;
274
+ }
275
+ throw error;
276
+ }
277
+ this.requestFlush();
278
+ const result = await this.requestedFlush;
279
+ return result;
280
+ }
281
+
282
+ /**
283
+ * @param {unknown} spans
284
+ * @returns {{ error: unknown } | undefined}
285
+ */
286
+ enqueueExport(spans) {
287
+ if (this.closed) {
288
+ return { error: new SdkError("shutdown_error", "OpenTelemetry span exporter is already shut down") };
289
+ }
290
+ if (!isList(spans)) {
291
+ const error = new SdkError("validation_error", "OpenTelemetry span exporter spans must be an array");
292
+ this.settings.onError(error);
293
+ return { error };
294
+ }
295
+ return this.enqueueAll(spans);
296
+ }
297
+
298
+ /**
299
+ * @param {readonly unknown[]} spans
300
+ * @returns {{ error: unknown } | undefined}
301
+ */
302
+ enqueueAll(spans) {
303
+ try {
304
+ for (const span of spans) {
305
+ this.enqueue(span);
306
+ }
307
+ } catch (error) {
308
+ this.settings.onError(error);
309
+ return { error };
310
+ }
311
+ return undefined;
312
+ }
313
+
314
+ /**
315
+ * @param {(result: unknown) => void} report
316
+ * @returns {Promise<void>}
317
+ */
318
+ async reportExport(report) {
319
+ try {
320
+ await this.flushQueue(this.settings.flushEnabled);
321
+ } catch (error) {
322
+ this.settings.onError(error);
323
+ report(openTelemetryExportFailure(error));
324
+ return;
325
+ }
326
+ report({ code: OTEL_EXPORT_RESULT_SUCCESS });
327
+ }
328
+ }
329
+
330
+ module.exports = { OpenTelemetryBridge, bridgeSettings, openTelemetryExportFailure };
@@ -0,0 +1,245 @@
1
+ const { NULL_VALUE } = require("./null-value.cjs");
2
+ const { SdkError } = require("./sdk-error.cjs");
3
+ const {
4
+ ZERO_SPAN_ID,
5
+ isObjectValue,
6
+ optionalProperty,
7
+ requireSpanId,
8
+ requireTraceId
9
+ } = require("./validation.cjs");
10
+
11
+ /**
12
+ * @import { LogCorrelationTraceContext } from "./index.cjs"
13
+ * @typedef {{ traceId: string, spanId: string, sampled: boolean }} SpanContextIds
14
+ * @typedef {Readonly<{ spanId?: unknown, spanIdFactory?: unknown }>} ChildSpanOptions
15
+ */
16
+
17
+ const SPAN_ID_BYTES = 8;
18
+ const MAX_SPAN_ID_ATTEMPTS = 3;
19
+ const FIRST_ATTEMPT = 0;
20
+ const ATTEMPT_STEP = 1;
21
+ const BYTE_VALUES = 256;
22
+ const HEX_RADIX = 16;
23
+ const HEX_BYTE_WIDTH = 2;
24
+ const SAMPLED_BIT_BASE = 2;
25
+ const SAMPLED_BIT = 1;
26
+ const OPENTELEMETRY_API_PACKAGE = "@opentelemetry/api";
27
+
28
+ /**
29
+ * @param {unknown} traceId
30
+ * @returns {string | undefined}
31
+ */
32
+ function normalizeTraceId(traceId) {
33
+ try {
34
+ return requireTraceId(traceId).toLowerCase();
35
+ } catch {
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * @param {unknown} spanId
42
+ * @returns {string | undefined}
43
+ */
44
+ function normalizeSpanId(spanId) {
45
+ try {
46
+ return requireSpanId("trace spanId", spanId).toLowerCase();
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @param {unknown} traceFlags
54
+ * @returns {boolean}
55
+ */
56
+ function openTelemetryTraceFlagsSampled(traceFlags) {
57
+ const sampled = optionalProperty(traceFlags, "sampled");
58
+ if (typeof sampled === "boolean") {
59
+ return sampled;
60
+ }
61
+ if (typeof traceFlags === "number" && Number.isFinite(traceFlags)) {
62
+ return Math.abs(Math.trunc(traceFlags) % SAMPLED_BIT_BASE) === SAMPLED_BIT;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * @param {unknown} spanContext
69
+ * @returns {SpanContextIds | undefined}
70
+ */
71
+ function normalizeOpenTelemetrySpanContextIds(spanContext) {
72
+ if (!isObjectValue(spanContext) || spanContext["isValid"] === false) {
73
+ return undefined;
74
+ }
75
+ const traceId = normalizeTraceId(spanContext["traceId"]);
76
+ const spanId = normalizeSpanId(spanContext["spanId"]);
77
+ if (traceId === undefined || spanId === undefined) {
78
+ return undefined;
79
+ }
80
+ return {
81
+ traceId,
82
+
83
+ spanId,
84
+
85
+ sampled: openTelemetryTraceFlagsSampled(spanContext["traceFlags"])
86
+ };
87
+ }
88
+
89
+ /**
90
+ * @param {number} byteLength
91
+ * @returns {string}
92
+ */
93
+ function randomHex(byteLength) {
94
+ const bytes = new Uint8Array(byteLength);
95
+ const crypto = optionalProperty(globalThis, "crypto");
96
+ const getRandomValues = optionalProperty(crypto, "getRandomValues");
97
+ if (typeof getRandomValues === "function") {
98
+ Reflect.apply(getRandomValues, crypto, [bytes]);
99
+ } else {
100
+ for (const index of bytes.keys()) {
101
+ bytes[index] = Math.floor(Math.random() * BYTE_VALUES);
102
+ }
103
+ }
104
+ return Array.from(bytes, (byte) => byte.toString(HEX_RADIX).padStart(HEX_BYTE_WIDTH, "0")).join("");
105
+ }
106
+
107
+ /**
108
+ * @returns {string}
109
+ */
110
+ function defaultSpanIdFactory() {
111
+ for (let attempt = FIRST_ATTEMPT; attempt < MAX_SPAN_ID_ATTEMPTS; attempt += ATTEMPT_STEP) {
112
+ const spanId = randomHex(SPAN_ID_BYTES);
113
+ if (spanId !== ZERO_SPAN_ID) {
114
+ return spanId;
115
+ }
116
+ }
117
+ throw new SdkError("configuration_error", "spanIdFactory must return a non-zero 16-character hex span id");
118
+ }
119
+
120
+ /**
121
+ * @param {ChildSpanOptions} [options]
122
+ * @returns {string}
123
+ */
124
+ function resolveLogBrewChildSpanId(options = {}) {
125
+ const explicitSpanId = options.spanId;
126
+ if (explicitSpanId !== undefined) {
127
+ return requireSpanId("spanId", explicitSpanId).toLowerCase();
128
+ }
129
+ const { spanIdFactory } = options;
130
+ /** @type {unknown} */
131
+ const spanId = typeof spanIdFactory === "function"
132
+ ? Reflect.apply(spanIdFactory, undefined, [])
133
+ : defaultSpanIdFactory();
134
+ return requireSpanId("spanId", spanId).toLowerCase();
135
+ }
136
+
137
+ /**
138
+ * @param {unknown} method
139
+ * @param {unknown} thisArg
140
+ * @returns {{ value: unknown } | undefined}
141
+ */
142
+ function callWithoutArguments(method, thisArg) {
143
+ if (typeof method !== "function") {
144
+ return undefined;
145
+ }
146
+ try {
147
+ return { value: Reflect.apply(method, thisArg, []) };
148
+ } catch {
149
+ return undefined;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @param {unknown} spanContext
155
+ * @param {ChildSpanOptions} [options]
156
+ * @returns {LogCorrelationTraceContext | null}
157
+ */
158
+ function logbrewTraceContextFromOpenTelemetrySpanContext(spanContext, options = {}) {
159
+ const context = normalizeOpenTelemetrySpanContextIds(spanContext);
160
+ if (context === undefined) {
161
+ return NULL_VALUE;
162
+ }
163
+ return {
164
+ traceId: context.traceId,
165
+
166
+ spanId: resolveLogBrewChildSpanId(options),
167
+
168
+ parentSpanId: context.spanId,
169
+ sampled: context.sampled
170
+ };
171
+ }
172
+
173
+ /**
174
+ * @param {unknown} span
175
+ * @param {ChildSpanOptions} [options]
176
+ * @returns {LogCorrelationTraceContext | null}
177
+ */
178
+ function logbrewTraceContextFromOpenTelemetrySpan(span, options = {}) {
179
+ if (!isObjectValue(span)) {
180
+ return NULL_VALUE;
181
+ }
182
+ const getSpanContext = typeof span["spanContext"] === "function"
183
+ ? span["spanContext"]
184
+ : span["getSpanContext"];
185
+ const spanContext = callWithoutArguments(getSpanContext, span);
186
+ if (spanContext === undefined) {
187
+ return NULL_VALUE;
188
+ }
189
+ return logbrewTraceContextFromOpenTelemetrySpanContext(spanContext.value, options);
190
+ }
191
+
192
+ /**
193
+ * @returns {((id: string) => unknown) | undefined}
194
+ */
195
+ function optionalRequire() {
196
+ /** @type {unknown} */
197
+ const moduleRequire = typeof module === "undefined" ? undefined : Reflect.get(module, "require");
198
+ if (typeof moduleRequire === "function") {
199
+ return (id) => {
200
+ /** @type {unknown} */
201
+ const required = Reflect.apply(moduleRequire, module, [id]);
202
+ return required;
203
+ };
204
+ }
205
+ return typeof require === "function" ? require : undefined;
206
+ }
207
+
208
+ /**
209
+ * @returns {unknown}
210
+ */
211
+ function optionalOpenTelemetryApi() {
212
+ const requireOptional = optionalRequire();
213
+ if (requireOptional === undefined) {
214
+ return undefined;
215
+ }
216
+ try {
217
+ return requireOptional(OPENTELEMETRY_API_PACKAGE);
218
+ } catch {
219
+ return undefined;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * @param {ChildSpanOptions} [options]
225
+ * @returns {LogCorrelationTraceContext | null}
226
+ */
227
+ function logbrewTraceContextFromCurrentOpenTelemetrySpan(options = {}) {
228
+ const openTelemetryApi = optionalProperty(options, "openTelemetryApi") ?? optionalOpenTelemetryApi();
229
+ const trace = optionalProperty(openTelemetryApi, "trace");
230
+ const getActiveSpan = optionalProperty(trace, "getActiveSpan");
231
+ const activeSpan = callWithoutArguments(getActiveSpan, trace);
232
+ if (activeSpan === undefined) {
233
+ return NULL_VALUE;
234
+ }
235
+ return logbrewTraceContextFromOpenTelemetrySpan(activeSpan.value, options);
236
+ }
237
+
238
+ module.exports = {
239
+ defaultSpanIdFactory,
240
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
241
+ logbrewTraceContextFromOpenTelemetrySpan,
242
+ logbrewTraceContextFromOpenTelemetrySpanContext,
243
+ normalizeOpenTelemetrySpanContextIds,
244
+ normalizeSpanId
245
+ };
@@ -0,0 +1,69 @@
1
+ const { isList, isRecord, optionalProperty } = require("./validation.cjs");
2
+
3
+ /**
4
+ * @import { Metadata } from "./index.cjs"
5
+ */
6
+
7
+ const MAX_EXCEPTION_TYPES = 8;
8
+ const NO_EXCEPTIONS = 0;
9
+ const EXCEPTION_TYPE_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$:.]{0,127}$/u;
10
+
11
+ /**
12
+ * @param {unknown} value
13
+ * @returns {string | undefined}
14
+ */
15
+ function safeOpenTelemetryExceptionType(value) {
16
+ return typeof value === "string" && EXCEPTION_TYPE_PATTERN.test(value) ? value : undefined;
17
+ }
18
+
19
+ class OpenTelemetryExceptionSummary {
20
+ count = NO_EXCEPTIONS;
21
+ escapedCount = NO_EXCEPTIONS;
22
+ /** @type {string[]} */
23
+ types = [];
24
+
25
+ /**
26
+ * @param {Readonly<Record<string, unknown>>} event
27
+ * @returns {void}
28
+ */
29
+ add(event) {
30
+ this.count += 1;
31
+ if (optionalProperty(event["attributes"], "exception.escaped") === true) {
32
+ this.escapedCount += 1;
33
+ }
34
+ const type = safeOpenTelemetryExceptionType(optionalProperty(event["attributes"], "exception.type"));
35
+ if (type !== undefined && !this.types.includes(type) && this.types.length < MAX_EXCEPTION_TYPES) {
36
+ this.types.push(type);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * @returns {Metadata}
42
+ */
43
+ metadata() {
44
+ return {
45
+ "otel.exception_event_count": this.count,
46
+ ...(this.escapedCount > NO_EXCEPTIONS ? { "otel.exception_escaped_count": this.escapedCount } : {}),
47
+ ...(this.types.length > NO_EXCEPTIONS ? { "otel.exception_types": this.types.join(",") } : {})
48
+ };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @param {unknown} events
54
+ * @returns {OpenTelemetryExceptionSummary | undefined}
55
+ */
56
+ function openTelemetryExceptionEventSummary(events) {
57
+ if (!isList(events) || events.length === NO_EXCEPTIONS) {
58
+ return undefined;
59
+ }
60
+ const summary = new OpenTelemetryExceptionSummary();
61
+ for (const event of events) {
62
+ if (isRecord(event) && event["name"] === "exception") {
63
+ summary.add(event);
64
+ }
65
+ }
66
+ return summary.count > NO_EXCEPTIONS ? summary : undefined;
67
+ }
68
+
69
+ module.exports = { openTelemetryExceptionEventSummary, safeOpenTelemetryExceptionType };