@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.
Files changed (90) 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 +30 -2810
  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-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
package/winston.cjs CHANGED
@@ -1,323 +1,223 @@
1
1
  const { Writable } = require("node:stream");
2
2
 
3
- const { LogBrewClient, SdkError } = require("./core.cjs");
4
- const {
5
- compactMetadata,
6
- isMetadataValue,
7
- traceFromProvider,
8
- traceMetadataFromLogContext
9
- } = require("./log-context.cjs");
3
+ const { callableOrUndefined, captureSettings, settledFlush } = require("./capture-support.cjs");
4
+ const { LogBrewClient } = require("./client.cjs");
5
+ const { traceFromProvider } = require("./log-context.cjs");
6
+ const { NULL_VALUE } = require("./null-value.cjs");
7
+ const { SdkError } = require("./sdk-error.cjs");
8
+ const { isObjectValue, isRecord, isTruthy, optionalProperty } = require("./validation.cjs");
9
+ const { logAttributesFromWinstonInfo, timestampFromWinstonInfo, winstonMessage } = require("./winston-info.cjs");
10
+
11
+ /**
12
+ * @import { IssueBreadcrumbLevel } from "./index.cjs"
13
+ * @import { CaptureClient } from "./capture-support.cjs"
14
+ * @typedef {CaptureClient & Readonly<{
15
+ * breadcrumbProvider: unknown,
16
+ * addBreadcrumb: (breadcrumb: unknown) => unknown
17
+ * }>} WinstonClient
18
+ * @typedef {{ type: "debug", category: string, level: IssueBreadcrumbLevel, message: string }} WinstonBreadcrumb
19
+ */
10
20
 
11
- // Runtime packages with request scopes (such as @logbrew/node) register a recorder that keeps
12
- // breadcrumbs with the active request instead of the whole client.
13
21
  const BREADCRUMB_RECORDER = Symbol.for("@logbrew/sdk.breadcrumbRecorder");
14
22
  const MAX_BREADCRUMB_MESSAGE_LENGTH = 512;
