@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.
- package/automatic-delivery.cjs +271 -0
- package/batch-sender.cjs +164 -0
- package/bounded-number.cjs +34 -0
- package/browser-breadcrumbs.cjs +346 -136
- package/capture-support.cjs +100 -0
- package/client-options.cjs +292 -0
- package/client-report.cjs +232 -0
- package/client.cjs +447 -0
- package/console-arguments.cjs +151 -0
- package/console-capture.cjs +194 -0
- package/core.cjs +35 -2984
- package/delivery-health.cjs +133 -0
- package/error-causes.cjs +130 -0
- package/error-details.cjs +124 -0
- package/error-issue.cjs +187 -0
- package/event-clone.cjs +55 -0
- package/event-metadata.cjs +40 -0
- package/event-pipeline.cjs +325 -0
- package/event-queue.cjs +306 -0
- package/event-store.cjs +237 -0
- package/event-validation.cjs +159 -0
- package/examples/agent-timeline.cjs +57 -46
- package/examples/agent-timeline.mjs +41 -35
- package/examples/index.mjs +122 -94
- package/examples/readme-example.cjs +36 -27
- package/examples/readme-example.mjs +21 -17
- package/examples/real-user-smoke.cjs +41 -32
- package/examples/real-user-smoke.mjs +26 -22
- package/exception-chain.cjs +205 -0
- package/index.d.ts +144 -1076
- package/index.js +1 -1
- package/issue-breadcrumbs.cjs +143 -0
- package/issue-diagnostics.cjs +94 -582
- package/issue-evidence.cjs +256 -0
- package/issue-exception.cjs +268 -0
- package/issue-stack-frames.cjs +220 -0
- package/issue-stack-text.cjs +101 -0
- package/issue-stack.cjs +188 -225
- package/issue-text.cjs +72 -0
- package/log-context.cjs +87 -67
- package/metric-validation.cjs +95 -0
- package/network-milestone.cjs +155 -0
- package/null-value.cjs +4 -0
- package/opentelemetry-bridge.cjs +330 -0
- package/opentelemetry-context.cjs +245 -0
- package/opentelemetry-exceptions.cjs +69 -0
- package/opentelemetry-keys.cjs +183 -0
- package/opentelemetry-span.cjs +286 -0
- package/opentelemetry-summary.cjs +213 -0
- package/opentelemetry-time.cjs +131 -0
- package/opentelemetry.cjs +99 -1068
- package/package.json +64 -1
- package/pino-destination.cjs +108 -0
- package/pino-record.cjs +237 -0
- package/product-action.cjs +163 -0
- package/provided-breadcrumbs.cjs +117 -0
- package/random-hex.cjs +29 -0
- package/react-native.js +1 -1
- package/recording-transport.cjs +68 -0
- package/release-artifacts-build.cjs +263 -84
- package/release-artifacts-common.js +347 -66
- package/release-artifacts-debug-id.js +178 -0
- package/release-artifacts-inspect.js +213 -0
- package/release-artifacts-issue-event.js +99 -0
- package/release-artifacts-manifest-artifact.js +107 -0
- package/release-artifacts-manifest.js +77 -0
- package/release-artifacts-mappings.js +265 -0
- package/release-artifacts-prepare.js +297 -0
- package/release-artifacts-source-context.js +210 -0
- package/release-artifacts-symbolication.js +272 -421
- package/release-artifacts-upload-files.js +122 -0
- package/release-artifacts-upload-request.js +246 -0
- package/release-artifacts-upload.js +155 -300
- package/release-artifacts-urls.js +49 -0
- package/release-artifacts.js +189 -662
- package/retry-delay.cjs +32 -0
- package/sdk-error.cjs +36 -0
- package/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -0
- package/support-ticket.cjs +232 -122
- package/telemetry-context.cjs +343 -251
- package/trace-context.cjs +376 -179
- package/transport-error.cjs +23 -0
- package/types/client.d.ts +67 -0
- package/types/delivery.d.ts +125 -0
- package/types/issues.d.ts +249 -0
- package/types/logging.d.ts +175 -0
- package/types/opentelemetry.d.ts +205 -0
- package/types/product.d.ts +128 -0
- package/types/recording-transport.d.ts +15 -0
- package/types/sdk-error.d.ts +9 -0
- package/types/telemetry.d.ts +171 -0
- package/types/trace-context.d.ts +110 -0
- package/types/transport-error.d.ts +10 -0
- package/utf8.cjs +37 -0
- package/validation.cjs +327 -0
- package/vite-release-artifacts-config.cjs +120 -0
- package/vite-release-artifacts.cjs +199 -99
- package/vite-release-artifacts.d.cts +65 -40
- package/vite-release-artifacts.d.ts +65 -40
- package/vite-release-artifacts.js +2 -2
- package/winston-info.cjs +205 -0
- 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 };
|