@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/event-store.cjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const { isObjectLike } = require("./error-details.cjs");
|
|
2
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
3
|
+
const {
|
|
4
|
+
invalidStoredRecord,
|
|
5
|
+
normalizeStoredRecord,
|
|
6
|
+
requireSynchronousStoreResult,
|
|
7
|
+
restoreStoredEvent
|
|
8
|
+
} = require("./stored-events.cjs");
|
|
9
|
+
const { isRecord } = require("./validation.cjs");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @import { SdkEvent } from "./event-validation.cjs"
|
|
13
|
+
* @import { StoredRecord } from "./stored-events.cjs"
|
|
14
|
+
* @import { Callable, DeepReadonly } from "./validation.cjs"
|
|
15
|
+
* @typedef {Readonly<{
|
|
16
|
+
* loadClientReport: () => unknown,
|
|
17
|
+
* saveClientReport: (state: unknown) => unknown
|
|
18
|
+
* }>} ClientReportStore
|
|
19
|
+
* @typedef {Readonly<{
|
|
20
|
+
* load: () => unknown,
|
|
21
|
+
* append: (record: Readonly<StoredRecord>) => unknown,
|
|
22
|
+
* acknowledge: (count: number) => unknown,
|
|
23
|
+
* purge: () => unknown,
|
|
24
|
+
* close: () => unknown,
|
|
25
|
+
* remove?: (indexes: readonly number[]) => unknown,
|
|
26
|
+
* loadClientReport?: () => unknown,
|
|
27
|
+
* saveClientReport?: (state: unknown) => unknown
|
|
28
|
+
* }>} EventStore
|
|
29
|
+
* @typedef {Readonly<{
|
|
30
|
+
* acknowledge: (count: unknown) => unknown,
|
|
31
|
+
* append: (record: unknown) => unknown,
|
|
32
|
+
* byteCount: () => unknown,
|
|
33
|
+
* close: () => unknown,
|
|
34
|
+
* eventBytesAt: (index: number) => unknown,
|
|
35
|
+
* events: () => unknown,
|
|
36
|
+
* length: () => unknown,
|
|
37
|
+
* serializedAt: (index: number) => unknown,
|
|
38
|
+
* remove?: (indexes: readonly number[]) => unknown,
|
|
39
|
+
* loadClientReport?: () => unknown,
|
|
40
|
+
* saveClientReport?: (state: unknown) => unknown
|
|
41
|
+
* }>} EventQueueBackend
|
|
42
|
+
* @typedef {Readonly<{
|
|
43
|
+
* batchPrefixBytes: number,
|
|
44
|
+
* batchSuffixBytes: number,
|
|
45
|
+
* maxBatchBytes: number,
|
|
46
|
+
* maxQueueBytes: number,
|
|
47
|
+
* maxQueueSize: number
|
|
48
|
+
* }>} StoreLimits
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const EVENT_STORE_METHODS = Object.freeze(["load", "append", "acknowledge", "purge", "close"]);
|
|
52
|
+
const EVENT_QUEUE_METHODS = Object.freeze(["acknowledge", "append", "byteCount", "close", "eventBytesAt", "events", "length", "serializedAt"]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {unknown} store
|
|
56
|
+
* @returns {store is ClientReportStore}
|
|
57
|
+
*/
|
|
58
|
+
function isClientReportStore(store) {
|
|
59
|
+
return isObjectLike(store)
|
|
60
|
+
&& typeof Reflect.get(store, "loadClientReport") === "function"
|
|
61
|
+
&& typeof Reflect.get(store, "saveClientReport") === "function";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Readonly<Record<string, unknown>>} value
|
|
66
|
+
* @param {readonly string[]} methods
|
|
67
|
+
* @returns {string | undefined}
|
|
68
|
+
*/
|
|
69
|
+
function missingMethod(value, methods) {
|
|
70
|
+
return methods.find((method) => typeof value[method] !== "function");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Readonly<Record<string, unknown>>} value
|
|
75
|
+
* @returns {value is EventStore}
|
|
76
|
+
*/
|
|
77
|
+
function isEventStore(value) {
|
|
78
|
+
return missingMethod(value, EVENT_STORE_METHODS) === undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {unknown} eventStore
|
|
83
|
+
* @returns {EventStore | undefined}
|
|
84
|
+
*/
|
|
85
|
+
function validateEventStore(eventStore) {
|
|
86
|
+
if (eventStore === undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (!isRecord(eventStore)) {
|
|
90
|
+
throw new SdkError("validation_error", "eventStore must be an object");
|
|
91
|
+
}
|
|
92
|
+
const missing = missingMethod(eventStore, EVENT_STORE_METHODS);
|
|
93
|
+
if (missing !== undefined) {
|
|
94
|
+
throw new SdkError("validation_error", `eventStore.${missing} must be a function`);
|
|
95
|
+
}
|
|
96
|
+
return isEventStore(eventStore) ? eventStore : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {unknown} queue
|
|
101
|
+
* @returns {queue is EventQueueBackend}
|
|
102
|
+
*/
|
|
103
|
+
function isEventQueueBackend(queue) {
|
|
104
|
+
return isRecord(queue) && missingMethod(queue, EVENT_QUEUE_METHODS) === undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {EventQueueBackend} queue
|
|
109
|
+
* @returns {unknown[]}
|
|
110
|
+
*/
|
|
111
|
+
function loadQueueRecords(queue) {
|
|
112
|
+
const events = queue.events();
|
|
113
|
+
const count = queue.length();
|
|
114
|
+
if (!Array.isArray(events) || events.length !== count) {
|
|
115
|
+
throw invalidStoredRecord();
|
|
116
|
+
}
|
|
117
|
+
/** @type {readonly unknown[]} */
|
|
118
|
+
const loaded = events;
|
|
119
|
+
return loaded.map((event, index) => ({
|
|
120
|
+
event,
|
|
121
|
+
eventBytes: queue.eventBytesAt(index),
|
|
122
|
+
serializedEvent: queue.serializedAt(index)
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {EventQueueBackend} queue
|
|
128
|
+
* @returns {Partial<EventStore>}
|
|
129
|
+
*/
|
|
130
|
+
function optionalQueueMethods(queue) {
|
|
131
|
+
const { remove } = queue;
|
|
132
|
+
return {
|
|
133
|
+
...(typeof remove === "function" ? { remove: (indexes) => queue.remove?.(indexes) } : {}),
|
|
134
|
+
...(isClientReportStore(queue)
|
|
135
|
+
? { loadClientReport: () => queue.loadClientReport(), saveClientReport: (state) => queue.saveClientReport(state) }
|
|
136
|
+
: {})
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {Callable} eventQueueFactory
|
|
142
|
+
* @param {StoreLimits} limits
|
|
143
|
+
* @returns {EventStore}
|
|
144
|
+
*/
|
|
145
|
+
function createEventStoreFromQueueFactory(eventQueueFactory, limits) {
|
|
146
|
+
/** @type {unknown} */
|
|
147
|
+
const queue = Reflect.apply(eventQueueFactory, undefined, [{ ...limits, restoreEvent: restoreStoredEvent }]);
|
|
148
|
+
if (!isEventQueueBackend(queue)) {
|
|
149
|
+
throw new SdkError("validation_error", "event queue factory returned an invalid queue");
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
load: () => loadQueueRecords(queue),
|
|
153
|
+
|
|
154
|
+
append: (record) => queue.append({
|
|
155
|
+
event: record.event,
|
|
156
|
+
|
|
157
|
+
byteCount: record.eventBytes,
|
|
158
|
+
|
|
159
|
+
serialized: record.serializedEvent
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
acknowledge: (count) => queue.acknowledge(count),
|
|
163
|
+
|
|
164
|
+
purge: () => queue.acknowledge(queue.length()),
|
|
165
|
+
|
|
166
|
+
close: () => queue.close(),
|
|
167
|
+
...optionalQueueMethods(queue)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class RecoveredEvents {
|
|
172
|
+
/**
|
|
173
|
+
* @param {StoreLimits} limits
|
|
174
|
+
*/
|
|
175
|
+
constructor(limits) {
|
|
176
|
+
this.limits = limits;
|
|
177
|
+
/** @type {DeepReadonly<SdkEvent>[]} */
|
|
178
|
+
this.events = [];
|
|
179
|
+
this.queuedEventBytes = 0;
|
|
180
|
+
/** @type {number[]} */
|
|
181
|
+
this.serializedEventBytes = [];
|
|
182
|
+
/** @type {string[]} */
|
|
183
|
+
this.serializedEvents = [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Readonly<StoredRecord>} normalized
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
add(normalized) {
|
|
191
|
+
const { limits } = this;
|
|
192
|
+
if (limits.batchPrefixBytes + normalized.eventBytes + limits.batchSuffixBytes > limits.maxBatchBytes) {
|
|
193
|
+
throw new SdkError("persistence_error", "recovered event exceeds maxBatchBytes");
|
|
194
|
+
}
|
|
195
|
+
this.queuedEventBytes += normalized.eventBytes;
|
|
196
|
+
if (this.queuedEventBytes > limits.maxQueueBytes) {
|
|
197
|
+
throw new SdkError("persistence_error", "recovered event bytes exceed maxQueueBytes");
|
|
198
|
+
}
|
|
199
|
+
this.events.push(normalized.event);
|
|
200
|
+
this.serializedEvents.push(normalized.serializedEvent);
|
|
201
|
+
this.serializedEventBytes.push(normalized.eventBytes);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {EventStore} eventStore
|
|
207
|
+
* @param {number} maxQueueSize
|
|
208
|
+
* @returns {readonly unknown[]}
|
|
209
|
+
*/
|
|
210
|
+
function storedRecords(eventStore, maxQueueSize) {
|
|
211
|
+
const records = requireSynchronousStoreResult("load", eventStore.load());
|
|
212
|
+
if (!Array.isArray(records)) {
|
|
213
|
+
throw invalidStoredRecord();
|
|
214
|
+
}
|
|
215
|
+
if (records.length > maxQueueSize) {
|
|
216
|
+
throw new SdkError("persistence_error", "recovered event count exceeds maxQueueSize");
|
|
217
|
+
}
|
|
218
|
+
/** @type {readonly unknown[]} */
|
|
219
|
+
const loaded = records;
|
|
220
|
+
return loaded;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {EventStore | undefined} eventStore
|
|
225
|
+
* @param {StoreLimits} limits
|
|
226
|
+
* @returns {RecoveredEvents}
|
|
227
|
+
*/
|
|
228
|
+
function loadStoredEvents(eventStore, limits) {
|
|
229
|
+
const recovered = new RecoveredEvents(limits);
|
|
230
|
+
const records = eventStore === undefined ? [] : storedRecords(eventStore, limits.maxQueueSize);
|
|
231
|
+
for (const record of records) {
|
|
232
|
+
recovered.add(normalizeStoredRecord(record));
|
|
233
|
+
}
|
|
234
|
+
return recovered;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { createEventStoreFromQueueFactory, isClientReportStore, loadStoredEvents, validateEventStore };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const { withMetadata } = require("./event-metadata.cjs");
|
|
2
|
+
const { validateIssueDiagnostics } = require("./issue-diagnostics.cjs");
|
|
3
|
+
const { validateIssueStackFrames, validateNativeStackFrames } = require("./issue-stack.cjs");
|
|
4
|
+
const { validateMetric } = require("./metric-validation.cjs");
|
|
5
|
+
const { validateSpan } = require("./span-validation.cjs");
|
|
6
|
+
const { optionalEntry, requireAllowedValue, requireNonEmpty } = require("./validation.cjs");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @import {
|
|
10
|
+
* ActionAttributes,
|
|
11
|
+
* IssueStackFrame,
|
|
12
|
+
* NativeStackFrame,
|
|
13
|
+
* Severity,
|
|
14
|
+
* SeverityInput,
|
|
15
|
+
* SpanEventSummary,
|
|
16
|
+
* SpanLinkSummary,
|
|
17
|
+
* TelemetryContext
|
|
18
|
+
* } from "./index.cjs"
|
|
19
|
+
* @import { IssueDiagnostics } from "./issue-diagnostics.cjs"
|
|
20
|
+
* @typedef {"release" | "environment" | "issue" | "log" | "span" | "action" | "metric"} EventType
|
|
21
|
+
* @typedef {IssueDiagnostics & {
|
|
22
|
+
* [key: string]: unknown,
|
|
23
|
+
* context?: TelemetryContext,
|
|
24
|
+
* events?: SpanEventSummary[],
|
|
25
|
+
* links?: SpanLinkSummary[],
|
|
26
|
+
* metadata?: Record<string, unknown>,
|
|
27
|
+
* nativeStackFrames?: NativeStackFrame[],
|
|
28
|
+
* stackFrames?: IssueStackFrame[]
|
|
29
|
+
* }} EventAttributes
|
|
30
|
+
* @typedef {{ type: EventType, id: string, timestamp: string, attributes: EventAttributes }} SdkEvent
|
|
31
|
+
* @typedef {(attributes: Readonly<Record<string, unknown>>) => EventAttributes} EventValidator
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** @type {ReadonlyMap<SeverityInput, Severity>} */
|
|
35
|
+
const SEVERITY_ALIASES = new Map([
|
|
36
|
+
["trace", "info"],
|
|
37
|
+
["debug", "info"],
|
|
38
|
+
["info", "info"],
|
|
39
|
+
["warn", "warning"],
|
|
40
|
+
["warning", "warning"],
|
|
41
|
+
["error", "error"],
|
|
42
|
+
["fatal", "critical"],
|
|
43
|
+
["critical", "critical"]
|
|
44
|
+
]);
|
|
45
|
+
const SEVERITY_VALUES = new Set(SEVERITY_ALIASES.keys());
|
|
46
|
+
/** @type {ReadonlySet<ActionAttributes["status"]>} */
|
|
47
|
+
const ACTION_STATUSES = new Set(["queued", "running", "success", "failure"]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} label
|
|
51
|
+
* @param {unknown} value
|
|
52
|
+
* @returns {Severity}
|
|
53
|
+
*/
|
|
54
|
+
function normalizeSeverity(label, value) {
|
|
55
|
+
const severity = requireAllowedValue(label, value, SEVERITY_VALUES);
|
|
56
|
+
return SEVERITY_ALIASES.get(severity) ?? "info";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
61
|
+
* @returns {EventAttributes}
|
|
62
|
+
*/
|
|
63
|
+
function validateRelease(attributes) {
|
|
64
|
+
const version = requireNonEmpty("release version", attributes["version"]);
|
|
65
|
+
const commit = attributes["commit"] === undefined ? undefined : requireNonEmpty("release commit", attributes["commit"]);
|
|
66
|
+
return withMetadata({
|
|
67
|
+
version,
|
|
68
|
+
...optionalEntry("commit", commit),
|
|
69
|
+
...optionalEntry("notes", attributes["notes"])
|
|
70
|
+
}, attributes["metadata"], attributes["context"]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
75
|
+
* @returns {EventAttributes}
|
|
76
|
+
*/
|
|
77
|
+
function validateEnvironment(attributes) {
|
|
78
|
+
const name = requireNonEmpty("environment name", attributes["name"]);
|
|
79
|
+
return withMetadata({
|
|
80
|
+
name,
|
|
81
|
+
...optionalEntry("region", attributes["region"])
|
|
82
|
+
}, attributes["metadata"], attributes["context"]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
87
|
+
* @returns {EventAttributes}
|
|
88
|
+
*/
|
|
89
|
+
function validateIssue(attributes) {
|
|
90
|
+
const title = requireNonEmpty("issue title", attributes["title"]);
|
|
91
|
+
const level = normalizeSeverity("issue level", attributes["level"]);
|
|
92
|
+
const stackFrames = validateIssueStackFrames(attributes["stackFrames"]);
|
|
93
|
+
const nativeStackFrames = validateNativeStackFrames(attributes["nativeStackFrames"]);
|
|
94
|
+
return withMetadata({
|
|
95
|
+
title,
|
|
96
|
+
|
|
97
|
+
level,
|
|
98
|
+
...optionalEntry("message", attributes["message"]),
|
|
99
|
+
...optionalEntry("stackFrames", stackFrames),
|
|
100
|
+
...optionalEntry("nativeStackFrames", nativeStackFrames),
|
|
101
|
+
...validateIssueDiagnostics(attributes)
|
|
102
|
+
}, attributes["metadata"], attributes["context"]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
107
|
+
* @returns {EventAttributes}
|
|
108
|
+
*/
|
|
109
|
+
function validateLog(attributes) {
|
|
110
|
+
const message = requireNonEmpty("log message", attributes["message"]);
|
|
111
|
+
const level = normalizeSeverity("log level", attributes["level"]);
|
|
112
|
+
return withMetadata({
|
|
113
|
+
message,
|
|
114
|
+
|
|
115
|
+
level,
|
|
116
|
+
...optionalEntry("logger", attributes["logger"])
|
|
117
|
+
}, attributes["metadata"], attributes["context"]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
122
|
+
* @returns {EventAttributes}
|
|
123
|
+
*/
|
|
124
|
+
function validateAction(attributes) {
|
|
125
|
+
const name = requireNonEmpty("action name", attributes["name"]);
|
|
126
|
+
const status = requireAllowedValue("action status", attributes["status"], ACTION_STATUSES);
|
|
127
|
+
return withMetadata({ name, status }, attributes["metadata"], attributes["context"]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @type {ReadonlyMap<string, EventValidator>} */
|
|
131
|
+
const EVENT_VALIDATORS = new Map([
|
|
132
|
+
["release", validateRelease],
|
|
133
|
+
["environment", validateEnvironment],
|
|
134
|
+
["issue", validateIssue],
|
|
135
|
+
["log", validateLog],
|
|
136
|
+
["span", validateSpan],
|
|
137
|
+
["action", validateAction],
|
|
138
|
+
["metric", validateMetric]
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {unknown} type
|
|
143
|
+
* @returns {type is EventType}
|
|
144
|
+
*/
|
|
145
|
+
function isEventType(type) {
|
|
146
|
+
return typeof type === "string" && EVENT_VALIDATORS.has(type);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
ACTION_STATUSES,
|
|
151
|
+
EVENT_VALIDATORS,
|
|
152
|
+
isEventType,
|
|
153
|
+
normalizeSeverity,
|
|
154
|
+
validateAction,
|
|
155
|
+
validateEnvironment,
|
|
156
|
+
validateIssue,
|
|
157
|
+
validateLog,
|
|
158
|
+
validateRelease
|
|
159
|
+
};
|
|
@@ -1,87 +1,98 @@
|
|
|
1
|
-
let sdk;
|
|
2
|
-
try {
|
|
3
|
-
sdk = require("@logbrew/sdk");
|
|
4
|
-
} catch (error) {
|
|
5
|
-
if (error && error.code === "MODULE_NOT_FOUND") {
|
|
6
|
-
sdk = require("../index.cjs");
|
|
7
|
-
} else {
|
|
8
|
-
throw error;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
1
|
const {
|
|
2
|
+
LogBrewClient,
|
|
3
|
+
RecordingTransport,
|
|
13
4
|
createNetworkMilestoneAttributes,
|
|
14
5
|
createProductActionAttributes,
|
|
15
|
-
createTraceparentHeaders
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
createTraceparentHeaders
|
|
7
|
+
} = require("../index.cjs");
|
|
8
|
+
const { inspect } = require("node:util");
|
|
9
|
+
const process = require("node:process");
|
|
19
10
|
|
|
20
11
|
const traceId = "4bf92f3577b34da6a3ce929d0e0e4736";
|
|
21
12
|
const spanId = "b7ad6b7169203331";
|
|
22
13
|
const sessionId = "sess_checkout_001";
|
|
23
14
|
const timestamp = "2026-06-02T10:00:00Z";
|
|
15
|
+
const RETAINED_EVENTS = 2;
|
|
24
16
|
|
|
25
17
|
const client = LogBrewClient.create({
|
|
26
18
|
apiKey: "LOGBREW_API_KEY",
|
|
27
|
-
sdkName: "checkout-agent-timeline",
|
|
28
|
-
sdkVersion: "1.0.0",
|
|
29
19
|
eventFilter(event) {
|
|
30
20
|
return !(event.type === "log" && event.attributes.level === "info");
|
|
31
|
-
}
|
|
21
|
+
},
|
|
22
|
+
sdkName: "checkout-agent-timeline",
|
|
23
|
+
sdkVersion: "1.0.0"
|
|
32
24
|
});
|
|
33
25
|
|
|
34
26
|
client.action("evt_checkout_started", timestamp, createProductActionAttributes({
|
|
27
|
+
funnel: "checkout",
|
|
28
|
+
metadata: {
|
|
29
|
+
service: "checkout",
|
|
30
|
+
|
|
31
|
+
plan: "pro"
|
|
32
|
+
},
|
|
35
33
|
name: "checkout.started",
|
|
36
|
-
status: "success",
|
|
37
|
-
sessionId,
|
|
38
|
-
traceId,
|
|
39
34
|
routeTemplate: "/checkout/:step?coupon=private#payment",
|
|
40
|
-
|
|
35
|
+
sessionId,
|
|
36
|
+
status: "success",
|
|
41
37
|
step: "start",
|
|
42
|
-
|
|
38
|
+
traceId
|
|
43
39
|
}));
|
|
44
40
|
|
|
45
41
|
client.action("evt_payment_api", "2026-06-02T10:00:01Z", createNetworkMilestoneAttributes({
|
|
46
|
-
routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
|
|
47
|
-
method: "POST",
|
|
48
|
-
statusCode: 503,
|
|
49
42
|
durationMs: 241.5,
|
|
43
|
+
metadata: {
|
|
44
|
+
service: "payments",
|
|
45
|
+
|
|
46
|
+
retryable: true
|
|
47
|
+
},
|
|
48
|
+
method: "POST",
|
|
49
|
+
routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
|
|
50
50
|
sessionId,
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
statusCode: 503,
|
|
52
|
+
traceId
|
|
53
53
|
}));
|
|
54
54
|
|
|
55
55
|
client.log("evt_debug_noise", "2026-06-02T10:00:02Z", {
|
|
56
|
-
message: "debug heartbeat",
|
|
57
56
|
level: "info",
|
|
58
|
-
logger: "checkout"
|
|
57
|
+
logger: "checkout",
|
|
58
|
+
message: "debug heartbeat"
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const headers = createTraceparentHeaders({
|
|
62
|
-
traceId,
|
|
63
62
|
spanId,
|
|
64
|
-
traceFlags: "01"
|
|
63
|
+
traceFlags: "01",
|
|
64
|
+
traceId
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
const preview = client.previewJson();
|
|
68
68
|
if (preview.includes("card=private") || preview.includes("coupon=private") || preview.includes("#payment")) {
|
|
69
69
|
throw new Error("agent timeline leaked query or hash metadata");
|
|
70
70
|
}
|
|
71
|
-
if (client.pendingEvents() !==
|
|
72
|
-
throw new Error(`expected two retained timeline events, got ${client.pendingEvents()}`);
|
|
71
|
+
if (client.pendingEvents() !== RETAINED_EVENTS) {
|
|
72
|
+
throw new Error(`expected two retained timeline events, got ${String(client.pendingEvents())}`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
process.stdout.write(`${preview}\n`);
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
/**
|
|
78
|
+
* @returns {Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
async function deliver() {
|
|
81
|
+
try {
|
|
82
|
+
const response = await client.shutdown(RecordingTransport.alwaysAccept());
|
|
83
|
+
process.stderr.write(`${JSON.stringify({
|
|
84
|
+
ok: true,
|
|
85
|
+
|
|
86
|
+
events: RETAINED_EVENTS,
|
|
87
|
+
|
|
88
|
+
traceparent: headers.traceparent,
|
|
89
|
+
|
|
90
|
+
status: response.statusCode
|
|
91
|
+
})}\n`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
process.stderr.write(`${inspect(error)}\n`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
void deliver();
|
|
@@ -1,79 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
throw error;
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const {
|
|
1
|
+
import {
|
|
2
|
+
LogBrewClient,
|
|
3
|
+
RecordingTransport,
|
|
9
4
|
createNetworkMilestoneAttributes,
|
|
10
5
|
createProductActionAttributes,
|
|
11
|
-
createTraceparentHeaders
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} = sdk;
|
|
6
|
+
createTraceparentHeaders
|
|
7
|
+
} from "../index.js";
|
|
8
|
+
import process from "node:process";
|
|
15
9
|
|
|
16
10
|
const traceId = "4bf92f3577b34da6a3ce929d0e0e4736";
|
|
17
11
|
const spanId = "b7ad6b7169203331";
|
|
18
12
|
const sessionId = "sess_checkout_001";
|
|
19
13
|
const timestamp = "2026-06-02T10:00:00Z";
|
|
14
|
+
const RETAINED_EVENTS = 2;
|
|
20
15
|
|
|
21
16
|
const client = LogBrewClient.create({
|
|
22
17
|
apiKey: "LOGBREW_API_KEY",
|
|
23
|
-
sdkName: "checkout-agent-timeline",
|
|
24
|
-
sdkVersion: "1.0.0",
|
|
25
18
|
eventFilter(event) {
|
|
26
19
|
return !(event.type === "log" && event.attributes.level === "info");
|
|
27
|
-
}
|
|
20
|
+
},
|
|
21
|
+
sdkName: "checkout-agent-timeline",
|
|
22
|
+
sdkVersion: "1.0.0"
|
|
28
23
|
});
|
|
29
24
|
|
|
30
25
|
client.action("evt_checkout_started", timestamp, createProductActionAttributes({
|
|
26
|
+
funnel: "checkout",
|
|
27
|
+
metadata: {
|
|
28
|
+
service: "checkout",
|
|
29
|
+
|
|
30
|
+
plan: "pro"
|
|
31
|
+
},
|
|
31
32
|
name: "checkout.started",
|
|
32
|
-
status: "success",
|
|
33
|
-
sessionId,
|
|
34
|
-
traceId,
|
|
35
33
|
routeTemplate: "/checkout/:step?coupon=private#payment",
|
|
36
|
-
|
|
34
|
+
sessionId,
|
|
35
|
+
status: "success",
|
|
37
36
|
step: "start",
|
|
38
|
-
|
|
37
|
+
traceId
|
|
39
38
|
}));
|
|
40
39
|
|
|
41
40
|
client.action("evt_payment_api", "2026-06-02T10:00:01Z", createNetworkMilestoneAttributes({
|
|
42
|
-
routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
|
|
43
|
-
method: "POST",
|
|
44
|
-
statusCode: 503,
|
|
45
41
|
durationMs: 241.5,
|
|
42
|
+
metadata: {
|
|
43
|
+
service: "payments",
|
|
44
|
+
|
|
45
|
+
retryable: true
|
|
46
|
+
},
|
|
47
|
+
method: "POST",
|
|
48
|
+
routeTemplate: "https://api.example.invalid/payments/123?card=private#retry",
|
|
46
49
|
sessionId,
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
statusCode: 503,
|
|
51
|
+
traceId
|
|
49
52
|
}));
|
|
50
53
|
|
|
51
54
|
client.log("evt_debug_noise", "2026-06-02T10:00:02Z", {
|
|
52
|
-
message: "debug heartbeat",
|
|
53
55
|
level: "info",
|
|
54
|
-
logger: "checkout"
|
|
56
|
+
logger: "checkout",
|
|
57
|
+
message: "debug heartbeat"
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
const headers = createTraceparentHeaders({
|
|
58
|
-
traceId,
|
|
59
61
|
spanId,
|
|
60
|
-
traceFlags: "01"
|
|
62
|
+
traceFlags: "01",
|
|
63
|
+
traceId
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
const preview = client.previewJson();
|
|
64
67
|
if (preview.includes("card=private") || preview.includes("coupon=private") || preview.includes("#payment")) {
|
|
65
68
|
throw new Error("agent timeline leaked query or hash metadata");
|
|
66
69
|
}
|
|
67
|
-
if (client.pendingEvents() !==
|
|
68
|
-
throw new Error(`expected two retained timeline events, got ${client.pendingEvents()}`);
|
|
70
|
+
if (client.pendingEvents() !== RETAINED_EVENTS) {
|
|
71
|
+
throw new Error(`expected two retained timeline events, got ${String(client.pendingEvents())}`);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
process.stdout.write(`${preview}\n`);
|
|
72
75
|
|
|
73
76
|
const response = await client.shutdown(RecordingTransport.alwaysAccept());
|
|
74
|
-
|
|
77
|
+
process.stderr.write(`${JSON.stringify({
|
|
75
78
|
ok: true,
|
|
76
|
-
|
|
79
|
+
|
|
80
|
+
events: RETAINED_EVENTS,
|
|
81
|
+
|
|
77
82
|
traceparent: headers.traceparent,
|
|
83
|
+
|
|
78
84
|
status: response.statusCode
|
|
79
|
-
}));
|
|
85
|
+
})}\n`);
|