15
- const WINSTON_RESERVED_FIELDS = new Set([
16
- "level",
17
- "message",
18
- "timestamp",
19
- "time",
20
- "err",
21
- "error",
22
- "stack"
23
+ const FIRST_CHARACTER = 0;
24
+ const NO_PENDING_EVENTS = 0;
25
+ /** @type {ReadonlyMap<string, IssueBreadcrumbLevel>} */
26
+ const BREADCRUMB_LEVELS = new Map([
27
+ ["error", "error"],
28
+ ["crit", "error"],
29
+ ["critical", "error"],
30
+ ["fatal", "error"],
31
+ ["alert", "error"],
32
+ ["emerg", "error"],
33
+ ["warn", "warning"],
34
+ ["warning", "warning"],
35
+ ["verbose", "debug"],
36
+ ["debug", "debug"],
37
+ ["silly", "debug"]
23
38
  ]);
24
39
 
25
- function createLogBrewWinstonTransport(config) {
26
- if (!config || typeof config !== "object") {
27
- throw new SdkError("validation_error", "Winston transport config must be an object");
28
- }
29
-
30
- const client = config.client;
31
- if (!(client instanceof LogBrewClient)) {
32
- throw new SdkError("validation_error", "Winston transport client must be a LogBrewClient");
33
- }
34
-
35
- const transport = config.transport;
36
- const breadcrumbs = config.breadcrumbs !== false;
37
- const flushOnWrite = config.flushOnWrite === true;
38
- const includeErrorStack = config.includeErrorStack === true;
39
- const logger = config.logger ?? "winston";
40
- const metadata = compactMetadata(config.metadata);
41
- const timestamp = typeof config.timestamp === "function"
42
- ? config.timestamp
43
- : () => new Date().toISOString();
44
- const eventIdPrefix = config.eventIdPrefix ?? "winston";
45
- const onError = typeof config.onError === "function" ? config.onError : () => {};
46
- const traceProvider = typeof config.traceProvider === "function" ? config.traceProvider : null;
47
- const state = {
48
- captured: 0,
49
- pendingFlush: Promise.resolve(null)
50
- };
40
+ /**
41
+ * @param {unknown} level
42
+ * @returns {IssueBreadcrumbLevel}
43
+ */
44
+ function winstonBreadcrumbLevel(level) {
45
+ return BREADCRUMB_LEVELS.get(String(level).toLowerCase()) ?? "info";
46
+ }
51
47
 
52
- const winstonTransport = new Writable({
53
- objectMode: true,
54
- write(info, _encoding, callback) {
55
- try {
56
- if (breadcrumbs) {
57
- recordWinstonBreadcrumb(client, info);
58
- }
59
- captureWinstonInfo({
60
- client,
61
- eventIdPrefix,
62
- flushOnWrite,
63
- includeErrorStack,
64
- info,
65
- logger,
66
- metadata,
67
- onError,
68
- state,
69
- timestamp,
70
- traceProvider,
71
- transport
72
- });
73
- } catch (error) {
74
- onError(error);
75
- } finally {
76
- callback();
48
+ class WinstonCapture {
49
+ /**
50
+ * @param {Readonly<Record<string, unknown>>} config
51
+ * @param {WinstonClient} client
52
+ */
53
+ constructor(config, client) {
54
+ this.client = client;
55
+ this.breadcrumbs = config["breadcrumbs"] !== false;
56
+ this.settings = captureSettings(config, "flushOnWrite", "winston");
57
+ this.traceProvider = callableOrUndefined(config["traceProvider"]);
58
+ this.captured = 0;
59
+ /** @type {Promise<unknown>} */
60
+ this.pendingFlush = Promise.resolve(NULL_VALUE);
61
+ }
62
+
63
+ /**
64
+ * @param {unknown} info
65
+ * @returns {void}
66
+ */
67
+ write(info) {
68
+ try {
69
+ if (this.breadcrumbs) {
70
+ this.recordBreadcrumb(info);
77
71
  }
72
+ this.capture(info);
73
+ } catch (error) {
74
+ this.settings.onError(error);
78
75
  }
79
- });
76
+ }
80
77
 
81
- winstonTransport.log = function log(info, callback) {
82
- this.write(info);
83
- if (typeof callback === "function") {
84
- callback();
78
+ /**
79
+ * @param {unknown} info
80
+ * @returns {boolean}
81
+ */
82
+ recordBreadcrumb(info) {
83
+ if (!isRecord(info) || info["silent"] === true) {
84
+ return false;
85
85
  }
86
- };
87
- winstonTransport.flush = async () => {
88
- if (transport && client.pendingEvents() > 0) {
89
- state.pendingFlush = Promise.resolve(client.flush(transport)).catch((error) => {
90
- onError(error);
91
- return null;
92
- });
86
+ /** @type {WinstonBreadcrumb} */
87
+ const breadcrumb = {
88
+ type: "debug",
89
+
90
+ category: "log.winston",
91
+ level: winstonBreadcrumbLevel(info["level"]),
92
+ message: [...winstonMessage(info)].slice(FIRST_CHARACTER, MAX_BREADCRUMB_MESSAGE_LENGTH).join("")
93
+ };
94
+ /** @type {unknown} */
95
+ const recorder = isTruthy(this.client.breadcrumbProvider) ? Reflect.get(globalThis, BREADCRUMB_RECORDER) : undefined;
96
+ try {
97
+ if (typeof recorder === "function") {
98
+ Reflect.apply(recorder, undefined, [breadcrumb]);
99
+ } else {
100
+ this.client.addBreadcrumb(breadcrumb);
101
+ }
102
+ return true;
103
+ } catch {
104
+ return false;
93
105
  }
94
- return state.pendingFlush;
95
- };
96
- if (typeof config.level === "string" && config.level.trim() !== "") {
97
- winstonTransport.level = config.level;
98
106
  }
99
- if (config.name !== undefined) {
100
- winstonTransport.name = String(config.name);
101
- }
102
- if (config.silent === true) {
103
- winstonTransport.silent = true;
104
- }
105
- if (config.handleExceptions === true) {
106
- winstonTransport.handleExceptions = true;
107
- }
108
- if (config.handleRejections === true) {
109
- winstonTransport.handleRejections = true;
110
- }
111
-
112
- return winstonTransport;
113
- }
114
-
115
- function captureWinstonInfo(config) {
116
- if (config.info?.silent === true) {
117
- return;
118
- }
119
- config.state.captured += 1;
120
- config.client.log(
121
- `${config.eventIdPrefix}_${config.state.captured}`,
122
- timestampFromWinstonInfo(config.info, config.timestamp),
123
- logAttributesFromWinstonInfo(config.info, {
124
- includeErrorStack: config.includeErrorStack,
125
- logger: config.logger,
126
- metadata: config.metadata,
127
- trace: traceFromProvider(config.traceProvider, config.onError)
128
- })
129
- );
130
- if (config.flushOnWrite && config.transport) {
131
- config.state.pendingFlush = Promise.resolve(config.client.flush(config.transport)).catch((error) => {
132
- config.onError(error);
133
- return null;
134
- });
135
- }
136
- }
137
107
 
138
- function recordWinstonBreadcrumb(client, info) {
139
- if (!info || Array.isArray(info) || typeof info !== "object" || info.silent === true) {
140
- return;
141
- }
142
- const breadcrumb = {
143
- type: "debug",
144
- category: "log.winston",
145
- level: winstonBreadcrumbLevel(info.level),
146
- message: Array.from(winstonMessage(info)).slice(0, MAX_BREADCRUMB_MESSAGE_LENGTH).join("")
147
- };
148
- const recorder = client.breadcrumbProvider ? globalThis[BREADCRUMB_RECORDER] : undefined;
149
- try {
150
- if (typeof recorder === "function") {
151
- recorder(breadcrumb);
152
- } else {
153
- client.addBreadcrumb(breadcrumb);
108
+ /**
109
+ * @param {unknown} info
110
+ * @returns {void}
111
+ */
112
+ capture(info) {
113
+ if (optionalProperty(info, "silent") === true) {
114
+ return;
154
115
  }
155
- } catch {
156
- // Breadcrumbs are advisory; the log event below still goes out.
157
- }
158
- }
159
-
160
- function winstonBreadcrumbLevel(level) {
161
- switch (String(level).toLowerCase()) {
162
- case "error":
163
- case "crit":
164
- case "critical":
165
- case "fatal":
166
- case "alert":
167
- case "emerg":
168
- return "error";
169
- case "warn":
170
- case "warning":
171
- return "warning";
172
- case "verbose":
173
- case "debug":
174
- case "silly":
175
- return "debug";
176
- default:
177
- return "info";
178
- }
179
- }
180
-
181
- function logAttributesFromWinstonInfo(info, options = {}) {
182
- if (!info || Array.isArray(info) || typeof info !== "object") {
183
- throw new SdkError("validation_error", "Winston info must be an object");
184
- }
185
-
186
- const level = logbrewLevelFromWinstonLevel(info.level);
187
- const metadata = {
188
- ...compactMetadata(options.metadata),
189
- winstonLevel: winstonLevelLabel(info.level),
190
- ...winstonContextMetadata(info),
191
- ...traceMetadataFromLogContext(options.trace)
192
- };
193
- addWinstonErrorMetadata(metadata, info, options.includeErrorStack === true);
194
-
195
- return {
196
- message: winstonMessage(info),
197
- level,
198
- ...(options.logger ? { logger: options.logger } : {}),
199
- metadata
200
- };
201
- }
202
-
203
- function timestampFromWinstonInfo(info, fallbackTimestamp) {
204
- const value = info?.timestamp ?? info?.time;
205
- if (typeof value === "number" && Number.isFinite(value)) {
206
- return new Date(value).toISOString();
207
- }
208
- if (typeof value === "string" && value.trim() !== "") {
209
- const parsed = new Date(value);
210
- if (!Number.isNaN(parsed.valueOf())) {
211
- return parsed.toISOString();
116
+ const { settings } = this;
117
+ this.captured += 1;
118
+ this.client.log(
119
+ [settings.eventIdPrefix, this.captured].join("_"),
120
+ timestampFromWinstonInfo(info, settings.timestamp),
121
+ logAttributesFromWinstonInfo(info, {
122
+ includeErrorStack: settings.includeErrorStack,
123
+ logger: settings.logger,
124
+ metadata: settings.metadata,
125
+ trace: traceFromProvider(this.traceProvider, settings.onError)
126
+ })
127
+ );
128
+ if (settings.flushOnEvent && isTruthy(settings.transport)) {
129
+ this.pendingFlush = settledFlush(this.client.flush(settings.transport), settings.onError);
212
130
  }
213
- return value;
214
131
  }
215
- if (value instanceof Date && !Number.isNaN(value.valueOf())) {
216
- return value.toISOString();
217
- }
218
- return fallbackTimestamp();
219
- }
220
132
 
221
- function logbrewLevelFromWinstonLevel(level) {
222
- switch (String(level).toLowerCase()) {
223
- case "debug":
224
- case "silly":
225
- return "info";
226
- case "warn":
227
- case "warning":
228
- return "warning";
229
- case "error":
230
- return "error";
231
- case "crit":
232
- case "critical":
233
- case "fatal":
234
- return "critical";
235
- case "http":
236
- case "verbose":
237
- case "info":
238
- default:
239
- return "info";
133
+ /**
134
+ * @returns {void}
135
+ */
136
+ flushPending() {
137
+ const { settings } = this;
138
+ if (isTruthy(settings.transport) && this.client.pendingEvents() > NO_PENDING_EVENTS) {
139
+ this.pendingFlush = settledFlush(this.client.flush(settings.transport), settings.onError);
140
+ }
240
141
  }
241
- }
242
142
 
243
- function winstonLevelLabel(level) {
244
- return typeof level === "string" && level.trim() !== "" ? level : "info";
245
- }
246
-
247
- function winstonMessage(info) {
248
- if (typeof info.message === "string" && info.message.trim() !== "") {
249
- return info.message;
250
- }
251
- const error = info.err ?? info.error;
252
- if (error && typeof error === "object" && typeof error.message === "string" && error.message.trim() !== "") {
253
- return error.message;
143
+ /**
144
+ * @returns {() => Promise<unknown>}
145
+ */
146
+ flushHandler() {
147
+ return async () => {
148
+ this.flushPending();
149
+ return this.pendingFlush;
150
+ };
254
151
  }
255
- return "winston event";
256
152
  }
257
153
 
258
- function winstonContextMetadata(info) {
259
- const metadata = {};
260
- for (const [key, value] of Object.entries(info)) {
261
- if (!WINSTON_RESERVED_FIELDS.has(key) && isMetadataValue(value)) {
262
- metadata[`context.${key}`] = value;
263
- }
154
+ /**
155
+ * @this {Readonly<{ write: (chunk: unknown) => boolean }>}
156
+ * @param {unknown} info
157
+ * @param {unknown} callback
158
+ * @returns {void}
159
+ */
160
+ function log(info, callback) {
161
+ this.write(info);
162
+ if (typeof callback === "function") {
163
+ Reflect.apply(callback, undefined, []);
264
164
  }
265
- return metadata;
266
165
  }
267
166
 
268
- function addWinstonErrorMetadata(metadata, info, includeErrorStack) {
269
- const error = info.err ?? info.error;
270
- addWinstonNestedErrorMetadata(metadata, error, includeErrorStack);
271
- if (typeof info.stack === "string" && info.stack.trim() !== "") {
272
- const firstLine = info.stack.split(/\r?\n/u)[0] ?? "";
273
- const match = /^([A-Za-z][A-Za-z0-9_.]*(?:Error|Exception)?):\s*(.*)$/u.exec(firstLine);
274
- if (match && metadata.errorName === undefined) {
275
- metadata.errorName = match[1];
276
- }
277
- if (match && match[2] && metadata.errorMessage === undefined) {
278
- metadata.errorMessage = match[2];
279
- }
280
- if (includeErrorStack) {
281
- metadata.errorStack = info.stack;
282
- }
283
- }
167
+ /**
168
+ * @param {Readonly<Record<string, unknown>>} config
169
+ * @returns {Readonly<Record<string, unknown>>}
170
+ */
171
+ function winstonTransportOptions(config) {
172
+ const { level, name } = config;
173
+ const nameText = String(name);
174
+ return {
175
+ ...(typeof level === "string" && level.trim() !== "" ? { level } : {}),
176
+ ...(name === undefined ? {} : { name: nameText }),
177
+ ...(config["silent"] === true ? { silent: true } : {}),
178
+ ...(config["handleExceptions"] === true ? { handleExceptions: true } : {}),
179
+ ...(config["handleRejections"] === true ? { handleRejections: true } : {})
180
+ };
284
181
  }
285
182
 
286
- function addWinstonNestedErrorMetadata(metadata, error, includeErrorStack) {
287
- if (!error) {
288
- return;
183
+ /**
184
+ * @param {unknown} config
185
+ * @returns {Writable}
186
+ */
187
+ function createLogBrewWinstonTransport(config) {
188
+ if (!isObjectValue(config)) {
189
+ throw new SdkError("validation_error", "Winston transport config must be an object");
289
190
  }
290
- if (error instanceof Error) {
291
- metadata.errorName = error.name || "Error";
292
- if (error.message) {
293
- metadata.errorMessage = error.message;
294
- }
295
- if (includeErrorStack && error.stack) {
296
- metadata.errorStack = error.stack;
297
- }
298
- return;
191
+ const { client } = config;
192
+ if (!(client instanceof LogBrewClient)) {
193
+ throw new SdkError("validation_error", "Winston transport client must be a LogBrewClient");
299
194
  }
300
- if (typeof error === "object") {
301
- const name = error.type ?? error.name;
302
- const message = error.message;
303
- const stack = error.stack;
304
- if (typeof name === "string" && name.trim() !== "") {
305
- metadata.errorName = name;
306
- }
307
- if (typeof message === "string" && message.trim() !== "") {
308
- metadata.errorMessage = message;
309
- }
310
- if (includeErrorStack && typeof stack === "string" && stack.trim() !== "") {
311
- metadata.errorStack = stack;
195
+ const capture = new WinstonCapture(config, client);
196
+ const winstonTransport = new Writable({
197
+ objectMode: true,
198
+ /**
199
+ * @param {unknown} info
200
+ * @param {unknown} _encoding
201
+ * @param {() => void} callback
202
+ * @returns {void}
203
+ */
204
+ write(info, _encoding, callback) {
205
+ try {
206
+ capture.write(info);
207
+ } finally {
208
+ Reflect.apply(callback, undefined, []);
209
+ }
312
210
  }
313
- return;
314
- }
315
- if (typeof error === "string" && error.trim() !== "") {
316
- metadata.errorMessage = error;
317
- }
211
+ });
212
+ return Object.assign(
213
+ winstonTransport,
214
+ {
215
+ log,
216
+
217
+ flush: capture.flushHandler()
218
+ },
219
+ winstonTransportOptions(config)
220
+ );
318
221
  }
319
222
 
320
- module.exports = {
321
- createLogBrewWinstonTransport,
322
- logAttributesFromWinstonInfo
323
- };
223
+ module.exports = { createLogBrewWinstonTransport, logAttributesFromWinstonInfo };