@steve31415/baselib 3.22.0 → 3.24.0
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/dist/log-browser.d.ts +20 -1
- package/dist/log-browser.js +74 -3
- package/dist/log-core.d.ts +10 -0
- package/dist/log-core.js +15 -1
- package/package.json +1 -1
package/dist/log-browser.d.ts
CHANGED
|
@@ -11,5 +11,24 @@ export interface BrowserLoggerOptions {
|
|
|
11
11
|
console?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export declare function createBrowserLogger(opts?: BrowserLoggerOptions): Logger;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Make the shared `logger` forward to `instance`. Call once at boot, right
|
|
16
|
+
* after createBrowserLogger; tests install a recorder.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installBrowserLogger(instance: Logger | null): void;
|
|
19
|
+
/**
|
|
20
|
+
* The page's logger, importable from any module — no threading a Logger
|
|
21
|
+
* through props and constructors. It forwards to the instance given to
|
|
22
|
+
* installBrowserLogger; before that (or in a test that installs none)
|
|
23
|
+
* events go to the devtools console rather than being lost.
|
|
24
|
+
*/
|
|
25
|
+
export declare const logger: Logger;
|
|
26
|
+
/**
|
|
27
|
+
* Route uncaught errors, unhandled rejections, and failed resource loads
|
|
28
|
+
* (`<img>`, `<script>`, `<link>`, media) into the logger. A resource load
|
|
29
|
+
* failure fires a non-bubbling `error` on its element, which only a
|
|
30
|
+
* capturing window listener sees; it logs WARN while the browser reports
|
|
31
|
+
* itself online and INFO while offline (expected then), once per URL per
|
|
32
|
+
* page so a re-rendering broken element cannot flood the dataset.
|
|
33
|
+
*/
|
|
15
34
|
export declare function installGlobalErrorHandlers(logger: Logger): void;
|
package/dist/log-browser.js
CHANGED
|
@@ -31,6 +31,12 @@ export function createBrowserLogger(opts = {}) {
|
|
|
31
31
|
// remains best-effort, so a browser give-up is only console-noted.
|
|
32
32
|
batchSize: 20,
|
|
33
33
|
fetchInit: { keepalive: true },
|
|
34
|
+
// Browser logs have no stdout backstop, and the ERRORs most worth
|
|
35
|
+
// keeping are often the ones an offline stretch causes: while the
|
|
36
|
+
// browser reports itself offline, failed attempts don't count, so a
|
|
37
|
+
// batch waits out the outage (until the page dies or the buffer cap
|
|
38
|
+
// evicts it) instead of being dropped after ~60 s.
|
|
39
|
+
isOffline: () => navigator.onLine === false,
|
|
34
40
|
onGiveUp: (batch, error, reason) => console.warn(`log ship dropped a batch (${reason}): ${String(error)}`, {
|
|
35
41
|
batch_id: batch.id,
|
|
36
42
|
events: batch.events.length,
|
|
@@ -66,17 +72,82 @@ export function createBrowserLogger(opts = {}) {
|
|
|
66
72
|
// Ship what's pending whenever the page may be going away.
|
|
67
73
|
const flushNow = () => void logger.flush();
|
|
68
74
|
addEventListener('pagehide', flushNow);
|
|
75
|
+
addEventListener('online', () => shipper?.resume());
|
|
69
76
|
document.addEventListener('visibilitychange', () => {
|
|
70
77
|
if (document.visibilityState === 'hidden')
|
|
71
78
|
flushNow();
|
|
72
79
|
});
|
|
73
80
|
return logger;
|
|
74
81
|
}
|
|
75
|
-
|
|
82
|
+
let installed = null;
|
|
83
|
+
/**
|
|
84
|
+
* Make the shared `logger` forward to `instance`. Call once at boot, right
|
|
85
|
+
* after createBrowserLogger; tests install a recorder.
|
|
86
|
+
*/
|
|
87
|
+
export function installBrowserLogger(instance) {
|
|
88
|
+
installed = instance;
|
|
89
|
+
}
|
|
90
|
+
const CONSOLE_METHOD = { debug: 'debug', info: 'log', warn: 'warn', error: 'error' };
|
|
91
|
+
function forwardingLogger(context) {
|
|
92
|
+
const emit = (level, message, meta) => {
|
|
93
|
+
// Resolved per call, so a module-scope `logger.with(...)` taken before
|
|
94
|
+
// boot still ships once the app installs its logger.
|
|
95
|
+
if (installed)
|
|
96
|
+
(context ? installed.with(context) : installed)[level](message, meta);
|
|
97
|
+
else
|
|
98
|
+
console[CONSOLE_METHOD[level]](message, meta ?? '');
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
debug: (m, meta) => emit('debug', m, meta),
|
|
102
|
+
info: (m, meta) => emit('info', m, meta),
|
|
103
|
+
warn: (m, meta) => emit('warn', m, meta),
|
|
104
|
+
error: (m, meta) => emit('error', m, meta),
|
|
105
|
+
with: (extra) => forwardingLogger({ ...context, ...extra }),
|
|
106
|
+
flush: () => installed?.flush() ?? Promise.resolve(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The page's logger, importable from any module — no threading a Logger
|
|
111
|
+
* through props and constructors. It forwards to the instance given to
|
|
112
|
+
* installBrowserLogger; before that (or in a test that installs none)
|
|
113
|
+
* events go to the devtools console rather than being lost.
|
|
114
|
+
*/
|
|
115
|
+
export const logger = forwardingLogger(undefined);
|
|
116
|
+
const MAX_RESOURCE_URL = 500;
|
|
117
|
+
function resourceUrl(target) {
|
|
118
|
+
// The resolved (absolute) URL, as the browser requested it.
|
|
119
|
+
const element = target;
|
|
120
|
+
const url = element.currentSrc || element.src || element.href || '';
|
|
121
|
+
return url.length > MAX_RESOURCE_URL ? `${url.slice(0, MAX_RESOURCE_URL)}…` : url;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Route uncaught errors, unhandled rejections, and failed resource loads
|
|
125
|
+
* (`<img>`, `<script>`, `<link>`, media) into the logger. A resource load
|
|
126
|
+
* failure fires a non-bubbling `error` on its element, which only a
|
|
127
|
+
* capturing window listener sees; it logs WARN while the browser reports
|
|
128
|
+
* itself online and INFO while offline (expected then), once per URL per
|
|
129
|
+
* page so a re-rendering broken element cannot flood the dataset.
|
|
130
|
+
*/
|
|
76
131
|
export function installGlobalErrorHandlers(logger) {
|
|
132
|
+
const failedResources = new Set();
|
|
77
133
|
addEventListener('error', (e) => {
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
const target = e.target;
|
|
135
|
+
if (typeof Element !== 'undefined' && target instanceof Element) {
|
|
136
|
+
const url = resourceUrl(target);
|
|
137
|
+
if (failedResources.has(url))
|
|
138
|
+
return;
|
|
139
|
+
failedResources.add(url);
|
|
140
|
+
const online = navigator.onLine !== false;
|
|
141
|
+
logger[online ? 'warn' : 'info']('resource failed to load', {
|
|
142
|
+
element: target.tagName.toLowerCase(),
|
|
143
|
+
url,
|
|
144
|
+
online,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const event = e;
|
|
149
|
+
logger.error('uncaught error', { error: event.error ?? event.message });
|
|
150
|
+
}, true);
|
|
80
151
|
addEventListener('unhandledrejection', (e) => {
|
|
81
152
|
logger.error('unhandled rejection', { error: e.reason });
|
|
82
153
|
});
|
package/dist/log-core.d.ts
CHANGED
|
@@ -67,6 +67,13 @@ export interface ShipperOptions {
|
|
|
67
67
|
fetchFn?: typeof fetch;
|
|
68
68
|
/** Extra fetch options (browser sets keepalive). */
|
|
69
69
|
fetchInit?: RequestInit;
|
|
70
|
+
/** True while the platform knows it has no network (the browser passes
|
|
71
|
+
* `navigator.onLine === false`). A failed attempt made while offline says
|
|
72
|
+
* nothing about the batch or the ingest service, so it is not counted
|
|
73
|
+
* toward maxAttempts and not reported: the batch waits and retries at the
|
|
74
|
+
* longest backoff delay, or at once on resume(). The signal only decides
|
|
75
|
+
* what a failure means — attempts still run while "offline". */
|
|
76
|
+
isOffline?: () => boolean;
|
|
70
77
|
/** A delivery attempt failed; the batch will be retried. */
|
|
71
78
|
onAttemptFailure?: (batch: ShippedBatch, error: unknown) => void;
|
|
72
79
|
/** The batch is abandoned — its events will never reach Axiom. */
|
|
@@ -101,6 +108,9 @@ export declare class LogShipper {
|
|
|
101
108
|
* — raced with one attempt timeout, so it is bounded even when the pump
|
|
102
109
|
* is busy with older batches. */
|
|
103
110
|
flush(): Promise<void>;
|
|
111
|
+
/** Retry every waiting batch now, ahead of its backoff — the browser
|
|
112
|
+
* calls this when it comes back online. */
|
|
113
|
+
resume(): void;
|
|
104
114
|
/** Shutdown path: one parallel last-chance attempt for everything
|
|
105
115
|
* undelivered, then a sweep that marks the rest dropped. Single-flight
|
|
106
116
|
* while in flight (racing SIGTERM callers join one promise), but re-arms
|
package/dist/log-core.js
CHANGED
|
@@ -111,6 +111,13 @@ export class LogShipper {
|
|
|
111
111
|
return raceTimeout(Promise.allSettled([...this.inflight]), cap);
|
|
112
112
|
return Promise.resolve();
|
|
113
113
|
}
|
|
114
|
+
/** Retry every waiting batch now, ahead of its backoff — the browser
|
|
115
|
+
* calls this when it comes back online. */
|
|
116
|
+
resume() {
|
|
117
|
+
for (const b of this.pending)
|
|
118
|
+
b.nextAttemptAt = 0;
|
|
119
|
+
void this.pump();
|
|
120
|
+
}
|
|
114
121
|
/** Shutdown path: one parallel last-chance attempt for everything
|
|
115
122
|
* undelivered, then a sweep that marks the rest dropped. Single-flight
|
|
116
123
|
* while in flight (racing SIGTERM callers join one promise), but re-arms
|
|
@@ -229,8 +236,15 @@ export class LogShipper {
|
|
|
229
236
|
batch.attempting = false;
|
|
230
237
|
if (batch.terminal)
|
|
231
238
|
return;
|
|
232
|
-
batch.attempt++;
|
|
233
239
|
batch.settleFirst();
|
|
240
|
+
if (!outcome.ok && !outcome.terminal && this.opts.isOffline?.()) {
|
|
241
|
+
// Uncounted: the budget is for failures that could be the batch's
|
|
242
|
+
// or the service's, and an offline failure is neither.
|
|
243
|
+
batch.nextAttemptAt = Date.now() + this.retryDelaysMs[this.retryDelaysMs.length - 1];
|
|
244
|
+
this.schedulePump();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
batch.attempt++;
|
|
234
248
|
if (outcome.ok) {
|
|
235
249
|
this.remove(batch);
|
|
236
250
|
if (outcome.failed > 0)
|
package/package.json
CHANGED