@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,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 };
|