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