@logbrew/sdk 0.1.30 → 0.1.32
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/README.md +2 -0
- package/browser-breadcrumbs.cjs +194 -0
- package/core.cjs +48 -10
- package/index.d.ts +21 -0
- package/index.js +1 -0
- package/package.json +2 -1
- package/react-native.d.ts +1 -0
- package/react-native.js +1 -0
- package/winston.cjs +51 -0
package/README.md
CHANGED
|
@@ -697,6 +697,8 @@ await logbrewTransport.flush();
|
|
|
697
697
|
|
|
698
698
|
The Winston adapter receives Winston `info` objects, maps `debug`/`silly` to LogBrew `info`, `warn` to `warning`, `error` to `error`, `fatal`/`critical` to `critical`, and other common Winston levels to `info`. It captures primitive info fields as `context.*`, captures nested `err`/`error` objects or formatted error stack name/message, omits stack text unless `includeErrorStack: true` is set, and exposes `onError` for capture failures. It does not mutate Winston globals or replace the app's logger.
|
|
699
699
|
|
|
700
|
+
Each Winston message is also kept as an issue breadcrumb (category `log.winston`, message capped at 512 characters, no info fields), so the next issue shows the log lines that led to it. With `@logbrew/node`, breadcrumbs written during a request stay with that request's issues. Pass `breadcrumbs: false` to turn this off.
|
|
701
|
+
|
|
700
702
|
Use the same `traceProvider: getActiveLogBrewTrace` option with LogBrew Node/framework helpers when you want Winston logs to carry the active request trace. The adapter only copies normalized W3C-shaped IDs and sampled state; it does not patch Winston globally or serialize arbitrary active context.
|
|
701
703
|
|
|
702
704
|
Use a clearly fake placeholder like `LOGBREW_API_KEY` in examples. Call `flush` or `shutdown` to send queued events through a transport, and use `previewJson()` when you want a stable local JSON preview before sending anything.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MAX_MESSAGE_LENGTH = 512;
|
|
4
|
+
const MAX_URL_LENGTH = 256;
|
|
5
|
+
const CONSOLE_LEVELS = Object.freeze({
|
|
6
|
+
debug: "debug",
|
|
7
|
+
error: "error",
|
|
8
|
+
info: "info",
|
|
9
|
+
log: "info",
|
|
10
|
+
warn: "warning"
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Records console calls, fetch and XHR requests, and route changes as issue breadcrumbs.
|
|
14
|
+
// Browser-safe and shared by @logbrew/browser and @logbrew/next/client. Returns an uninstall function
|
|
15
|
+
// that puts back every patched method it still owns.
|
|
16
|
+
function installBrowserBreadcrumbs(browserWindow, client, {
|
|
17
|
+
console: captureConsole = true,
|
|
18
|
+
ignoredUrls = [],
|
|
19
|
+
navigation = true,
|
|
20
|
+
network = true
|
|
21
|
+
} = {}) {
|
|
22
|
+
const ignored = new Set(ignoredUrls.map((url) => sanitizeUrl(url, browserWindow)).filter(Boolean));
|
|
23
|
+
const add = (breadcrumb) => {
|
|
24
|
+
try {
|
|
25
|
+
client.addBreadcrumb(breadcrumb);
|
|
26
|
+
} catch {
|
|
27
|
+
// Breadcrumbs are advisory and never reach application code.
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const restores = [];
|
|
31
|
+
if (captureConsole) {
|
|
32
|
+
restores.push(...patchConsole(browserWindow.console ?? globalThis.console, add));
|
|
33
|
+
}
|
|
34
|
+
if (network) {
|
|
35
|
+
restores.push(...patchFetch(browserWindow, add, ignored), ...patchXhr(browserWindow, add, ignored));
|
|
36
|
+
}
|
|
37
|
+
if (navigation) {
|
|
38
|
+
restores.push(...patchNavigation(browserWindow, add));
|
|
39
|
+
}
|
|
40
|
+
return () => {
|
|
41
|
+
for (const restore of restores.splice(0).reverse()) {
|
|
42
|
+
restore();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function patchConsole(target, add) {
|
|
48
|
+
if (!target) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return Object.entries(CONSOLE_LEVELS).flatMap(([method, level]) => replaceMethod(target, method, (original) => (
|
|
52
|
+
function logBrewConsoleBreadcrumb(...args) {
|
|
53
|
+
const message = boundedText(consoleTemplate(args), MAX_MESSAGE_LENGTH);
|
|
54
|
+
if (message) {
|
|
55
|
+
add({ type: "debug", category: "console", level, message });
|
|
56
|
+
}
|
|
57
|
+
return original.apply(this, args);
|
|
58
|
+
}
|
|
59
|
+
)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function patchFetch(browserWindow, add, ignored) {
|
|
63
|
+
return replaceMethod(browserWindow, "fetch", (original) => async function logBrewFetchBreadcrumb(input, init) {
|
|
64
|
+
const method = String(init?.method ?? input?.method ?? "GET").toUpperCase();
|
|
65
|
+
const url = sanitizeUrl(typeof input === "string" || input instanceof URL ? input : input?.url, browserWindow);
|
|
66
|
+
try {
|
|
67
|
+
const response = await original.call(this, input, init);
|
|
68
|
+
if (!ignored.has(url)) {
|
|
69
|
+
add(httpBreadcrumb("fetch", method, url, response?.status));
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (!ignored.has(url)) {
|
|
74
|
+
add({ ...httpBreadcrumb("fetch", method, url), level: "error" });
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function patchXhr(browserWindow, add, ignored) {
|
|
82
|
+
const prototype = browserWindow.XMLHttpRequest?.prototype;
|
|
83
|
+
if (!prototype) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const state = new WeakMap();
|
|
87
|
+
return [
|
|
88
|
+
...replaceMethod(prototype, "open", (original) => function logBrewXhrOpen(method, url, ...rest) {
|
|
89
|
+
state.set(this, { method: String(method ?? "GET").toUpperCase(), url: sanitizeUrl(url, browserWindow) });
|
|
90
|
+
return original.call(this, method, url, ...rest);
|
|
91
|
+
}),
|
|
92
|
+
...replaceMethod(prototype, "send", (original) => function logBrewXhrSend(...args) {
|
|
93
|
+
const request = state.get(this);
|
|
94
|
+
if (request && !ignored.has(request.url) && typeof this.addEventListener === "function") {
|
|
95
|
+
this.addEventListener("loadend", () => {
|
|
96
|
+
add(httpBreadcrumb("xhr", request.method, request.url, this.status || undefined));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return original.apply(this, args);
|
|
100
|
+
})
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function patchNavigation(browserWindow, add) {
|
|
105
|
+
const history = browserWindow.history;
|
|
106
|
+
if (!history) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
let from = currentPath(browserWindow);
|
|
110
|
+
const record = () => {
|
|
111
|
+
const to = currentPath(browserWindow);
|
|
112
|
+
if (to && to !== from) {
|
|
113
|
+
add({ type: "navigation", category: "navigation", data: { ...(from ? { from } : {}), to } });
|
|
114
|
+
from = to;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const restores = ["pushState", "replaceState"].flatMap((method) => replaceMethod(history, method, (original) => (
|
|
118
|
+
function logBrewNavigationBreadcrumb(...args) {
|
|
119
|
+
const result = original.apply(this, args);
|
|
120
|
+
record();
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
)));
|
|
124
|
+
if (typeof browserWindow.addEventListener === "function") {
|
|
125
|
+
browserWindow.addEventListener("popstate", record);
|
|
126
|
+
restores.push(() => browserWindow.removeEventListener?.("popstate", record));
|
|
127
|
+
}
|
|
128
|
+
return restores;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function replaceMethod(target, name, wrap) {
|
|
132
|
+
const original = target?.[name];
|
|
133
|
+
if (typeof original !== "function") {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const replacement = wrap(original);
|
|
137
|
+
target[name] = replacement;
|
|
138
|
+
return [() => {
|
|
139
|
+
if (target[name] === replacement) {
|
|
140
|
+
target[name] = original;
|
|
141
|
+
}
|
|
142
|
+
}];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function httpBreadcrumb(category, method, url, status) {
|
|
146
|
+
const statusCode = Number.isInteger(status) && status > 0 ? status : undefined;
|
|
147
|
+
return {
|
|
148
|
+
type: "http",
|
|
149
|
+
category,
|
|
150
|
+
level: statusCode === undefined || statusCode < 400 ? "info" : statusCode >= 500 ? "error" : "warning",
|
|
151
|
+
data: {
|
|
152
|
+
method: method.slice(0, 16) || "GET",
|
|
153
|
+
...(url ? { url } : {}),
|
|
154
|
+
...(statusCode === undefined ? {} : { status_code: statusCode })
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Records the message template, never the interpolated arguments.
|
|
160
|
+
function consoleTemplate(args) {
|
|
161
|
+
const [first] = args;
|
|
162
|
+
if (typeof first === "string") {
|
|
163
|
+
return first;
|
|
164
|
+
}
|
|
165
|
+
return first instanceof Error ? first.name || "Error" : "";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function currentPath(browserWindow) {
|
|
169
|
+
const pathname = browserWindow.location?.pathname;
|
|
170
|
+
return typeof pathname === "string" ? boundedText(pathname, MAX_URL_LENGTH) : undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Keeps scheme, host, and path; drops user info, query, and fragment.
|
|
174
|
+
function sanitizeUrl(value, browserWindow) {
|
|
175
|
+
if (value === undefined || value === null) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const url = new URL(String(value), String(browserWindow.location?.href ?? "http://localhost/"));
|
|
180
|
+
return boundedText(`${url.protocol}//${url.host}${url.pathname}`, MAX_URL_LENGTH) || undefined;
|
|
181
|
+
} catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function boundedText(value, limit) {
|
|
187
|
+
const characters = Array.from(String(value), (character) => {
|
|
188
|
+
const code = character.codePointAt(0);
|
|
189
|
+
return code <= 31 || (code >= 127 && code <= 159) ? " " : character;
|
|
190
|
+
});
|
|
191
|
+
return Array.from(characters.join("").replace(/\s+/gu, " ").trim()).slice(0, limit).join("");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { installBrowserBreadcrumbs };
|
package/core.cjs
CHANGED
|
@@ -97,6 +97,8 @@ const MAX_CLIENT_REPORT_COUNT = 1_000_000_000;
|
|
|
97
97
|
const DELIVERY_HEALTH_SCHEMA_VERSION = 1;
|
|
98
98
|
const EVENT_QUEUE_FACTORY = Symbol.for("@logbrew/sdk.eventQueueFactory");
|
|
99
99
|
const CONTEXT_PROVIDER = Symbol.for("@logbrew/sdk.contextProvider");
|
|
100
|
+
// Runtime packages supply request-scoped breadcrumbs through this seam; core merges them into issues.
|
|
101
|
+
const BREADCRUMB_PROVIDER = Symbol.for("@logbrew/sdk.breadcrumbProvider");
|
|
100
102
|
const MAX_ERROR_CAUSES = 5;
|
|
101
103
|
const BUILTIN_ERROR_NAMES = new Set([
|
|
102
104
|
"AggregateError",
|
|
@@ -216,6 +218,7 @@ class LogBrewClient {
|
|
|
216
218
|
sdkVersion,
|
|
217
219
|
context,
|
|
218
220
|
[CONTEXT_PROVIDER]: contextProvider,
|
|
221
|
+
[BREADCRUMB_PROVIDER]: breadcrumbProvider,
|
|
219
222
|
maxRetries = 2,
|
|
220
223
|
eventFilter,
|
|
221
224
|
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
|
|
@@ -237,6 +240,9 @@ class LogBrewClient {
|
|
|
237
240
|
if (contextProvider !== undefined && typeof contextProvider !== "function") {
|
|
238
241
|
throw new SdkError("validation_error", "context provider must be a function");
|
|
239
242
|
}
|
|
243
|
+
if (breadcrumbProvider !== undefined && typeof breadcrumbProvider !== "function") {
|
|
244
|
+
throw new SdkError("validation_error", "breadcrumb provider must be a function");
|
|
245
|
+
}
|
|
240
246
|
requireNonNegativeInteger("maxRetries", maxRetries);
|
|
241
247
|
if (eventFilter !== undefined && typeof eventFilter !== "function") {
|
|
242
248
|
throw new SdkError("validation_error", "eventFilter must be a function");
|
|
@@ -286,6 +292,7 @@ class LogBrewClient {
|
|
|
286
292
|
onEventDropped,
|
|
287
293
|
context: clientContext,
|
|
288
294
|
contextProvider,
|
|
295
|
+
breadcrumbProvider,
|
|
289
296
|
sdk: {
|
|
290
297
|
name: sdkName,
|
|
291
298
|
language: "javascript",
|
|
@@ -308,6 +315,7 @@ class LogBrewClient {
|
|
|
308
315
|
onEventDropped,
|
|
309
316
|
context,
|
|
310
317
|
contextProvider,
|
|
318
|
+
breadcrumbProvider,
|
|
311
319
|
eventStore,
|
|
312
320
|
eventQueueFactory,
|
|
313
321
|
transport,
|
|
@@ -327,6 +335,7 @@ class LogBrewClient {
|
|
|
327
335
|
this.onEventDropped = onEventDropped;
|
|
328
336
|
this.context = cloneTelemetryContext(context);
|
|
329
337
|
this.contextProvider = contextProvider;
|
|
338
|
+
this.breadcrumbProvider = breadcrumbProvider;
|
|
330
339
|
this.transport = transport;
|
|
331
340
|
this.sdk = sdk;
|
|
332
341
|
this.maxRetries = maxRetries;
|
|
@@ -632,20 +641,46 @@ class LogBrewClient {
|
|
|
632
641
|
}
|
|
633
642
|
|
|
634
643
|
#pushIssue(id, timestamp, attributes) {
|
|
635
|
-
|
|
644
|
+
if (attributes.breadcrumbs !== undefined) {
|
|
645
|
+
return this.#pushEvent("issue", id, timestamp, validateIssue(attributes));
|
|
646
|
+
}
|
|
647
|
+
const provided = this.#providedBreadcrumbs();
|
|
648
|
+
const merged = provided.breadcrumbs.length === 0
|
|
649
|
+
? this.issueBreadcrumbs
|
|
650
|
+
: [...this.issueBreadcrumbs, ...provided.breadcrumbs]
|
|
651
|
+
.sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
|
|
652
|
+
const breadcrumbs = merged.slice(-MAX_ISSUE_BREADCRUMBS);
|
|
653
|
+
const truncated = attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
|
|
654
|
+
|| provided.truncated || merged.length > breadcrumbs.length;
|
|
655
|
+
const withBreadcrumbs = breadcrumbs.length === 0
|
|
636
656
|
? attributes
|
|
637
|
-
: {
|
|
638
|
-
...attributes,
|
|
639
|
-
breadcrumbs: this.issueBreadcrumbs,
|
|
640
|
-
...(
|
|
641
|
-
attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
|
|
642
|
-
? { breadcrumbsTruncated: true }
|
|
643
|
-
: {}
|
|
644
|
-
)
|
|
645
|
-
};
|
|
657
|
+
: { ...attributes, breadcrumbs, ...(truncated ? { breadcrumbsTruncated: true } : {}) };
|
|
646
658
|
return this.#pushEvent("issue", id, timestamp, validateIssue(withBreadcrumbs));
|
|
647
659
|
}
|
|
648
660
|
|
|
661
|
+
// Provider failures and invalid entries never cost the issue itself.
|
|
662
|
+
#providedBreadcrumbs() {
|
|
663
|
+
let provided;
|
|
664
|
+
try {
|
|
665
|
+
provided = this.breadcrumbProvider?.();
|
|
666
|
+
} catch {
|
|
667
|
+
return { breadcrumbs: [], truncated: false };
|
|
668
|
+
}
|
|
669
|
+
if (!provided || !Array.isArray(provided.breadcrumbs)) {
|
|
670
|
+
return { breadcrumbs: [], truncated: false };
|
|
671
|
+
}
|
|
672
|
+
const breadcrumbs = [];
|
|
673
|
+
let truncated = provided.truncated === true || provided.breadcrumbs.length > MAX_ISSUE_BREADCRUMBS;
|
|
674
|
+
for (const breadcrumb of provided.breadcrumbs.slice(-MAX_ISSUE_BREADCRUMBS)) {
|
|
675
|
+
try {
|
|
676
|
+
breadcrumbs.push(validateIssueBreadcrumb(breadcrumb));
|
|
677
|
+
} catch {
|
|
678
|
+
truncated = true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return { breadcrumbs, truncated };
|
|
682
|
+
}
|
|
683
|
+
|
|
649
684
|
#pushEvent(eventType, id, timestamp, attributes) {
|
|
650
685
|
if (this.closed) {
|
|
651
686
|
throw new SdkError("shutdown_error", "client is already shut down");
|
|
@@ -2829,8 +2864,11 @@ function formatConsoleArgument(value, includeErrorStack) {
|
|
|
2829
2864
|
}
|
|
2830
2865
|
}
|
|
2831
2866
|
|
|
2867
|
+
const { installBrowserBreadcrumbs } = require("./browser-breadcrumbs.cjs");
|
|
2868
|
+
|
|
2832
2869
|
module.exports = {
|
|
2833
2870
|
PRODUCT_ANALYTICS_KINDS,
|
|
2871
|
+
installBrowserBreadcrumbs,
|
|
2834
2872
|
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
2835
2873
|
createBaggage,
|
|
2836
2874
|
createIssueAttributesFromError,
|
package/index.d.ts
CHANGED
|
@@ -535,6 +535,8 @@ export type WinstonLogInfo = Record<string, unknown> & {
|
|
|
535
535
|
/** Configuration for the dependency-free Winston transport adapter. */
|
|
536
536
|
export type WinstonTransportConfig = {
|
|
537
537
|
client: LogBrewClient;
|
|
538
|
+
/** Record each Winston message as an issue breadcrumb (category `log.winston`). Defaults to true. */
|
|
539
|
+
breadcrumbs?: boolean;
|
|
538
540
|
logger?: string;
|
|
539
541
|
metadata?: Metadata;
|
|
540
542
|
traceProvider?: () => LogCorrelationTraceContext | null | undefined;
|
|
@@ -925,6 +927,25 @@ export declare class LogBrewClient {
|
|
|
925
927
|
/** Install explicit console capture while preserving the target console's normal output behavior. */
|
|
926
928
|
export declare function installLogBrewConsoleCapture(config: ConsoleCaptureConfig): ConsoleCaptureHandle;
|
|
927
929
|
|
|
930
|
+
/** Options for automatic browser breadcrumbs. Every source defaults to true. */
|
|
931
|
+
export type BrowserBreadcrumbOptions = {
|
|
932
|
+
console?: boolean;
|
|
933
|
+
network?: boolean;
|
|
934
|
+
navigation?: boolean;
|
|
935
|
+
/** URLs whose requests are not recorded, such as the LogBrew ingest endpoint. */
|
|
936
|
+
ignoredUrls?: string[];
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Record console calls, fetch and XHR requests, and route changes in a browser window as issue
|
|
941
|
+
* breadcrumbs on the client. The wrappers only observe calls. Returns an uninstall function.
|
|
942
|
+
*/
|
|
943
|
+
export declare function installBrowserBreadcrumbs(
|
|
944
|
+
browserWindow: unknown,
|
|
945
|
+
client: LogBrewClient,
|
|
946
|
+
options?: BrowserBreadcrumbOptions
|
|
947
|
+
): () => void;
|
|
948
|
+
|
|
928
949
|
/** Create safe action attributes for an app-owned product step without automatic UI capture. */
|
|
929
950
|
export declare function createProductActionAttributes(
|
|
930
951
|
action: ProductActionInput,
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
45
|
"index.cjs",
|
|
46
|
+
"browser-breadcrumbs.cjs",
|
|
46
47
|
"core.cjs",
|
|
47
48
|
"index.js",
|
|
48
49
|
"index.d.ts",
|
package/react-native.d.ts
CHANGED
package/react-native.js
CHANGED
package/winston.cjs
CHANGED
|
@@ -3,6 +3,10 @@ const { Writable } = require("node:stream");
|
|
|
3
3
|
const { LogBrewClient, SdkError } = require("./core.cjs");
|
|
4
4
|
const { buildLogContextHelpers } = require("./log-context.cjs");
|
|
5
5
|
|
|
6
|
+
// Runtime packages with request scopes (such as @logbrew/node) register a recorder that keeps
|
|
7
|
+
// breadcrumbs with the active request instead of the whole client.
|
|
8
|
+
const BREADCRUMB_RECORDER = Symbol.for("@logbrew/sdk.breadcrumbRecorder");
|
|
9
|
+
const MAX_BREADCRUMB_MESSAGE_LENGTH = 512;
|
|
6
10
|
const WINSTON_RESERVED_FIELDS = new Set([
|
|
7
11
|
"level",
|
|
8
12
|
"message",
|
|
@@ -30,6 +34,7 @@ function createLogBrewWinstonTransport(config) {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const transport = config.transport;
|
|
37
|
+
const breadcrumbs = config.breadcrumbs !== false;
|
|
33
38
|
const flushOnWrite = config.flushOnWrite === true;
|
|
34
39
|
const includeErrorStack = config.includeErrorStack === true;
|
|
35
40
|
const logger = config.logger ?? "winston";
|
|
@@ -49,6 +54,9 @@ function createLogBrewWinstonTransport(config) {
|
|
|
49
54
|
objectMode: true,
|
|
50
55
|
write(info, _encoding, callback) {
|
|
51
56
|
try {
|
|
57
|
+
if (breadcrumbs) {
|
|
58
|
+
recordWinstonBreadcrumb(client, info);
|
|
59
|
+
}
|
|
52
60
|
captureWinstonInfo({
|
|
53
61
|
client,
|
|
54
62
|
eventIdPrefix,
|
|
@@ -128,6 +136,49 @@ function captureWinstonInfo(config) {
|
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
function recordWinstonBreadcrumb(client, info) {
|
|
140
|
+
if (!info || Array.isArray(info) || typeof info !== "object" || info.silent === true) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const breadcrumb = {
|
|
144
|
+
type: "debug",
|
|
145
|
+
category: "log.winston",
|
|
146
|
+
level: winstonBreadcrumbLevel(info.level),
|
|
147
|
+
message: Array.from(winstonMessage(info)).slice(0, MAX_BREADCRUMB_MESSAGE_LENGTH).join("")
|
|
148
|
+
};
|
|
149
|
+
const recorder = client.breadcrumbProvider ? globalThis[BREADCRUMB_RECORDER] : undefined;
|
|
150
|
+
try {
|
|
151
|
+
if (typeof recorder === "function") {
|
|
152
|
+
recorder(breadcrumb);
|
|
153
|
+
} else {
|
|
154
|
+
client.addBreadcrumb(breadcrumb);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Breadcrumbs are advisory; the log event below still goes out.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function winstonBreadcrumbLevel(level) {
|
|
162
|
+
switch (String(level).toLowerCase()) {
|
|
163
|
+
case "error":
|
|
164
|
+
case "crit":
|
|
165
|
+
case "critical":
|
|
166
|
+
case "fatal":
|
|
167
|
+
case "alert":
|
|
168
|
+
case "emerg":
|
|
169
|
+
return "error";
|
|
170
|
+
case "warn":
|
|
171
|
+
case "warning":
|
|
172
|
+
return "warning";
|
|
173
|
+
case "verbose":
|
|
174
|
+
case "debug":
|
|
175
|
+
case "silly":
|
|
176
|
+
return "debug";
|
|
177
|
+
default:
|
|
178
|
+
return "info";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
131
182
|
function logAttributesFromWinstonInfo(info, options = {}) {
|
|
132
183
|
if (!info || Array.isArray(info) || typeof info !== "object") {
|
|
133
184
|
throw new SdkError("validation_error", "Winston info must be an object");
|