@logbrew/sdk 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +35 -2984
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-breadcrumbs.cjs +143 -0
  33. package/issue-diagnostics.cjs +94 -582
  34. package/issue-evidence.cjs +256 -0
  35. package/issue-exception.cjs +268 -0
  36. package/issue-stack-frames.cjs +220 -0
  37. package/issue-stack-text.cjs +101 -0
  38. package/issue-stack.cjs +188 -225
  39. package/issue-text.cjs +72 -0
  40. package/log-context.cjs +87 -67
  41. package/metric-validation.cjs +95 -0
  42. package/network-milestone.cjs +155 -0
  43. package/null-value.cjs +4 -0
  44. package/opentelemetry-bridge.cjs +330 -0
  45. package/opentelemetry-context.cjs +245 -0
  46. package/opentelemetry-exceptions.cjs +69 -0
  47. package/opentelemetry-keys.cjs +183 -0
  48. package/opentelemetry-span.cjs +286 -0
  49. package/opentelemetry-summary.cjs +213 -0
  50. package/opentelemetry-time.cjs +131 -0
  51. package/opentelemetry.cjs +99 -1068
  52. package/package.json +64 -1
  53. package/pino-destination.cjs +108 -0
  54. package/pino-record.cjs +237 -0
  55. package/product-action.cjs +163 -0
  56. package/provided-breadcrumbs.cjs +117 -0
  57. package/random-hex.cjs +29 -0
  58. package/react-native.js +1 -1
  59. package/recording-transport.cjs +68 -0
  60. package/release-artifacts-build.cjs +263 -84
  61. package/release-artifacts-common.js +347 -66
  62. package/release-artifacts-debug-id.js +178 -0
  63. package/release-artifacts-inspect.js +213 -0
  64. package/release-artifacts-issue-event.js +99 -0
  65. package/release-artifacts-manifest-artifact.js +107 -0
  66. package/release-artifacts-manifest.js +77 -0
  67. package/release-artifacts-mappings.js +265 -0
  68. package/release-artifacts-prepare.js +297 -0
  69. package/release-artifacts-source-context.js +210 -0
  70. package/release-artifacts-symbolication.js +272 -421
  71. package/release-artifacts-upload-files.js +122 -0
  72. package/release-artifacts-upload-request.js +246 -0
  73. package/release-artifacts-upload.js +155 -300
  74. package/release-artifacts-urls.js +49 -0
  75. package/release-artifacts.js +189 -662
  76. package/retry-delay.cjs +32 -0
  77. package/sdk-error.cjs +36 -0
  78. package/span-validation.cjs +191 -0
  79. package/stored-events.cjs +243 -0
  80. package/support-ticket.cjs +232 -122
  81. package/telemetry-context.cjs +343 -251
  82. package/trace-context.cjs +376 -179
  83. package/transport-error.cjs +23 -0
  84. package/types/client.d.ts +67 -0
  85. package/types/delivery.d.ts +125 -0
  86. package/types/issues.d.ts +249 -0
  87. package/types/logging.d.ts +175 -0
  88. package/types/opentelemetry.d.ts +205 -0
  89. package/types/product.d.ts +128 -0
  90. package/types/recording-transport.d.ts +15 -0
  91. package/types/sdk-error.d.ts +9 -0
  92. package/types/telemetry.d.ts +171 -0
  93. package/types/trace-context.d.ts +110 -0
  94. package/types/transport-error.d.ts +10 -0
  95. package/utf8.cjs +37 -0
  96. package/validation.cjs +327 -0
  97. package/vite-release-artifacts-config.cjs +120 -0
  98. package/vite-release-artifacts.cjs +199 -99
  99. package/vite-release-artifacts.d.cts +65 -40
  100. package/vite-release-artifacts.d.ts +65 -40
  101. package/vite-release-artifacts.js +2 -2
  102. package/winston-info.cjs +205 -0
  103. package/winston.cjs +189 -290
