@logbrew/sdk 0.1.3 → 0.1.4

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.
@@ -0,0 +1,1080 @@
1
+ const DEFAULT_OTEL_SPAN_ATTRIBUTE_KEYS = new Set([
2
+ "db.operation.name",
3
+ "db.system",
4
+ "faas.trigger",
5
+ "graphql.operation.name",
6
+ "graphql.operation.type",
7
+ "http.method",
8
+ "http.request.method",
9
+ "http.response.status_code",
10
+ "http.route",
11
+ "http.status_code",
12
+ "messaging.operation.name",
13
+ "messaging.system",
14
+ "rpc.method",
15
+ "rpc.service",
16
+ "rpc.system"
17
+ ]);
18
+ const DEFAULT_OTEL_RESOURCE_ATTRIBUTE_KEYS = new Set([
19
+ "deployment.environment",
20
+ "deployment.environment.name",
21
+ "service.name",
22
+ "service.version",
23
+ "telemetry.sdk.language",
24
+ "telemetry.sdk.name",
25
+ "telemetry.sdk.version"
26
+ ]);
27
+ const DEFAULT_OTEL_EVENT_ATTRIBUTE_KEYS = new Set([
28
+ "exception.escaped",
29
+ "exception.type"
30
+ ]);
31
+ const DEFAULT_OTEL_LINK_ATTRIBUTE_KEYS = new Set();
32
+ const TRACE_SUMMARY_METADATA_KEYS = new Set([
33
+ ...DEFAULT_OTEL_RESOURCE_ATTRIBUTE_KEYS,
34
+ "db.operation.name",
35
+ "db.system",
36
+ "faas.trigger",
37
+ "graphql.operation.name",
38
+ "graphql.operation.type",
39
+ "http.method",
40
+ "http.request.method",
41
+ "http.response.status_code",
42
+ "http.route",
43
+ "http.status_code",
44
+ "messaging.operation.name",
45
+ "messaging.system",
46
+ "rpc.method",
47
+ "rpc.service",
48
+ "rpc.system"
49
+ ]);
50
+ const OTEL_SPAN_KIND_NAMES = new Map([
51
+ [0, "internal"],
52
+ [1, "server"],
53
+ [2, "client"],
54
+ [3, "producer"],
55
+ [4, "consumer"]
56
+ ]);
57
+ const OTEL_STATUS_CODE_ERROR = 2;
58
+ const OTEL_STATUS_CODE_OK = 1;
59
+ const OTEL_EXPORT_RESULT_SUCCESS = 0;
60
+ const OTEL_EXPORT_RESULT_FAILED = 1;
61
+ const ZERO_SPAN_ID = "0000000000000000";
62
+ const SENSITIVE_OTEL_ATTRIBUTE_KEYS = new Set([
63
+ "code.stacktrace",
64
+ "db.statement",
65
+ "exception.message",
66
+ "exception.stacktrace",
67
+ "http.request.body",
68
+ "http.response.body",
69
+ "http.url",
70
+ "url.full"
71
+ ]);
72
+ const SENSITIVE_OTEL_ATTRIBUTE_PREFIXES = [
73
+ "http.request.header.",
74
+ "http.response.header."
75
+ ];
76
+ const SENSITIVE_OTEL_ATTRIBUTE_PATTERN = /(^|[._-])(authorization|body|cookie|credential|fragment|header|headers|payload|password|passwd|private[-_]?key|query|secret|stack|stacktrace|token)([._-]|$)/iu;
77
+
78
+ function buildOpenTelemetryHelpers({
79
+ compactMetadata,
80
+ isMetadataValue,
81
+ LogBrewClient,
82
+ maxSpanEvents,
83
+ maxSpanLinks,
84
+ requireNonEmpty,
85
+ requireSpanId,
86
+ requireTraceId,
87
+ SdkError,
88
+ stringOrUndefined
89
+ }) {
90
+ function logbrewTraceContextFromOpenTelemetrySpanContext(spanContext, options = {}) {
91
+ const context = normalizeOpenTelemetrySpanContext(spanContext);
92
+ if (!context) {
93
+ return null;
94
+ }
95
+ return {
96
+ traceId: context.traceId,
97
+ spanId: resolveLogBrewChildSpanId(options),
98
+ parentSpanId: context.parentSpanId,
99
+ sampled: context.sampled
100
+ };
101
+ }
102
+
103
+ function logbrewTraceContextFromOpenTelemetrySpan(span, options = {}) {
104
+ if (!span || typeof span !== "object") {
105
+ return null;
106
+ }
107
+ const getSpanContext = typeof span.spanContext === "function"
108
+ ? span.spanContext
109
+ : span.getSpanContext;
110
+ if (typeof getSpanContext !== "function") {
111
+ return null;
112
+ }
113
+ let spanContext;
114
+ try {
115
+ spanContext = getSpanContext.call(span);
116
+ } catch {
117
+ return null;
118
+ }
119
+ return logbrewTraceContextFromOpenTelemetrySpanContext(spanContext, options);
120
+ }
121
+
122
+ function logbrewTraceContextFromCurrentOpenTelemetrySpan(options = {}) {
123
+ const openTelemetryApi = options?.openTelemetryApi ?? optionalOpenTelemetryApi();
124
+ const getActiveSpan = openTelemetryApi?.trace?.getActiveSpan;
125
+ if (typeof getActiveSpan !== "function") {
126
+ return null;
127
+ }
128
+ let activeSpan;
129
+ try {
130
+ activeSpan = getActiveSpan.call(openTelemetryApi.trace);
131
+ } catch {
132
+ return null;
133
+ }
134
+ return logbrewTraceContextFromOpenTelemetrySpan(activeSpan, options);
135
+ }
136
+
137
+ function spanAttributesFromOpenTelemetryReadableSpan(span, options = {}) {
138
+ return spanAttributesFromResolvedOpenTelemetryReadableSpan(
139
+ span,
140
+ resolveOpenTelemetryReadableSpanOptions(options)
141
+ );
142
+ }
143
+
144
+ function createLogBrewOpenTelemetrySpanProcessor(config) {
145
+ if (!config || Array.isArray(config) || typeof config !== "object") {
146
+ throw new SdkError("validation_error", "OpenTelemetry span processor config must be an object");
147
+ }
148
+ const client = config.client;
149
+ if (!(client instanceof LogBrewClient)) {
150
+ throw new SdkError("validation_error", "OpenTelemetry span processor client must be a LogBrewClient");
151
+ }
152
+ const eventIdPrefix = config.eventIdPrefix ?? "otel";
153
+ requireNonEmpty("OpenTelemetry eventIdPrefix", eventIdPrefix);
154
+ const transport = config.transport;
155
+ const timestamp = typeof config.timestamp === "function"
156
+ ? config.timestamp
157
+ : undefined;
158
+ const onError = typeof config.onError === "function" ? config.onError : () => {};
159
+ const spanFilter = typeof config.spanFilter === "function" ? config.spanFilter : null;
160
+ const flushOnForceFlush = config.flushOnForceFlush !== false;
161
+ const includeTraceSummary = config.includeTraceSummary === true;
162
+ const resolvedOptions = resolveOpenTelemetryReadableSpanOptions(config);
163
+ const state = {
164
+ captured: 0,
165
+ closed: false,
166
+ flushInFlight: false,
167
+ pendingFlush: Promise.resolve(null),
168
+ queuedFlush: null,
169
+ traceSummaryCount: 0,
170
+ traceSummaries: includeTraceSummary ? new Map() : null
171
+ };
172
+
173
+ return {
174
+ onStart() {},
175
+ onEnd(span) {
176
+ if (state.closed) {
177
+ return;
178
+ }
179
+ try {
180
+ enqueueOpenTelemetryReadableSpan({
181
+ client,
182
+ eventIdPrefix,
183
+ resolvedOptions,
184
+ span,
185
+ spanFilter,
186
+ state,
187
+ timestamp
188
+ });
189
+ } catch (error) {
190
+ onError(error);
191
+ }
192
+ },
193
+ async forceFlush() {
194
+ await flushOpenTelemetryProcessorQueue({
195
+ client,
196
+ eventIdPrefix,
197
+ flushOnForceFlush,
198
+ includeTraceSummary,
199
+ onError,
200
+ state,
201
+ timestamp,
202
+ transport
203
+ });
204
+ },
205
+ async shutdown() {
206
+ state.closed = true;
207
+ await flushOpenTelemetryProcessorQueue({
208
+ client,
209
+ eventIdPrefix,
210
+ flushOnForceFlush,
211
+ includeTraceSummary,
212
+ onError,
213
+ state,
214
+ timestamp,
215
+ transport
216
+ });
217
+ }
218
+ };
219
+ }
220
+
221
+ function createLogBrewOpenTelemetrySpanExporter(config) {
222
+ if (!config || Array.isArray(config) || typeof config !== "object") {
223
+ throw new SdkError("validation_error", "OpenTelemetry span exporter config must be an object");
224
+ }
225
+ const client = config.client;
226
+ if (!(client instanceof LogBrewClient)) {
227
+ throw new SdkError("validation_error", "OpenTelemetry span exporter client must be a LogBrewClient");
228
+ }
229
+ const eventIdPrefix = config.eventIdPrefix ?? "otel";
230
+ requireNonEmpty("OpenTelemetry eventIdPrefix", eventIdPrefix);
231
+ const transport = config.transport;
232
+ const timestamp = typeof config.timestamp === "function"
233
+ ? config.timestamp
234
+ : undefined;
235
+ const onError = typeof config.onError === "function" ? config.onError : () => {};
236
+ const spanFilter = typeof config.spanFilter === "function" ? config.spanFilter : null;
237
+ const flushOnExport = config.flushOnExport !== false;
238
+ const includeTraceSummary = config.includeTraceSummary === true;
239
+ const resolvedOptions = resolveOpenTelemetryReadableSpanOptions(config);
240
+ const state = {
241
+ captured: 0,
242
+ closed: false,
243
+ flushInFlight: false,
244
+ pendingFlush: Promise.resolve(null),
245
+ queuedFlush: null,
246
+ traceSummaryCount: 0,
247
+ traceSummaries: includeTraceSummary ? new Map() : null
248
+ };
249
+
250
+ return {
251
+ export(spans, resultCallback) {
252
+ const callback = typeof resultCallback === "function" ? resultCallback : () => {};
253
+ if (state.closed) {
254
+ const error = new SdkError("shutdown_error", "OpenTelemetry span exporter is already shut down");
255
+ callback(openTelemetryExportFailure(error));
256
+ return;
257
+ }
258
+ if (!Array.isArray(spans)) {
259
+ const error = new SdkError("validation_error", "OpenTelemetry span exporter spans must be an array");
260
+ onError(error);
261
+ callback(openTelemetryExportFailure(error));
262
+ return;
263
+ }
264
+ try {
265
+ for (const span of spans) {
266
+ enqueueOpenTelemetryReadableSpan({
267
+ client,
268
+ eventIdPrefix,
269
+ resolvedOptions,
270
+ span,
271
+ spanFilter,
272
+ state,
273
+ timestamp
274
+ });
275
+ }
276
+ } catch (error) {
277
+ onError(error);
278
+ callback(openTelemetryExportFailure(error));
279
+ return;
280
+ }
281
+ flushOpenTelemetryExporterQueue({
282
+ client,
283
+ eventIdPrefix,
284
+ flushOnExport,
285
+ includeTraceSummary,
286
+ onError,
287
+ state,
288
+ timestamp,
289
+ transport
290
+ }).then(
291
+ () => callback({ code: OTEL_EXPORT_RESULT_SUCCESS }),
292
+ (error) => {
293
+ onError(error);
294
+ callback(openTelemetryExportFailure(error));
295
+ }
296
+ );
297
+ },
298
+ async forceFlush() {
299
+ await flushOpenTelemetryExporterQueue({
300
+ client,
301
+ eventIdPrefix,
302
+ flushOnExport: true,
303
+ includeTraceSummary,
304
+ onError,
305
+ state,
306
+ timestamp,
307
+ transport
308
+ });
309
+ },
310
+ async shutdown() {
311
+ state.closed = true;
312
+ await flushOpenTelemetryExporterQueue({
313
+ client,
314
+ eventIdPrefix,
315
+ flushOnExport: true,
316
+ includeTraceSummary,
317
+ onError,
318
+ state,
319
+ timestamp,
320
+ transport
321
+ });
322
+ }
323
+ };
324
+ }
325
+
326
+ function enqueueOpenTelemetryReadableSpan({
327
+ client,
328
+ eventIdPrefix,
329
+ resolvedOptions,
330
+ span,
331
+ spanFilter,
332
+ state,
333
+ timestamp
334
+ }) {
335
+ if (spanFilter && spanFilter(span) === false) {
336
+ return;
337
+ }
338
+ const attributes = spanAttributesFromResolvedOpenTelemetryReadableSpan(span, resolvedOptions);
339
+ if (!attributes) {
340
+ return;
341
+ }
342
+ recordOpenTelemetryTraceSummary(state, attributes, span);
343
+ state.captured += 1;
344
+ client.span(
345
+ `${eventIdPrefix}_${state.captured}`,
346
+ timestampFromOpenTelemetryReadableSpan(span, timestamp),
347
+ attributes
348
+ );
349
+ }
350
+
351
+ function spanAttributesFromResolvedOpenTelemetryReadableSpan(span, options) {
352
+ if (!span || Array.isArray(span) || typeof span !== "object") {
353
+ return null;
354
+ }
355
+ const context = normalizeOpenTelemetryReadableSpanContext(span);
356
+ if (!context) {
357
+ return null;
358
+ }
359
+ if (!options.captureUnsampled && context.sampled === false) {
360
+ return null;
361
+ }
362
+
363
+ const metadata = openTelemetryReadableSpanMetadata(span, options);
364
+ const events = options.includeSpanEvents
365
+ ? openTelemetryReadableSpanEvents(span.events, options)
366
+ : undefined;
367
+ const links = options.includeSpanLinks
368
+ ? openTelemetryReadableSpanLinks(span.links, options)
369
+ : undefined;
370
+ const exceptionSummary = openTelemetryExceptionEventSummary(span.events);
371
+ const exceptionMetadata = openTelemetryExceptionMetadata(exceptionSummary);
372
+ const durationMs = durationMsFromOpenTelemetryReadableSpan(span);
373
+ const resolvedMetadata = {
374
+ ...metadata,
375
+ ...exceptionMetadata
376
+ };
377
+
378
+ return {
379
+ name: openTelemetrySpanName(span),
380
+ traceId: context.traceId,
381
+ spanId: context.spanId,
382
+ ...(context.parentSpanId !== undefined ? { parentSpanId: context.parentSpanId } : {}),
383
+ status: openTelemetrySpanStatus(span.status, exceptionSummary),
384
+ ...(durationMs !== undefined ? { durationMs } : {}),
385
+ ...(events !== undefined ? { events } : {}),
386
+ ...(links !== undefined ? { links } : {}),
387
+ ...(Object.keys(resolvedMetadata).length > 0 ? { metadata: resolvedMetadata } : {})
388
+ };
389
+ }
390
+
391
+ function resolveOpenTelemetryReadableSpanOptions(options = {}) {
392
+ return {
393
+ attributeKeys: openTelemetryAttributeKeySet(
394
+ DEFAULT_OTEL_SPAN_ATTRIBUTE_KEYS,
395
+ options.attributeKeys,
396
+ "OpenTelemetry attributeKeys"
397
+ ),
398
+ captureUnsampled: options.captureUnsampled === true,
399
+ eventAttributeKeys: openTelemetryAttributeKeySet(
400
+ DEFAULT_OTEL_EVENT_ATTRIBUTE_KEYS,
401
+ options.eventAttributeKeys,
402
+ "OpenTelemetry eventAttributeKeys"
403
+ ),
404
+ includeSpanEvents: options.includeSpanEvents !== false,
405
+ includeSpanLinks: options.includeSpanLinks !== false,
406
+ linkAttributeKeys: openTelemetryAttributeKeySet(
407
+ DEFAULT_OTEL_LINK_ATTRIBUTE_KEYS,
408
+ options.linkAttributeKeys,
409
+ "OpenTelemetry linkAttributeKeys"
410
+ ),
411
+ metadata: compactMetadata(options.metadata),
412
+ resourceAttributeKeys: openTelemetryAttributeKeySet(
413
+ DEFAULT_OTEL_RESOURCE_ATTRIBUTE_KEYS,
414
+ options.resourceAttributeKeys,
415
+ "OpenTelemetry resourceAttributeKeys"
416
+ )
417
+ };
418
+ }
419
+
420
+ function openTelemetryAttributeKeySet(defaultKeys, extraKeys, label) {
421
+ const allowedKeys = new Set(defaultKeys);
422
+ if (extraKeys === undefined) {
423
+ return allowedKeys;
424
+ }
425
+ if (!Array.isArray(extraKeys)) {
426
+ throw new SdkError("validation_error", `${label} must be an array`);
427
+ }
428
+ for (const key of extraKeys) {
429
+ requireNonEmpty(label, key);
430
+ if (isSensitiveOpenTelemetryAttributeKey(key)) {
431
+ throw new SdkError("validation_error", `${label} cannot include sensitive key: ${key}`);
432
+ }
433
+ allowedKeys.add(key);
434
+ }
435
+ return allowedKeys;
436
+ }
437
+
438
+ function normalizeOpenTelemetrySpanContext(spanContext) {
439
+ const context = normalizeOpenTelemetrySpanContextIds(spanContext);
440
+ if (!context) {
441
+ return null;
442
+ }
443
+ return {
444
+ traceId: context.traceId,
445
+ parentSpanId: context.spanId,
446
+ sampled: context.sampled
447
+ };
448
+ }
449
+
450
+ function normalizeOpenTelemetryReadableSpanContext(span) {
451
+ const context = normalizeOpenTelemetrySpanContextIds(readOpenTelemetrySpanContext(span));
452
+ if (!context) {
453
+ return null;
454
+ }
455
+ const parentContext = normalizeOpenTelemetrySpanContextIds(span.parentSpanContext);
456
+ const parentSpanId = parentContext?.traceId === context.traceId
457
+ ? parentContext.spanId
458
+ : normalizeSpanId(span.parentSpanId);
459
+ return {
460
+ traceId: context.traceId,
461
+ spanId: context.spanId,
462
+ ...(parentSpanId !== undefined ? { parentSpanId } : {}),
463
+ sampled: context.sampled
464
+ };
465
+ }
466
+
467
+ function normalizeOpenTelemetrySpanContextIds(spanContext) {
468
+ if (!spanContext || typeof spanContext !== "object" || spanContext.isValid === false) {
469
+ return null;
470
+ }
471
+ const traceId = normalizeTraceId(spanContext.traceId);
472
+ const spanId = normalizeSpanId(spanContext.spanId);
473
+ if (!traceId || !spanId) {
474
+ return null;
475
+ }
476
+ return {
477
+ traceId,
478
+ spanId,
479
+ sampled: openTelemetryTraceFlagsSampled(spanContext.traceFlags)
480
+ };
481
+ }
482
+
483
+ function normalizeTraceId(traceId) {
484
+ try {
485
+ requireTraceId(traceId);
486
+ } catch {
487
+ return undefined;
488
+ }
489
+ return traceId.toLowerCase();
490
+ }
491
+
492
+ function normalizeSpanId(spanId) {
493
+ try {
494
+ requireSpanId("trace spanId", spanId);
495
+ } catch {
496
+ return undefined;
497
+ }
498
+ return spanId.toLowerCase();
499
+ }
500
+
501
+ function resolveLogBrewChildSpanId(options = {}) {
502
+ if (options.spanId !== undefined) {
503
+ requireSpanId("spanId", options.spanId);
504
+ return options.spanId.toLowerCase();
505
+ }
506
+ const spanIdFactory = typeof options.spanIdFactory === "function"
507
+ ? options.spanIdFactory
508
+ : defaultSpanIdFactory;
509
+ const spanId = spanIdFactory();
510
+ requireSpanId("spanId", spanId);
511
+ return spanId.toLowerCase();
512
+ }
513
+
514
+ function defaultSpanIdFactory() {
515
+ for (let attempt = 0; attempt < 3; attempt += 1) {
516
+ const spanId = randomHex(8);
517
+ if (spanId !== ZERO_SPAN_ID) {
518
+ return spanId;
519
+ }
520
+ }
521
+ throw new SdkError("configuration_error", "spanIdFactory must return a non-zero 16-character hex span id");
522
+ }
523
+
524
+ function readOpenTelemetrySpanContext(span) {
525
+ if (typeof span.spanContext !== "function") {
526
+ return null;
527
+ }
528
+ try {
529
+ return span.spanContext();
530
+ } catch {
531
+ return null;
532
+ }
533
+ }
534
+
535
+ function openTelemetryTraceFlagsSampled(traceFlags) {
536
+ const sampled = traceFlags?.sampled;
537
+ if (typeof sampled === "boolean") {
538
+ return sampled;
539
+ }
540
+ if (typeof traceFlags === "number" && Number.isFinite(traceFlags)) {
541
+ return (traceFlags & 1) === 1;
542
+ }
543
+ return false;
544
+ }
545
+
546
+ function randomHex(byteLength) {
547
+ const bytes = new Uint8Array(byteLength);
548
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
549
+ globalThis.crypto.getRandomValues(bytes);
550
+ } else {
551
+ for (let index = 0; index < bytes.length; index += 1) {
552
+ bytes[index] = Math.floor(Math.random() * 256);
553
+ }
554
+ }
555
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
556
+ }
557
+
558
+ function openTelemetrySpanName(span) {
559
+ return typeof span.name === "string" && span.name.trim() !== ""
560
+ ? span.name
561
+ : "opentelemetry.span";
562
+ }
563
+
564
+ function openTelemetrySpanStatus(status, exceptionSummary = null) {
565
+ if (status?.code === OTEL_STATUS_CODE_ERROR || status?.code === "ERROR") {
566
+ return "error";
567
+ }
568
+ if (status?.code === OTEL_STATUS_CODE_OK || status?.code === "OK") {
569
+ return "ok";
570
+ }
571
+ if (exceptionSummary?.escapedCount > 0) {
572
+ return "error";
573
+ }
574
+ return "ok";
575
+ }
576
+
577
+ function openTelemetryReadableSpanMetadata(span, options) {
578
+ const metadata = {
579
+ source: "opentelemetry.readable_span",
580
+ ...options.metadata,
581
+ ...openTelemetrySelectedMetadata(span.resource?.attributes, options.resourceAttributeKeys)
582
+ };
583
+ const kind = openTelemetrySpanKindName(span.kind);
584
+ if (kind) {
585
+ metadata["otel.kind"] = kind;
586
+ }
587
+ const scopeName = stringOrUndefined(span.instrumentationScope?.name);
588
+ if (scopeName) {
589
+ metadata["otel.scope.name"] = scopeName;
590
+ }
591
+ const scopeVersion = stringOrUndefined(span.instrumentationScope?.version);
592
+ if (scopeVersion) {
593
+ metadata["otel.scope.version"] = scopeVersion;
594
+ }
595
+ addPositiveOpenTelemetryCount(metadata, "otel.dropped_attributes_count", span.droppedAttributesCount);
596
+ addPositiveOpenTelemetryCount(metadata, "otel.dropped_events_count", span.droppedEventsCount);
597
+ addPositiveOpenTelemetryCount(metadata, "otel.dropped_links_count", span.droppedLinksCount);
598
+ return {
599
+ ...metadata,
600
+ ...openTelemetrySelectedMetadata(span.attributes, options.attributeKeys)
601
+ };
602
+ }
603
+
604
+ function openTelemetrySelectedMetadata(attributes, allowedKeys) {
605
+ const metadata = {};
606
+ if (!attributes || Array.isArray(attributes) || typeof attributes !== "object") {
607
+ return metadata;
608
+ }
609
+ for (const [key, value] of Object.entries(attributes)) {
610
+ if (allowedKeys.has(key) && !isSensitiveOpenTelemetryAttributeKey(key) && isMetadataValue(value)) {
611
+ metadata[key] = value;
612
+ }
613
+ }
614
+ return metadata;
615
+ }
616
+
617
+ function openTelemetryReadableSpanEvents(events, options) {
618
+ if (!Array.isArray(events) || events.length === 0) {
619
+ return undefined;
620
+ }
621
+ const summaries = [];
622
+ for (const event of events.slice(0, maxSpanEvents)) {
623
+ if (!event || Array.isArray(event) || typeof event !== "object") {
624
+ continue;
625
+ }
626
+ const name = typeof event.name === "string" && event.name.trim() !== ""
627
+ ? event.name
628
+ : "opentelemetry.event";
629
+ const timestamp = timestampFromOpenTelemetryTime(event.time ?? event.timestamp);
630
+ const metadata = openTelemetrySelectedMetadata(event.attributes, options.eventAttributeKeys);
631
+ summaries.push({
632
+ name,
633
+ ...(timestamp !== undefined ? { timestamp } : {}),
634
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {})
635
+ });
636
+ }
637
+ return summaries.length > 0 ? summaries : undefined;
638
+ }
639
+
640
+ function openTelemetryReadableSpanLinks(links, options) {
641
+ if (!Array.isArray(links) || links.length === 0) {
642
+ return undefined;
643
+ }
644
+ const summaries = [];
645
+ for (const link of links.slice(0, maxSpanLinks)) {
646
+ const context = normalizeOpenTelemetrySpanContextIds(link?.context ?? link?.spanContext);
647
+ if (!context) {
648
+ continue;
649
+ }
650
+ const metadata = openTelemetrySelectedMetadata(link.attributes, options.linkAttributeKeys);
651
+ summaries.push({
652
+ traceId: context.traceId,
653
+ spanId: context.spanId,
654
+ sampled: context.sampled,
655
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {})
656
+ });
657
+ }
658
+ return summaries.length > 0 ? summaries : undefined;
659
+ }
660
+
661
+ function openTelemetryExceptionEventSummary(events) {
662
+ if (!Array.isArray(events) || events.length === 0) {
663
+ return null;
664
+ }
665
+ const types = [];
666
+ let count = 0;
667
+ let escapedCount = 0;
668
+ for (const event of events) {
669
+ if (!event || Array.isArray(event) || typeof event !== "object" || event.name !== "exception") {
670
+ continue;
671
+ }
672
+ count += 1;
673
+ if (event.attributes?.["exception.escaped"] === true) {
674
+ escapedCount += 1;
675
+ }
676
+ const type = safeOpenTelemetryExceptionType(event.attributes?.["exception.type"]);
677
+ if (type && !types.includes(type) && types.length < maxSpanEvents) {
678
+ types.push(type);
679
+ }
680
+ }
681
+ return count > 0 ? { count, escapedCount, types } : null;
682
+ }
683
+
684
+ function openTelemetryExceptionMetadata(summary) {
685
+ if (!summary || summary.count === 0) {
686
+ return {};
687
+ }
688
+ return {
689
+ "otel.exception_event_count": summary.count,
690
+ ...(summary.escapedCount > 0 ? { "otel.exception_escaped_count": summary.escapedCount } : {}),
691
+ ...(summary.types.length > 0 ? { "otel.exception_types": summary.types.join(",") } : {})
692
+ };
693
+ }
694
+
695
+ function safeOpenTelemetryExceptionType(value) {
696
+ return typeof value === "string" && /^[A-Za-z_$][A-Za-z0-9_$:.]{0,127}$/u.test(value) ? value : undefined;
697
+ }
698
+
699
+ function recordOpenTelemetryTraceSummary(state, attributes, span) {
700
+ if (!(state.traceSummaries instanceof Map)) {
701
+ return;
702
+ }
703
+ let summary = state.traceSummaries.get(attributes.traceId);
704
+ if (!summary) {
705
+ summary = {
706
+ traceId: attributes.traceId,
707
+ spanCount: 0,
708
+ errorSpanCount: 0,
709
+ metadata: {},
710
+ rootSeen: false
711
+ };
712
+ state.traceSummaries.set(attributes.traceId, summary);
713
+ }
714
+
715
+ summary.spanCount += 1;
716
+ if (attributes.status === "error") {
717
+ summary.errorSpanCount += 1;
718
+ }
719
+ recordOpenTelemetryTraceSummaryExceptions(summary, attributes.metadata);
720
+
721
+ const startMs = openTelemetryTimeMs(span.startTime);
722
+ const durationMs = attributes.durationMs;
723
+ const endMs = endMsFromOpenTelemetryReadableSpan(span, startMs, durationMs);
724
+ if (startMs !== undefined && (summary.firstStartMs === undefined || startMs < summary.firstStartMs)) {
725
+ summary.firstStartMs = startMs;
726
+ }
727
+ if (endMs !== undefined && (summary.lastEndMs === undefined || endMs > summary.lastEndMs)) {
728
+ summary.lastEndMs = endMs;
729
+ }
730
+
731
+ copyOpenTelemetryTraceSummaryMetadata(summary, attributes.metadata);
732
+
733
+ const isRootSpan = attributes.parentSpanId === undefined;
734
+ if (isRootSpan || !summary.rootSpanId) {
735
+ summary.rootSpanId = attributes.spanId;
736
+ summary.rootName = attributes.name;
737
+ summary.rootKind = attributes.metadata?.["otel.kind"];
738
+ summary.rootSeen = isRootSpan;
739
+ if (startMs !== undefined) {
740
+ summary.rootStartMs = startMs;
741
+ }
742
+ if (durationMs !== undefined) {
743
+ summary.rootDurationMs = durationMs;
744
+ }
745
+ copyOpenTelemetryTraceSummaryMetadata(summary, attributes.metadata, { overwrite: true });
746
+ }
747
+ }
748
+
749
+ function recordOpenTelemetryTraceSummaryExceptions(summary, metadata) {
750
+ if (!metadata || Array.isArray(metadata) || typeof metadata !== "object") {
751
+ return;
752
+ }
753
+ if (Number.isSafeInteger(metadata["otel.exception_event_count"]) && metadata["otel.exception_event_count"] > 0) {
754
+ summary.exceptionEventCount = (summary.exceptionEventCount ?? 0) + metadata["otel.exception_event_count"];
755
+ }
756
+ if (Number.isSafeInteger(metadata["otel.exception_escaped_count"]) && metadata["otel.exception_escaped_count"] > 0) {
757
+ summary.exceptionEscapedCount = (summary.exceptionEscapedCount ?? 0) + metadata["otel.exception_escaped_count"];
758
+ }
759
+ if (typeof metadata["otel.exception_types"] === "string" && metadata["otel.exception_types"].trim() !== "") {
760
+ const types = summary.exceptionTypes ?? new Set();
761
+ for (const type of metadata["otel.exception_types"].split(",")) {
762
+ const safeType = safeOpenTelemetryExceptionType(type);
763
+ if (safeType) {
764
+ types.add(safeType);
765
+ }
766
+ }
767
+ summary.exceptionTypes = types;
768
+ }
769
+ }
770
+
771
+ function copyOpenTelemetryTraceSummaryMetadata(summary, metadata, options = {}) {
772
+ if (!metadata || Array.isArray(metadata) || typeof metadata !== "object") {
773
+ return;
774
+ }
775
+ for (const [key, value] of Object.entries(metadata)) {
776
+ if (
777
+ TRACE_SUMMARY_METADATA_KEYS.has(key) &&
778
+ (options.overwrite === true || summary.metadata[key] === undefined) &&
779
+ isMetadataValue(value)
780
+ ) {
781
+ summary.metadata[key] = value;
782
+ }
783
+ }
784
+ }
785
+
786
+ function enqueueOpenTelemetryTraceSummaries({ client, eventIdPrefix, onError, state, timestamp }) {
787
+ if (!(state.traceSummaries instanceof Map) || state.traceSummaries.size === 0) {
788
+ return;
789
+ }
790
+ const summaries = Array.from(state.traceSummaries.values());
791
+ state.traceSummaries.clear();
792
+ for (const summary of summaries) {
793
+ try {
794
+ state.traceSummaryCount += 1;
795
+ client.span(
796
+ `${eventIdPrefix}_trace_${state.traceSummaryCount}`,
797
+ timestampFromOpenTelemetryTraceSummary(summary, timestamp),
798
+ openTelemetryTraceSummaryAttributes(summary)
799
+ );
800
+ } catch (error) {
801
+ onError(error);
802
+ }
803
+ }
804
+ }
805
+
806
+ function openTelemetryTraceSummaryAttributes(summary) {
807
+ const metadata = compactMetadata({
808
+ source: "opentelemetry.trace_summary",
809
+ ...summary.metadata,
810
+ "otel.trace.span_count": summary.spanCount,
811
+ ...(summary.errorSpanCount > 0 ? { "otel.trace.error_span_count": summary.errorSpanCount } : {}),
812
+ ...(summary.exceptionEventCount > 0 ? { "otel.trace.exception_event_count": summary.exceptionEventCount } : {}),
813
+ ...(summary.exceptionEscapedCount > 0 ? { "otel.trace.exception_escaped_count": summary.exceptionEscapedCount } : {}),
814
+ ...(summary.exceptionTypes?.size > 0 ? { "otel.trace.exception_types": Array.from(summary.exceptionTypes).join(",") } : {}),
815
+ ...(summary.rootSpanId ? { "otel.trace.root_span_id": summary.rootSpanId } : {}),
816
+ ...(summary.rootName ? { "otel.trace.root_name": summary.rootName } : {}),
817
+ ...(summary.rootKind ? { "otel.trace.root_kind": summary.rootKind } : {}),
818
+ "otel.trace.summary_kind": summary.rootSeen ? "rooted" : "flush_batch"
819
+ });
820
+ const durationMs = durationMsFromOpenTelemetryTraceSummary(summary);
821
+ return {
822
+ name: summary.rootName ? `opentelemetry.trace:${summary.rootName}` : "opentelemetry.trace",
823
+ traceId: summary.traceId,
824
+ spanId: defaultSpanIdFactory(),
825
+ status: summary.errorSpanCount > 0 ? "error" : "ok",
826
+ ...(durationMs !== undefined ? { durationMs } : {}),
827
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {})
828
+ };
829
+ }
830
+
831
+ function durationMsFromOpenTelemetryTraceSummary(summary) {
832
+ if (
833
+ summary.firstStartMs !== undefined &&
834
+ summary.lastEndMs !== undefined &&
835
+ summary.lastEndMs >= summary.firstStartMs
836
+ ) {
837
+ return summary.lastEndMs - summary.firstStartMs;
838
+ }
839
+ return summary.rootDurationMs;
840
+ }
841
+
842
+ function timestampFromOpenTelemetryTraceSummary(summary, fallbackTimestamp) {
843
+ return timestampFromOpenTelemetryTime(summary.rootStartMs)
844
+ ?? timestampFromOpenTelemetryTime(summary.firstStartMs)
845
+ ?? (typeof fallbackTimestamp === "function" ? fallbackTimestamp() : new Date().toISOString());
846
+ }
847
+
848
+ function openTelemetrySpanKindName(kind) {
849
+ if (typeof kind === "number") {
850
+ return OTEL_SPAN_KIND_NAMES.get(kind);
851
+ }
852
+ if (typeof kind === "string" && kind.trim() !== "") {
853
+ return kind.toLowerCase();
854
+ }
855
+ return undefined;
856
+ }
857
+
858
+ function addPositiveOpenTelemetryCount(metadata, key, value) {
859
+ if (Number.isSafeInteger(value) && value > 0) {
860
+ metadata[key] = value;
861
+ }
862
+ }
863
+
864
+ function durationMsFromOpenTelemetryReadableSpan(span) {
865
+ const durationMs = openTelemetryDurationMs(span.duration);
866
+ if (durationMs !== undefined) {
867
+ return durationMs;
868
+ }
869
+ const startMs = openTelemetryTimeMs(span.startTime);
870
+ const endMs = openTelemetryTimeMs(span.endTime);
871
+ if (startMs !== undefined && endMs !== undefined && endMs >= startMs) {
872
+ return endMs - startMs;
873
+ }
874
+ return undefined;
875
+ }
876
+
877
+ function endMsFromOpenTelemetryReadableSpan(span, startMs, durationMs) {
878
+ const endMs = openTelemetryTimeMs(span.endTime);
879
+ if (endMs !== undefined) {
880
+ return endMs;
881
+ }
882
+ if (startMs !== undefined && durationMs !== undefined) {
883
+ return startMs + durationMs;
884
+ }
885
+ return undefined;
886
+ }
887
+
888
+ function timestampFromOpenTelemetryReadableSpan(span, fallbackTimestamp) {
889
+ return timestampFromOpenTelemetryTime(span.startTime)
890
+ ?? (typeof fallbackTimestamp === "function" ? fallbackTimestamp() : new Date().toISOString());
891
+ }
892
+
893
+ function timestampFromOpenTelemetryTime(value) {
894
+ const milliseconds = openTelemetryTimeMs(value);
895
+ if (milliseconds === undefined) {
896
+ return undefined;
897
+ }
898
+ const date = new Date(milliseconds);
899
+ return Number.isNaN(date.valueOf()) ? undefined : date.toISOString();
900
+ }
901
+
902
+ function openTelemetryDurationMs(value) {
903
+ if (!Array.isArray(value) || value.length < 2) {
904
+ return undefined;
905
+ }
906
+ const [seconds, nanos] = value;
907
+ if (!Number.isFinite(seconds) || !Number.isFinite(nanos) || seconds < 0 || nanos < 0) {
908
+ return undefined;
909
+ }
910
+ return seconds * 1000 + nanos / 1_000_000;
911
+ }
912
+
913
+ function openTelemetryTimeMs(value) {
914
+ if (Array.isArray(value) && value.length >= 2) {
915
+ const [seconds, nanos] = value;
916
+ if (Number.isFinite(seconds) && Number.isFinite(nanos)) {
917
+ return seconds * 1000 + nanos / 1_000_000;
918
+ }
919
+ }
920
+ if (typeof value === "number" && Number.isFinite(value)) {
921
+ return value;
922
+ }
923
+ if (value instanceof Date && !Number.isNaN(value.valueOf())) {
924
+ return value.valueOf();
925
+ }
926
+ return undefined;
927
+ }
928
+
929
+ async function flushOpenTelemetryProcessorQueue({
930
+ client,
931
+ eventIdPrefix,
932
+ flushOnForceFlush,
933
+ includeTraceSummary,
934
+ onError,
935
+ state,
936
+ timestamp,
937
+ transport
938
+ }) {
939
+ if (includeTraceSummary) {
940
+ enqueueOpenTelemetryTraceSummaries({ client, eventIdPrefix, onError, state, timestamp });
941
+ }
942
+ if (!transport || !flushOnForceFlush || client.pendingEvents() === 0) {
943
+ await state.pendingFlush;
944
+ return;
945
+ }
946
+ await requestOpenTelemetryFlush({
947
+ client,
948
+ onError,
949
+ state,
950
+ swallowErrors: true,
951
+ transport
952
+ });
953
+ }
954
+
955
+ async function flushOpenTelemetryExporterQueue({
956
+ client,
957
+ eventIdPrefix,
958
+ flushOnExport,
959
+ includeTraceSummary,
960
+ onError,
961
+ state,
962
+ timestamp,
963
+ transport
964
+ }) {
965
+ if (includeTraceSummary) {
966
+ enqueueOpenTelemetryTraceSummaries({ client, eventIdPrefix, onError, state, timestamp });
967
+ }
968
+ if (!transport || !flushOnExport || client.pendingEvents() === 0) {
969
+ await state.pendingFlush;
970
+ return;
971
+ }
972
+ await requestOpenTelemetryFlush({
973
+ client,
974
+ onError,
975
+ state,
976
+ swallowErrors: false,
977
+ transport
978
+ });
979
+ }
980
+
981
+ function requestOpenTelemetryFlush({ client, onError, state, swallowErrors, transport }) {
982
+ if (state.flushInFlight) {
983
+ state.queuedFlush ??= createOpenTelemetryFlushRequest();
984
+ return state.queuedFlush.promise;
985
+ }
986
+
987
+ state.flushInFlight = true;
988
+ const rawFlush = Promise.resolve(client.flush(transport));
989
+ const visibleFlush = swallowErrors
990
+ ? rawFlush.catch((error) => {
991
+ onError(error);
992
+ return null;
993
+ })
994
+ : rawFlush;
995
+ state.pendingFlush = visibleFlush;
996
+
997
+ rawFlush.then(
998
+ () => finishOpenTelemetryFlush({ client, failed: false, onError, state, swallowErrors, transport }),
999
+ (error) => finishOpenTelemetryFlush({ error, failed: true, state, swallowErrors })
1000
+ );
1001
+ return visibleFlush;
1002
+ }
1003
+
1004
+ function finishOpenTelemetryFlush({ client, error, failed, onError, state, swallowErrors, transport }) {
1005
+ state.flushInFlight = false;
1006
+ const queuedFlush = state.queuedFlush;
1007
+ state.queuedFlush = null;
1008
+ if (queuedFlush === null) {
1009
+ return;
1010
+ }
1011
+ if (failed) {
1012
+ if (swallowErrors) {
1013
+ queuedFlush.resolve(null);
1014
+ } else {
1015
+ queuedFlush.reject(error);
1016
+ }
1017
+ return;
1018
+ }
1019
+
1020
+ requestOpenTelemetryFlush({ client, onError, state, swallowErrors, transport }).then(
1021
+ queuedFlush.resolve,
1022
+ queuedFlush.reject
1023
+ );
1024
+ }
1025
+
1026
+ function createOpenTelemetryFlushRequest() {
1027
+ let resolve;
1028
+ let reject;
1029
+ const promise = new Promise((resolvePromise, rejectPromise) => {
1030
+ resolve = resolvePromise;
1031
+ reject = rejectPromise;
1032
+ });
1033
+ return { promise, reject, resolve };
1034
+ }
1035
+
1036
+ function openTelemetryExportFailure(error) {
1037
+ return {
1038
+ code: OTEL_EXPORT_RESULT_FAILED,
1039
+ ...(error instanceof Error ? { error } : {})
1040
+ };
1041
+ }
1042
+
1043
+ function isSensitiveOpenTelemetryAttributeKey(key) {
1044
+ if (SENSITIVE_OTEL_ATTRIBUTE_KEYS.has(key)) {
1045
+ return true;
1046
+ }
1047
+ if (SENSITIVE_OTEL_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix))) {
1048
+ return true;
1049
+ }
1050
+ return SENSITIVE_OTEL_ATTRIBUTE_PATTERN.test(key);
1051
+ }
1052
+
1053
+ return {
1054
+ createLogBrewOpenTelemetrySpanExporter,
1055
+ createLogBrewOpenTelemetrySpanProcessor,
1056
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
1057
+ logbrewTraceContextFromOpenTelemetrySpan,
1058
+ logbrewTraceContextFromOpenTelemetrySpanContext,
1059
+ spanAttributesFromOpenTelemetryReadableSpan
1060
+ };
1061
+ }
1062
+
1063
+ function optionalOpenTelemetryApi() {
1064
+ const packageName = "@opentelemetry/api";
1065
+ const optionalRequire = typeof module !== "undefined" && typeof module.require === "function"
1066
+ ? module.require.bind(module)
1067
+ : typeof require === "function"
1068
+ ? require
1069
+ : undefined;
1070
+ if (!optionalRequire) {
1071
+ return undefined;
1072
+ }
1073
+ try {
1074
+ return optionalRequire(packageName);
1075
+ } catch {
1076
+ return undefined;
1077
+ }
1078
+ }
1079
+
1080
+ module.exports = { buildOpenTelemetryHelpers };