@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.
- 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 +30 -2810
- 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-exception.cjs +13 -8
- 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 +55 -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/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -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 +49 -1
- 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 -289
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const { NULL_VALUE } = require("./null-value.cjs");
|
|
2
|
+
const {
|
|
3
|
+
normalizeOpenTelemetrySpanContextIds,
|
|
4
|
+
normalizeSpanId
|
|
5
|
+
} = require("./opentelemetry-context.cjs");
|
|
6
|
+
const { openTelemetryExceptionEventSummary } = require("./opentelemetry-exceptions.cjs");
|
|
7
|
+
const {
|
|
8
|
+
openTelemetrySelectedMetadata,
|
|
9
|
+
resolveOpenTelemetryReadableSpanOptions
|
|
10
|
+
} = require("./opentelemetry-keys.cjs");
|
|
11
|
+
const { durationMsFromOpenTelemetryReadableSpan, timestampFromOpenTelemetryTime } = require("./opentelemetry-time.cjs");
|
|
12
|
+
const { isList, isRecord, optionalProperty, stringOrUndefined } = require("./validation.cjs");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @import { Metadata, SpanAttributes, SpanEventSummary, SpanLinkSummary } from "./index.cjs"
|
|
16
|
+
* @import { ReadableSpanOptions, ResolvedSpanOptions } from "./opentelemetry-keys.cjs"
|
|
17
|
+
* @typedef {Readonly<Record<string, unknown>>} ReadableSpan
|
|
18
|
+
* @typedef {{ traceId: string, spanId: string, parentSpanId?: string, sampled: boolean }} ReadableSpanContext
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const MAX_SPAN_EVENTS = 8;
|
|
22
|
+
const MAX_SPAN_LINKS = 8;
|
|
23
|
+
const FIRST_ITEM = 0;
|
|
24
|
+
const EMPTY = 0;
|
|
25
|
+
const OTEL_STATUS_CODE_ERROR = 2;
|
|
26
|
+
const OTEL_STATUS_CODE_OK = 1;
|
|
27
|
+
const SPAN_KIND_INTERNAL = 0;
|
|
28
|
+
const SPAN_KIND_SERVER = 1;
|
|
29
|
+
const SPAN_KIND_CLIENT = 2;
|
|
30
|
+
const SPAN_KIND_PRODUCER = 3;
|
|
31
|
+
const SPAN_KIND_CONSUMER = 4;
|
|
32
|
+
/** @type {ReadonlyMap<unknown, string>} */
|
|
33
|
+
const OTEL_SPAN_KIND_NAMES = new Map([
|
|
34
|
+
[SPAN_KIND_INTERNAL, "internal"],
|
|
35
|
+
[SPAN_KIND_SERVER, "server"],
|
|
36
|
+
[SPAN_KIND_CLIENT, "client"],
|
|
37
|
+
[SPAN_KIND_PRODUCER, "producer"],
|
|
38
|
+
[SPAN_KIND_CONSUMER, "consumer"]
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {ReadableSpan} span
|
|
43
|
+
* @returns {unknown}
|
|
44
|
+
*/
|
|
45
|
+
function readOpenTelemetrySpanContext(span) {
|
|
46
|
+
const { spanContext } = span;
|
|
47
|
+
if (typeof spanContext !== "function") {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return Reflect.apply(spanContext, span, []);
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {ReadableSpan} span
|
|
59
|
+
* @returns {ReadableSpanContext | undefined}
|
|
60
|
+
*/
|
|
61
|
+
function normalizeOpenTelemetryReadableSpanContext(span) {
|
|
62
|
+
const context = normalizeOpenTelemetrySpanContextIds(readOpenTelemetrySpanContext(span));
|
|
63
|
+
if (context === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const parentContext = normalizeOpenTelemetrySpanContextIds(span["parentSpanContext"]);
|
|
67
|
+
const parentSpanId = parentContext?.traceId === context.traceId
|
|
68
|
+
? parentContext.spanId
|
|
69
|
+
: normalizeSpanId(span["parentSpanId"]);
|
|
70
|
+
return {
|
|
71
|
+
traceId: context.traceId,
|
|
72
|
+
|
|
73
|
+
spanId: context.spanId,
|
|
74
|
+
...(parentSpanId === undefined ? {} : { parentSpanId }),
|
|
75
|
+
sampled: context.sampled
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {unknown} kind
|
|
81
|
+
* @returns {string | undefined}
|
|
82
|
+
*/
|
|
83
|
+
function openTelemetrySpanKindName(kind) {
|
|
84
|
+
if (typeof kind === "number") {
|
|
85
|
+
return OTEL_SPAN_KIND_NAMES.get(kind);
|
|
86
|
+
}
|
|
87
|
+
if (typeof kind === "string" && kind.trim() !== "") {
|
|
88
|
+
return kind.toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} key
|
|
95
|
+
* @param {unknown} value
|
|
96
|
+
* @returns {Metadata}
|
|
97
|
+
*/
|
|
98
|
+
function positiveOpenTelemetryCount(key, value) {
|
|
99
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > EMPTY ? { [key]: value } : {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} key
|
|
104
|
+
* @param {string | undefined} value
|
|
105
|
+
* @returns {Metadata}
|
|
106
|
+
*/
|
|
107
|
+
function definedEntry(key, value) {
|
|
108
|
+
return value === undefined ? {} : { [key]: value };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {ReadableSpan} span
|
|
113
|
+
* @param {ResolvedSpanOptions} options
|
|
114
|
+
* @returns {Metadata}
|
|
115
|
+
*/
|
|
116
|
+
function openTelemetryReadableSpanMetadata(span, options) {
|
|
117
|
+
const resource = openTelemetrySelectedMetadata(optionalProperty(span["resource"], "attributes"), options.resourceAttributeKeys);
|
|
118
|
+
const kind = openTelemetrySpanKindName(span["kind"]);
|
|
119
|
+
const scopeName = stringOrUndefined(optionalProperty(span["instrumentationScope"], "name"));
|
|
120
|
+
const scopeVersion = stringOrUndefined(optionalProperty(span["instrumentationScope"], "version"));
|
|
121
|
+
return {
|
|
122
|
+
source: "opentelemetry.readable_span",
|
|
123
|
+
...options.metadata,
|
|
124
|
+
...resource,
|
|
125
|
+
...definedEntry("otel.kind", kind),
|
|
126
|
+
...definedEntry("otel.scope.name", scopeName),
|
|
127
|
+
...definedEntry("otel.scope.version", scopeVersion),
|
|
128
|
+
...positiveOpenTelemetryCount("otel.dropped_attributes_count", span["droppedAttributesCount"]),
|
|
129
|
+
...positiveOpenTelemetryCount("otel.dropped_events_count", span["droppedEventsCount"]),
|
|
130
|
+
...positiveOpenTelemetryCount("otel.dropped_links_count", span["droppedLinksCount"]),
|
|
131
|
+
...openTelemetrySelectedMetadata(span["attributes"], options.attributeKeys)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {Readonly<Record<string, unknown>>} event
|
|
137
|
+
* @param {ResolvedSpanOptions} options
|
|
138
|
+
* @returns {SpanEventSummary}
|
|
139
|
+
*/
|
|
140
|
+
function openTelemetryEventSummary(event, options) {
|
|
141
|
+
const eventName = event["name"];
|
|
142
|
+
const name = typeof eventName === "string" && eventName.trim() !== "" ? eventName : "opentelemetry.event";
|
|
143
|
+
const timestamp = timestampFromOpenTelemetryTime(event["time"] ?? event["timestamp"]);
|
|
144
|
+
const metadata = openTelemetrySelectedMetadata(event["attributes"], options.eventAttributeKeys);
|
|
145
|
+
return {
|
|
146
|
+
name,
|
|
147
|
+
...(timestamp === undefined ? {} : { timestamp }),
|
|
148
|
+
...(Object.keys(metadata).length > EMPTY ? { metadata } : {})
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {unknown} events
|
|
154
|
+
* @param {ResolvedSpanOptions} options
|
|
155
|
+
* @returns {SpanEventSummary[] | undefined}
|
|
156
|
+
*/
|
|
157
|
+
function openTelemetryReadableSpanEvents(events, options) {
|
|
158
|
+
if (!isList(events) || events.length === EMPTY) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
/** @type {SpanEventSummary[]} */
|
|
162
|
+
const summaries = [];
|
|
163
|
+
for (const event of events.slice(FIRST_ITEM, MAX_SPAN_EVENTS)) {
|
|
164
|
+
if (isRecord(event)) {
|
|
165
|
+
summaries.push(openTelemetryEventSummary(event, options));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return summaries.length > EMPTY ? summaries : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {unknown} link
|
|
173
|
+
* @param {ResolvedSpanOptions} options
|
|
174
|
+
* @returns {SpanLinkSummary | undefined}
|
|
175
|
+
*/
|
|
176
|
+
function openTelemetryLinkSummary(link, options) {
|
|
177
|
+
const context = normalizeOpenTelemetrySpanContextIds(
|
|
178
|
+
optionalProperty(link, "context") ?? optionalProperty(link, "spanContext")
|
|
179
|
+
);
|
|
180
|
+
if (context === undefined) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const metadata = openTelemetrySelectedMetadata(optionalProperty(link, "attributes"), options.linkAttributeKeys);
|
|
184
|
+
return {
|
|
185
|
+
traceId: context.traceId,
|
|
186
|
+
|
|
187
|
+
spanId: context.spanId,
|
|
188
|
+
|
|
189
|
+
sampled: context.sampled,
|
|
190
|
+
...(Object.keys(metadata).length > EMPTY ? { metadata } : {})
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {unknown} links
|
|
196
|
+
* @param {ResolvedSpanOptions} options
|
|
197
|
+
* @returns {SpanLinkSummary[] | undefined}
|
|
198
|
+
*/
|
|
199
|
+
function openTelemetryReadableSpanLinks(links, options) {
|
|
200
|
+
if (!isList(links) || links.length === EMPTY) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
/** @type {SpanLinkSummary[]} */
|
|
204
|
+
const summaries = [];
|
|
205
|
+
for (const link of links.slice(FIRST_ITEM, MAX_SPAN_LINKS)) {
|
|
206
|
+
const summary = openTelemetryLinkSummary(link, options);
|
|
207
|
+
if (summary !== undefined) {
|
|
208
|
+
summaries.push(summary);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return summaries.length > EMPTY ? summaries : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {ReadableSpan} span
|
|
216
|
+
* @returns {string}
|
|
217
|
+
*/
|
|
218
|
+
function openTelemetrySpanName(span) {
|
|
219
|
+
const { name } = span;
|
|
220
|
+
return typeof name === "string" && name.trim() !== "" ? name : "opentelemetry.span";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {unknown} status
|
|
225
|
+
* @param {number} escapedExceptions
|
|
226
|
+
* @returns {"ok" | "error"}
|
|
227
|
+
*/
|
|
228
|
+
function openTelemetrySpanStatus(status, escapedExceptions) {
|
|
229
|
+
const code = optionalProperty(status, "code");
|
|
230
|
+
if (code === OTEL_STATUS_CODE_ERROR || code === "ERROR") {
|
|
231
|
+
return "error";
|
|
232
|
+
}
|
|
233
|
+
if (code === OTEL_STATUS_CODE_OK || code === "OK") {
|
|
234
|
+
return "ok";
|
|
235
|
+
}
|
|
236
|
+
if (escapedExceptions > EMPTY) {
|
|
237
|
+
return "error";
|
|
238
|
+
}
|
|
239
|
+
return "ok";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @param {ReadableSpan} span
|
|
244
|
+
* @param {ResolvedSpanOptions} options
|
|
245
|
+
* @returns {SpanAttributes | undefined}
|
|
246
|
+
*/
|
|
247
|
+
function readableSpanAttributes(span, options) {
|
|
248
|
+
const context = normalizeOpenTelemetryReadableSpanContext(span);
|
|
249
|
+
if (context === undefined || (!options.captureUnsampled && !context.sampled)) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const metadata = openTelemetryReadableSpanMetadata(span, options);
|
|
253
|
+
const events = options.includeSpanEvents ? openTelemetryReadableSpanEvents(span["events"], options) : undefined;
|
|
254
|
+
const links = options.includeSpanLinks ? openTelemetryReadableSpanLinks(span["links"], options) : undefined;
|
|
255
|
+
const exceptionSummary = openTelemetryExceptionEventSummary(span["events"]);
|
|
256
|
+
const durationMs = durationMsFromOpenTelemetryReadableSpan(span);
|
|
257
|
+
const resolvedMetadata = { ...metadata, ...exceptionSummary?.metadata() };
|
|
258
|
+
return {
|
|
259
|
+
name: openTelemetrySpanName(span),
|
|
260
|
+
|
|
261
|
+
traceId: context.traceId,
|
|
262
|
+
|
|
263
|
+
spanId: context.spanId,
|
|
264
|
+
...(context.parentSpanId === undefined ? {} : { parentSpanId: context.parentSpanId }),
|
|
265
|
+
status: openTelemetrySpanStatus(span["status"], exceptionSummary?.escapedCount ?? EMPTY),
|
|
266
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
267
|
+
...(events === undefined ? {} : { events }),
|
|
268
|
+
...(links === undefined ? {} : { links }),
|
|
269
|
+
...(Object.keys(resolvedMetadata).length > EMPTY ? { metadata: resolvedMetadata } : {})
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param {unknown} span
|
|
275
|
+
* @param {ReadableSpanOptions} [options]
|
|
276
|
+
* @returns {SpanAttributes | null}
|
|
277
|
+
*/
|
|
278
|
+
function spanAttributesFromOpenTelemetryReadableSpan(span, options = {}) {
|
|
279
|
+
const resolvedOptions = resolveOpenTelemetryReadableSpanOptions(options);
|
|
280
|
+
if (!isRecord(span)) {
|
|
281
|
+
return NULL_VALUE;
|
|
282
|
+
}
|
|
283
|
+
return readableSpanAttributes(span, resolvedOptions) ?? NULL_VALUE;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = { readableSpanAttributes, spanAttributesFromOpenTelemetryReadableSpan };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const { compactMetadata, isMetadataValue } = require("./log-context.cjs");
|
|
2
|
+
const { defaultSpanIdFactory } = require("./opentelemetry-context.cjs");
|
|
3
|
+
const { safeOpenTelemetryExceptionType } = require("./opentelemetry-exceptions.cjs");
|
|
4
|
+
const { TRACE_SUMMARY_METADATA_KEYS } = require("./opentelemetry-keys.cjs");
|
|
5
|
+
const {
|
|
6
|
+
endMsFromOpenTelemetryReadableSpan,
|
|
7
|
+
fallbackTimestampValue,
|
|
8
|
+
openTelemetryTimeMs,
|
|
9
|
+
timestampFromOpenTelemetryTime
|
|
10
|
+
} = require("./opentelemetry-time.cjs");
|
|
11
|
+
const { isTruthy } = require("./validation.cjs");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @import { Metadata, MetadataValue, SpanAttributes } from "./index.cjs"
|
|
15
|
+
* @import { DeepReadonly } from "./validation.cjs"
|
|
16
|
+
* @typedef {DeepReadonly<SpanAttributes>} ReadonlySpanAttributes
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const NONE = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Readonly<Metadata> | undefined} metadata
|
|
23
|
+
* @param {string} key
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
function positiveSafeInteger(metadata, key) {
|
|
27
|
+
const value = metadata?.[key];
|
|
28
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > NONE ? value : NONE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class OpenTelemetryTraceSummary {
|
|
32
|
+
spanCount = NONE;
|
|
33
|
+
errorSpanCount = NONE;
|
|
34
|
+
/** @type {Metadata} */
|
|
35
|
+
metadata = {};
|
|
36
|
+
rootSeen = false;
|
|
37
|
+
/** @type {number | undefined} */
|
|
38
|
+
firstStartMs = undefined;
|
|
39
|
+
/** @type {number | undefined} */
|
|
40
|
+
lastEndMs = undefined;
|
|
41
|
+
/** @type {string | undefined} */
|
|
42
|
+
rootSpanId = undefined;
|
|
43
|
+
/** @type {string | undefined} */
|
|
44
|
+
rootName = undefined;
|
|
45
|
+
/** @type {MetadataValue | undefined} */
|
|
46
|
+
rootKind = undefined;
|
|
47
|
+
/** @type {number | undefined} */
|
|
48
|
+
rootStartMs = undefined;
|
|
49
|
+
/** @type {number | undefined} */
|
|
50
|
+
rootDurationMs = undefined;
|
|
51
|
+
exceptionEventCount = NONE;
|
|
52
|
+
exceptionEscapedCount = NONE;
|
|
53
|
+
/** @type {Set<string>} */
|
|
54
|
+
exceptionTypes = new Set();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} traceId
|
|
58
|
+
*/
|
|
59
|
+
constructor(traceId) {
|
|
60
|
+
this.traceId = traceId;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {ReadonlySpanAttributes} attributes
|
|
65
|
+
* @param {Readonly<Record<string, unknown>>} span
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
record(attributes, span) {
|
|
69
|
+
this.spanCount += 1;
|
|
70
|
+
if (attributes.status === "error") {
|
|
71
|
+
this.errorSpanCount += 1;
|
|
72
|
+
}
|
|
73
|
+
this.recordExceptions(attributes.metadata);
|
|
74
|
+
const startMs = openTelemetryTimeMs(span["startTime"]);
|
|
75
|
+
this.recordWindow(startMs, endMsFromOpenTelemetryReadableSpan(span, startMs, attributes.durationMs));
|
|
76
|
+
this.copyMetadata(attributes.metadata, false);
|
|
77
|
+
const isRootSpan = attributes.parentSpanId === undefined;
|
|
78
|
+
if (isRootSpan || this.rootSpanId === undefined) {
|
|
79
|
+
this.recordRoot(attributes, isRootSpan, startMs);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {number | undefined} startMs
|
|
85
|
+
* @param {number | undefined} endMs
|
|
86
|
+
* @returns {void}
|
|
87
|
+
*/
|
|
88
|
+
recordWindow(startMs, endMs) {
|
|
89
|
+
if (startMs !== undefined && (this.firstStartMs === undefined || startMs < this.firstStartMs)) {
|
|
90
|
+
this.firstStartMs = startMs;
|
|
91
|
+
}
|
|
92
|
+
if (endMs !== undefined && (this.lastEndMs === undefined || endMs > this.lastEndMs)) {
|
|
93
|
+
this.lastEndMs = endMs;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {ReadonlySpanAttributes} attributes
|
|
99
|
+
* @param {boolean} isRootSpan
|
|
100
|
+
* @param {number | undefined} startMs
|
|
101
|
+
* @returns {void}
|
|
102
|
+
*/
|
|
103
|
+
recordRoot(attributes, isRootSpan, startMs) {
|
|
104
|
+
this.rootSpanId = attributes.spanId;
|
|
105
|
+
this.rootName = attributes.name;
|
|
106
|
+
this.rootKind = attributes.metadata?.["otel.kind"];
|
|
107
|
+
this.rootSeen = isRootSpan;
|
|
108
|
+
if (startMs !== undefined) {
|
|
109
|
+
this.rootStartMs = startMs;
|
|
110
|
+
}
|
|
111
|
+
if (attributes.durationMs !== undefined) {
|
|
112
|
+
this.rootDurationMs = attributes.durationMs;
|
|
113
|
+
}
|
|
114
|
+
this.copyMetadata(attributes.metadata, true);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {Readonly<Metadata> | undefined} metadata
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
121
|
+
recordExceptions(metadata) {
|
|
122
|
+
this.exceptionEventCount += positiveSafeInteger(metadata, "otel.exception_event_count");
|
|
123
|
+
this.exceptionEscapedCount += positiveSafeInteger(metadata, "otel.exception_escaped_count");
|
|
124
|
+
const types = metadata?.["otel.exception_types"];
|
|
125
|
+
if (typeof types === "string" && types.trim() !== "") {
|
|
126
|
+
for (const type of types.split(",")) {
|
|
127
|
+
const safeType = safeOpenTelemetryExceptionType(type);
|
|
128
|
+
if (safeType !== undefined) {
|
|
129
|
+
this.exceptionTypes.add(safeType);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {Readonly<Metadata> | undefined} metadata
|
|
137
|
+
* @param {boolean} overwrite
|
|
138
|
+
* @returns {void}
|
|
139
|
+
*/
|
|
140
|
+
copyMetadata(metadata, overwrite) {
|
|
141
|
+
if (metadata === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
145
|
+
if (
|
|
146
|
+
TRACE_SUMMARY_METADATA_KEYS.has(key)
|
|
147
|
+
&& (overwrite || this.metadata[key] === undefined)
|
|
148
|
+
&& isMetadataValue(value)
|
|
149
|
+
) {
|
|
150
|
+
this.metadata[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @returns {number | undefined}
|
|
157
|
+
*/
|
|
158
|
+
durationMs() {
|
|
159
|
+
if (this.firstStartMs !== undefined && this.lastEndMs !== undefined && this.lastEndMs >= this.firstStartMs) {
|
|
160
|
+
return this.lastEndMs - this.firstStartMs;
|
|
161
|
+
}
|
|
162
|
+
return this.rootDurationMs;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {(() => unknown) | undefined} fallbackTimestamp
|
|
167
|
+
* @returns {unknown}
|
|
168
|
+
*/
|
|
169
|
+
timestamp(fallbackTimestamp) {
|
|
170
|
+
return timestampFromOpenTelemetryTime(this.rootStartMs)
|
|
171
|
+
?? timestampFromOpenTelemetryTime(this.firstStartMs)
|
|
172
|
+
?? fallbackTimestampValue(fallbackTimestamp);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @returns {Metadata}
|
|
177
|
+
*/
|
|
178
|
+
summaryMetadata() {
|
|
179
|
+
return compactMetadata({
|
|
180
|
+
source: "opentelemetry.trace_summary",
|
|
181
|
+
...this.metadata,
|
|
182
|
+
"otel.trace.span_count": this.spanCount,
|
|
183
|
+
...(this.errorSpanCount > NONE ? { "otel.trace.error_span_count": this.errorSpanCount } : {}),
|
|
184
|
+
...(this.exceptionEventCount > NONE ? { "otel.trace.exception_event_count": this.exceptionEventCount } : {}),
|
|
185
|
+
...(this.exceptionEscapedCount > NONE ? { "otel.trace.exception_escaped_count": this.exceptionEscapedCount } : {}),
|
|
186
|
+
...(this.exceptionTypes.size > NONE ? { "otel.trace.exception_types": [...this.exceptionTypes].join(",") } : {}),
|
|
187
|
+
...(isTruthy(this.rootSpanId) ? { "otel.trace.root_span_id": this.rootSpanId } : {}),
|
|
188
|
+
...(isTruthy(this.rootName) ? { "otel.trace.root_name": this.rootName } : {}),
|
|
189
|
+
...(isTruthy(this.rootKind) ? { "otel.trace.root_kind": this.rootKind } : {}),
|
|
190
|
+
"otel.trace.summary_kind": this.rootSeen ? "rooted" : "flush_batch"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @returns {SpanAttributes}
|
|
196
|
+
*/
|
|
197
|
+
attributes() {
|
|
198
|
+
const metadata = this.summaryMetadata();
|
|
199
|
+
const durationMs = this.durationMs();
|
|
200
|
+
return {
|
|
201
|
+
name: isTruthy(this.rootName) ? `opentelemetry.trace:${String(this.rootName)}` : "opentelemetry.trace",
|
|
202
|
+
|
|
203
|
+
traceId: this.traceId,
|
|
204
|
+
|
|
205
|
+
spanId: defaultSpanIdFactory(),
|
|
206
|
+
status: this.errorSpanCount > NONE ? "error" : "ok",
|
|
207
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
208
|
+
...(Object.keys(metadata).length > NONE ? { metadata } : {})
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = { OpenTelemetryTraceSummary };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const { isList } = require("./validation.cjs");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Readonly<Record<string, unknown>>} ReadableSpan
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const HR_TIME_PARTS = 2;
|
|
8
|
+
const MS_PER_SECOND = 1000;
|
|
9
|
+
const NANOS_PER_MS = 1_000_000;
|
|
10
|
+
const MIN_HR_TIME_PART = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {unknown} value
|
|
14
|
+
* @returns {value is number}
|
|
15
|
+
*/
|
|
16
|
+
function isFiniteNumber(value) {
|
|
17
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} value
|
|
22
|
+
* @returns {number | undefined}
|
|
23
|
+
*/
|
|
24
|
+
function openTelemetryTimeMs(value) {
|
|
25
|
+
if (isList(value) && value.length >= HR_TIME_PARTS) {
|
|
26
|
+
const [seconds, nanos] = value;
|
|
27
|
+
if (isFiniteNumber(seconds) && isFiniteNumber(nanos)) {
|
|
28
|
+
return seconds * MS_PER_SECOND + nanos / NANOS_PER_MS;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (isFiniteNumber(value)) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
if (value instanceof Date && !Number.isNaN(value.valueOf())) {
|
|
35
|
+
return value.valueOf();
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {unknown} value
|
|
42
|
+
* @returns {number | undefined}
|
|
43
|
+
*/
|
|
44
|
+
function openTelemetryDurationMs(value) {
|
|
45
|
+
if (!isList(value) || value.length < HR_TIME_PARTS) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const [seconds, nanos] = value;
|
|
49
|
+
if (
|
|
50
|
+
!isFiniteNumber(seconds)
|
|
51
|
+
|| !isFiniteNumber(nanos)
|
|
52
|
+
|| seconds < MIN_HR_TIME_PART
|
|
53
|
+
|| nanos < MIN_HR_TIME_PART
|
|
54
|
+
) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return seconds * MS_PER_SECOND + nanos / NANOS_PER_MS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {unknown} value
|
|
62
|
+
* @returns {string | undefined}
|
|
63
|
+
*/
|
|
64
|
+
function timestampFromOpenTelemetryTime(value) {
|
|
65
|
+
const milliseconds = openTelemetryTimeMs(value);
|
|
66
|
+
if (milliseconds === undefined) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const date = new Date(milliseconds);
|
|
70
|
+
return Number.isNaN(date.valueOf()) ? undefined : date.toISOString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {(() => unknown) | undefined} fallbackTimestamp
|
|
75
|
+
* @returns {unknown}
|
|
76
|
+
*/
|
|
77
|
+
function fallbackTimestampValue(fallbackTimestamp) {
|
|
78
|
+
return fallbackTimestamp === undefined ? new Date().toISOString() : fallbackTimestamp();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {ReadableSpan} span
|
|
83
|
+
* @returns {number | undefined}
|
|
84
|
+
*/
|
|
85
|
+
function durationMsFromOpenTelemetryReadableSpan(span) {
|
|
86
|
+
const durationMs = openTelemetryDurationMs(span["duration"]);
|
|
87
|
+
if (durationMs !== undefined) {
|
|
88
|
+
return durationMs;
|
|
89
|
+
}
|
|
90
|
+
const startMs = openTelemetryTimeMs(span["startTime"]);
|
|
91
|
+
const endMs = openTelemetryTimeMs(span["endTime"]);
|
|
92
|
+
if (startMs !== undefined && endMs !== undefined && endMs >= startMs) {
|
|
93
|
+
return endMs - startMs;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {ReadableSpan} span
|
|
100
|
+
* @param {number | undefined} startMs
|
|
101
|
+
* @param {number | undefined} durationMs
|
|
102
|
+
* @returns {number | undefined}
|
|
103
|
+
*/
|
|
104
|
+
function endMsFromOpenTelemetryReadableSpan(span, startMs, durationMs) {
|
|
105
|
+
const endMs = openTelemetryTimeMs(span["endTime"]);
|
|
106
|
+
if (endMs !== undefined) {
|
|
107
|
+
return endMs;
|
|
108
|
+
}
|
|
109
|
+
if (startMs !== undefined && durationMs !== undefined) {
|
|
110
|
+
return startMs + durationMs;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {ReadableSpan} span
|
|
117
|
+
* @param {(() => unknown) | undefined} fallbackTimestamp
|
|
118
|
+
* @returns {unknown}
|
|
119
|
+
*/
|
|
120
|
+
function timestampFromOpenTelemetryReadableSpan(span, fallbackTimestamp) {
|
|
121
|
+
return timestampFromOpenTelemetryTime(span["startTime"]) ?? fallbackTimestampValue(fallbackTimestamp);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
durationMsFromOpenTelemetryReadableSpan,
|
|
126
|
+
endMsFromOpenTelemetryReadableSpan,
|
|
127
|
+
fallbackTimestampValue,
|
|
128
|
+
openTelemetryTimeMs,
|
|
129
|
+
timestampFromOpenTelemetryReadableSpan,
|
|
130
|
+
timestampFromOpenTelemetryTime
|
|
131
|
+
};
|