@@ -0,0 +1,183 @@
1
+ const { compactMetadata, isMetadataValue } = require("./log-context.cjs");
2
+ const { SdkError } = require("./sdk-error.cjs");
3
+ const { isList, isRecord, requireNonEmpty } = require("./validation.cjs");
4
+
5
+ /**
6
+ * @import { Metadata } from "./index.cjs"
7
+ * @typedef {Readonly<ReadonlySet<string>>} KeySet
8
+ * @typedef {Readonly<{
9
+ * attributeKeys?: unknown,
10
+ * captureUnsampled?: unknown,
11
+ * eventAttributeKeys?: unknown,
12
+ * includeSpanEvents?: unknown,
13
+ * includeSpanLinks?: unknown,
14
+ * linkAttributeKeys?: unknown,
15
+ * metadata?: unknown,
16
+ * resourceAttributeKeys?: unknown
17
+ * }>} ReadableSpanOptions
18
+ * @typedef {Readonly<{
19
+ * attributeKeys: KeySet,
20
+ * captureUnsampled: boolean,
21
+ * eventAttributeKeys: KeySet,
22
+ * includeSpanEvents: boolean,
23
+ * includeSpanLinks: boolean,
24
+ * linkAttributeKeys: KeySet,
25
+ * metadata: Readonly<Metadata>,
26
+ * resourceAttributeKeys: KeySet
27
+ * }>} ResolvedSpanOptions
28
+ */
29
+
30
+ const SPAN_ATTRIBUTE_KEYS = [
31
+ "db.operation.name",
32
+ "db.system",
33
+ "faas.trigger",
34
+ "graphql.operation.name",
35
+ "graphql.operation.type",
36
+ "http.method",
37
+ "http.request.method",
38
+ "http.response.status_code",
39
+ "http.route",
40
+ "http.status_code",
41
+ "messaging.operation.name",
42
+ "messaging.system",
43
+ "rpc.method",
44
+ "rpc.service",
45
+ "rpc.system"
46
+ ];
47
+ const RESOURCE_ATTRIBUTE_KEYS = [
48
+ "deployment.environment",
49
+ "deployment.environment.name",
50
+ "service.name",
51
+ "service.version",
52
+ "telemetry.sdk.language",
53
+ "telemetry.sdk.name",
54
+ "telemetry.sdk.version"
55
+ ];
56
+ /** @type {KeySet} */
57
+ const DEFAULT_SPAN_ATTRIBUTE_KEYS = new Set(SPAN_ATTRIBUTE_KEYS);
58
+ /** @type {KeySet} */
59
+ const DEFAULT_RESOURCE_ATTRIBUTE_KEYS = new Set(RESOURCE_ATTRIBUTE_KEYS);
60
+ /** @type {KeySet} */
61
+ const DEFAULT_EVENT_ATTRIBUTE_KEYS = new Set(["exception.escaped", "exception.type"]);
62
+ /** @type {KeySet} */
63
+ const DEFAULT_LINK_ATTRIBUTE_KEYS = new Set();
64
+ /** @type {KeySet} */
65
+ const TRACE_SUMMARY_METADATA_KEYS = new Set([...RESOURCE_ATTRIBUTE_KEYS, ...SPAN_ATTRIBUTE_KEYS]);
66
+ /** @type {KeySet} */
67
+ const SENSITIVE_OTEL_ATTRIBUTE_KEYS = new Set([
68
+ "code.stacktrace",
69
+ "db.statement",
70
+ "exception.message",
71
+ "exception.stacktrace",
72
+ "http.request.body",
73
+ "http.response.body",
74
+ "http.url",
75
+ "url.full"
76
+ ]);
77
+ const SENSITIVE_OTEL_ATTRIBUTE_PREFIXES = ["http.request.header.", "http.response.header."];
78
+ const SENSITIVE_OTEL_ATTRIBUTE_PATTERN = /(?:^|[._-])(?:authorization|body|cookie|credential|fragment|header|headers|payload|password|passwd|private[-_]?key|query|secret|stack|stacktrace|token)(?:[._-]|$)/iu;
79
+
80
+ /**
81
+ * @param {string} key
82
+ * @returns {boolean}
83
+ */
84
+ function isSensitiveOpenTelemetryAttributeKey(key) {
85
+ if (SENSITIVE_OTEL_ATTRIBUTE_KEYS.has(key)) {
86
+ return true;
87
+ }
88
+ if (SENSITIVE_OTEL_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix))) {
89
+ return true;
90
+ }
91
+ return SENSITIVE_OTEL_ATTRIBUTE_PATTERN.test(key);
92
+ }
93
+
94
+ /**
95
+ * @param {string} label
96
+ * @param {unknown} extraKey
97
+ * @returns {string}
98
+ */
99
+ function allowedExtraKey(label, extraKey) {
100
+ const key = requireNonEmpty(label, extraKey);
101
+ if (isSensitiveOpenTelemetryAttributeKey(key)) {
102
+ throw new SdkError("validation_error", `${label} cannot include sensitive key: ${key}`);
103
+ }
104
+ return key;
105
+ }
106
+
107
+ /**
108
+ * @param {KeySet} defaultKeys
109
+ * @param {unknown} extraKeys
110
+ * @param {string} label
111
+ * @returns {KeySet}
112
+ */
113
+ function openTelemetryAttributeKeySet(defaultKeys, extraKeys, label) {
114
+ const allowedKeys = new Set(defaultKeys);
115
+ if (extraKeys === undefined) {
116
+ return allowedKeys;
117
+ }
118
+ if (!isList(extraKeys)) {
119
+ throw new SdkError("validation_error", `${label} must be an array`);
120
+ }
121
+ for (const extraKey of extraKeys) {
122
+ allowedKeys.add(allowedExtraKey(label, extraKey));
123
+ }
124
+ return allowedKeys;
125
+ }
126
+
127
+ /**
128
+ * @param {ReadableSpanOptions} [options]
129
+ * @returns {ResolvedSpanOptions}
130
+ */
131
+ function resolveOpenTelemetryReadableSpanOptions(options = {}) {
132
+ return {
133
+ attributeKeys: openTelemetryAttributeKeySet(
134
+ DEFAULT_SPAN_ATTRIBUTE_KEYS,
135
+ options.attributeKeys,
136
+ "OpenTelemetry attributeKeys"
137
+ ),
138
+ captureUnsampled: options.captureUnsampled === true,
139
+ eventAttributeKeys: openTelemetryAttributeKeySet(
140
+ DEFAULT_EVENT_ATTRIBUTE_KEYS,
141
+ options.eventAttributeKeys,
142
+ "OpenTelemetry eventAttributeKeys"
143
+ ),
144
+ includeSpanEvents: options.includeSpanEvents !== false,
145
+ includeSpanLinks: options.includeSpanLinks !== false,
146
+ linkAttributeKeys: openTelemetryAttributeKeySet(
147
+ DEFAULT_LINK_ATTRIBUTE_KEYS,
148
+ options.linkAttributeKeys,
149
+ "OpenTelemetry linkAttributeKeys"
150
+ ),
151
+ metadata: compactMetadata(options.metadata),
152
+ resourceAttributeKeys: openTelemetryAttributeKeySet(
153
+ DEFAULT_RESOURCE_ATTRIBUTE_KEYS,
154
+ options.resourceAttributeKeys,
155
+ "OpenTelemetry resourceAttributeKeys"
156
+ )
157
+ };
158
+ }
159
+
160
+ /**
161
+ * @param {unknown} attributes
162
+ * @param {KeySet} allowedKeys
163
+ * @returns {Metadata}
164
+ */
165
+ function openTelemetrySelectedMetadata(attributes, allowedKeys) {
166
+ /** @type {Metadata} */
167
+ const metadata = {};
168
+ if (!isRecord(attributes)) {
169
+ return metadata;
170
+ }
171
+ for (const [key, value] of Object.entries(attributes)) {
172
+ if (allowedKeys.has(key) && !isSensitiveOpenTelemetryAttributeKey(key) && isMetadataValue(value)) {
173
+ metadata[key] = value;
174
+ }
175
+ }
176
+ return metadata;
177
+ }
178
+
179
+ module.exports = {
180
+ TRACE_SUMMARY_METADATA_KEYS,
181
+ openTelemetrySelectedMetadata,
182
+ resolveOpenTelemetryReadableSpanOptions
183
+ };
@@ -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 };