@logbrew/sdk 0.1.38 → 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 (90) 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 +30 -2810
  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-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
package/index.js CHANGED
@@ -36,4 +36,4 @@ export const {
36
36
  TransportError
37
37
  } = sdk;
38
38
 
39
- export default sdk;
39
+ export { default } from "./index.cjs";
@@ -73,12 +73,9 @@ function validateIssueExceptionMechanism(mechanism) {
73
73
 
74
74
  /**
75
75
  * @param {unknown} exception
76
- * @returns {IssueException | undefined}
76
+ * @returns {IssueException}
77
77
  */
78
- function validateIssueException(exception) {
79
- if (exception === undefined) {
80
- return undefined;
81
- }
78
+ function requireIssueException(exception) {
82
79
  const source = requireObject("issue exception", exception);
83
80
  rejectUnknownKeys("issue exception", source, EXCEPTION_KEYS);
84
81
  const type = boundedIdentity("issue exception type", source["type"], MAX_EXCEPTION_TYPE_LENGTH);
@@ -86,14 +83,22 @@ function validateIssueException(exception) {
86
83
  return { type, ...optionalEntry("mechanism", mechanism) };
87
84
  }
88
85
 
86
+ /**
87
+ * @param {unknown} exception
88
+ * @returns {IssueException | undefined}
89
+ */
90
+ function validateIssueException(exception) {
91
+ return exception === undefined ? undefined : requireIssueException(exception);
92
+ }
93
+
89
94
  /**
90
95
  * @param {string} type
91
96
  * @param {string} mechanism
92
- * @param {boolean} handled
93
- * @returns {IssueException | undefined}
97
+ * @param {unknown} handled
98
+ * @returns {IssueException}
94
99
  */
95
100
  function createIssueException(type, mechanism, handled) {
96
- return validateIssueException({ mechanism: { handled, type: mechanism }, type });
101
+ return requireIssueException({ mechanism: { handled, type: mechanism }, type });
97
102
  }
98
103
 
99
104
  /**
@@ -0,0 +1,95 @@
1
+ const { withMetadata } = require("./event-metadata.cjs");
2
+ const { SdkError } = require("./sdk-error.cjs");
3
+ const { hasUnicodeControlCharacter, optionalEntry, requireAllowedValue, requireFiniteNumber, requireNonEmpty } = require("./validation.cjs");
4
+
5
+ /**
6
+ * @import { EventAttributes } from "./event-validation.cjs"
7
+ * @typedef {"counter" | "gauge" | "histogram"} MetricKind
8
+ */
9
+
10
+ const MAX_METRIC_DESCRIPTION_LENGTH = 1024;
11
+ const MIN_METRIC_VALUE = 0;
12
+ const FIRST_CODE_POINT = 0;
13
+ const FIRST_SURROGATE = 0xD8_00;
14
+ const LAST_SURROGATE = 0xDF_FF;
15
+ const LINE_SEPARATOR = 0x20_28;
16
+ const PARAGRAPH_SEPARATOR = 0x20_29;
17
+ /** @type {ReadonlySet<MetricKind>} */
18
+ const METRIC_KINDS = new Set(["counter", "gauge", "histogram"]);
19
+ /** @type {ReadonlySet<MetricKind>} */
20
+ const NON_NEGATIVE_METRIC_KINDS = new Set(["counter", "histogram"]);
21
+ /** @type {ReadonlySet<string>} */
22
+ const CUMULATIVE_TEMPORALITIES = new Set(["delta", "cumulative"]);
23
+ /** @type {ReadonlyMap<MetricKind, ReadonlySet<string>>} */
24
+ const METRIC_TEMPORALITIES_BY_KIND = new Map([
25
+ ["counter", CUMULATIVE_TEMPORALITIES],
26
+ ["gauge", new Set(["instant"])],
27
+ ["histogram", CUMULATIVE_TEMPORALITIES]
28
+ ]);
29
+
30
+ /**
31
+ * @param {string} character
32
+ * @returns {boolean}
33
+ */
34
+ function isSeparatorOrSurrogate(character) {
35
+ const code = character.codePointAt(FIRST_CODE_POINT);
36
+ return code !== undefined && (
37
+ (code >= FIRST_SURROGATE && code <= LAST_SURROGATE)
38
+ || code === LINE_SEPARATOR
39
+ || code === PARAGRAPH_SEPARATOR
40
+ );
41
+ }
42
+
43
+ /**
44
+ * @param {unknown} value
45
+ * @returns {string | undefined}
46
+ */
47
+ function metricDescriptionOrUndefined(value) {
48
+ if (value === undefined) {
49
+ return undefined;
50
+ }
51
+ const characters = typeof value === "string" ? [...value] : [];
52
+ if (
53
+ typeof value !== "string"
54
+ || value.trim() === ""
55
+ || characters.length > MAX_METRIC_DESCRIPTION_LENGTH
56
+ || hasUnicodeControlCharacter(value)
57
+ || characters.some((character) => isSeparatorOrSurrogate(character))
58
+ ) {
59
+ throw new SdkError(
60
+ "validation_error",
61
+ `metric description must be a non-blank string of at most ${MAX_METRIC_DESCRIPTION_LENGTH} non-control characters`
62
+ );
63
+ }
64
+ return value.trim();
65
+ }
66
+
67
+ /**
68
+ * @param {Readonly<Record<string, unknown>>} attributes
69
+ * @returns {EventAttributes}
70
+ */
71
+ function validateMetric(attributes) {
72
+ const name = requireNonEmpty("metric name", attributes["name"]);
73
+ const kind = requireAllowedValue("metric kind", attributes["kind"], METRIC_KINDS);
74
+ const value = requireFiniteNumber("metric value", attributes["value"]);
75
+ const unit = requireNonEmpty("metric unit", attributes["unit"]);
76
+ const allowedTemporalities = METRIC_TEMPORALITIES_BY_KIND.get(kind) ?? CUMULATIVE_TEMPORALITIES;
77
+ const temporality = requireAllowedValue(`metric temporality for ${kind}`, attributes["temporality"], allowedTemporalities);
78
+ if (NON_NEGATIVE_METRIC_KINDS.has(kind) && value < MIN_METRIC_VALUE) {
79
+ throw new SdkError("validation_error", `metric ${kind} value must be non-negative`);
80
+ }
81
+ const description = metricDescriptionOrUndefined(attributes["description"]);
82
+ return withMetadata({
83
+ name,
84
+ ...optionalEntry("description", description),
85
+ kind,
86
+
87
+ value,
88
+
89
+ unit,
90
+
91
+ temporality
92
+ }, attributes["metadata"], attributes["context"]);
93
+ }
94
+
95
+ module.exports = { validateMetric };
@@ -0,0 +1,155 @@
1
+ const { ACTION_STATUSES } = require("./event-validation.cjs");
2
+ const { compactMetadata } = require("./log-context.cjs");
3
+ const { sanitizeRouteTemplate } = require("./product-action.cjs");
4
+ const { SdkError } = require("./sdk-error.cjs");
5
+ const { isRecord, requireAllowedValue, requireNonEmpty, stringOrUndefined } = require("./validation.cjs");
6
+
7
+ /**
8
+ * @import { ActionStatus, TimelineAttributes, TimelineOptions } from "./product-action.cjs"
9
+ * @typedef {{
10
+ * durationMs: number | undefined,
11
+ * metadata: unknown,
12
+ * method: string,
13
+ * name: string,
14
+ * routeTemplate: string,
15
+ * sessionId: unknown,
16
+ * status: ActionStatus,
17
+ * statusCode: number | undefined,
18
+ * traceId: unknown
19
+ * }} NetworkMilestoneDetails
20
+ */
21
+
22
+ const MIN_STATUS_CODE = 100;
23
+ const MAX_STATUS_CODE = 599;
24
+ const FAILURE_STATUS_CODE = 400;
25
+ const MIN_DURATION_MS = 0;
26
+ const HTTP_METHOD_PATTERN = /^[A-Z][A-Z0-9_-]*$/u;
27
+
28
+ /**
29
+ * @param {unknown} method
30
+ * @returns {string}
31
+ */
32
+ function normalizeHttpMethod(method) {
33
+ const value = method === undefined ? "GET" : method;
34
+ if (typeof value !== "string" || value.trim() === "") {
35
+ throw new SdkError("validation_error", "network milestone method must be a non-empty string");
36
+ }
37
+ const normalized = value.trim().toUpperCase();
38
+ if (!HTTP_METHOD_PATTERN.test(normalized)) {
39
+ throw new SdkError("validation_error", "network milestone method must be a valid HTTP method");
40
+ }
41
+ return normalized;
42
+ }
43
+
44
+ /**
45
+ * @param {unknown} value
46
+ * @returns {number | undefined}
47
+ */
48
+ function statusCodeOrUndefined(value) {
49
+ if (value === undefined) {
50
+ return undefined;
51
+ }
52
+ if (typeof value !== "number" || !Number.isInteger(value) || value < MIN_STATUS_CODE || value > MAX_STATUS_CODE) {
53
+ throw new SdkError("validation_error", "network milestone statusCode must be an integer from 100 to 599");
54
+ }
55
+ return value;
56
+ }
57
+
58
+ /**
59
+ * @param {string} label
60
+ * @param {unknown} value
61
+ * @returns {number | undefined}
62
+ */
63
+ function nonNegativeNumberOrUndefined(label, value) {
64
+ if (value === undefined) {
65
+ return undefined;
66
+ }
67
+ if (typeof value !== "number" || !Number.isFinite(value) || value < MIN_DURATION_MS) {
68
+ throw new SdkError("validation_error", `${label} must be a non-negative number`);
69
+ }
70
+ return value;
71
+ }
72
+
73
+ /**
74
+ * @param {number | undefined} statusCode
75
+ * @returns {"failure" | "success"}
76
+ */
77
+ function statusFromStatusCode(statusCode) {
78
+ return statusCode !== undefined && statusCode >= FAILURE_STATUS_CODE ? "failure" : "success";
79
+ }
80
+
81
+ /**
82
+ * @param {Readonly<Record<string, unknown>>} request
83
+ * @returns {NetworkMilestoneDetails}
84
+ */
85
+ function networkMilestoneFields(request) {
86
+ const routeTemplate = requireNonEmpty("network milestone routeTemplate", sanitizeRouteTemplate(request["routeTemplate"]));
87
+ const method = normalizeHttpMethod(request["method"]);
88
+ const statusCode = statusCodeOrUndefined(request["statusCode"]);
89
+ const requestedStatus = request["status"];
90
+ const status = requireAllowedValue(
91
+ "network milestone status",
92
+ requestedStatus === undefined ? statusFromStatusCode(statusCode) : requestedStatus,
93
+ ACTION_STATUSES
94
+ );
95
+ const durationMs = nonNegativeNumberOrUndefined("network milestone durationMs", request["durationMs"]);
96
+ const name = stringOrUndefined(request["name"]) ?? `network.${method.toLowerCase()} ${routeTemplate}`;
97
+ return {
98
+ durationMs,
99
+ metadata: request["metadata"],
100
+ method,
101
+ name,
102
+ routeTemplate,
103
+ sessionId: request["sessionId"],
104
+ status,
105
+ statusCode,
106
+ traceId: request["traceId"]
107
+ };
108
+ }
109
+
110
+ /**
111
+ * @param {unknown} request
112
+ * @returns {NetworkMilestoneDetails}
113
+ */
114
+ function networkMilestoneDetails(request) {
115
+ if (typeof request === "string") {
116
+ return networkMilestoneFields({ routeTemplate: request });
117
+ }
118
+ if (!isRecord(request)) {
119
+ throw new SdkError("validation_error", "network milestone must be a string or object");
120
+ }
121
+ return networkMilestoneFields(request);
122
+ }
123
+
124
+ /**
125
+ * @param {unknown} request
126
+ * @param {TimelineOptions} [options]
127
+ * @returns {TimelineAttributes}
128
+ */
129
+ function createNetworkMilestoneAttributes(request, options = {}) {
130
+ const details = networkMilestoneDetails(request);
131
+ return {
132
+ name: details.name,
133
+
134
+ status: details.status,
135
+
136
+ metadata: compactMetadata({
137
+ source: "network.milestone",
138
+ ...compactMetadata(options.metadata),
139
+ ...compactMetadata(details.metadata),
140
+ routeTemplate: details.routeTemplate,
141
+
142
+ method: details.method,
143
+
144
+ statusCode: details.statusCode,
145
+
146
+ durationMs: details.durationMs,
147
+
148
+ sessionId: stringOrUndefined(details.sessionId),
149
+
150
+ traceId: stringOrUndefined(details.traceId)
151
+ })
152
+ };
153
+ }
154
+
155
+ module.exports = { createNetworkMilestoneAttributes };
package/null-value.cjs ADDED
@@ -0,0 +1,4 @@
1
+ /** @type {null} */
2
+ const NULL_VALUE = null;
3
+
4
+ module.exports = { NULL_VALUE };
@@ -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 };