@steve31415/baselib 3.23.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.
@@ -11,5 +11,24 @@ export interface BrowserLoggerOptions {
11
11
  console?: boolean;
12
12
  }
13
13
  export declare function createBrowserLogger(opts?: BrowserLoggerOptions): Logger;
14
- /** Route uncaught errors and unhandled rejections into the logger. */
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;
@@ -79,11 +79,75 @@ export function createBrowserLogger(opts = {}) {
79
79
  });
80
80
  return logger;
81
81
  }
82
- /** Route uncaught errors and unhandled rejections into the logger. */
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
+ */
83
131
  export function installGlobalErrorHandlers(logger) {
132
+ const failedResources = new Set();
84
133
  addEventListener('error', (e) => {
85
- logger.error('uncaught error', { error: e.error ?? e.message });
86
- });
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);
87
151
  addEventListener('unhandledrejection', (e) => {
88
152
  logger.error('unhandled rejection', { error: e.reason });
89
153
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve31415/baselib",
3
- "version": "3.23.0",
3
+ "version": "3.24.0",
4
4
  "description": "Plasticine new-world shared platform library: logging, auth, service-to-service auth, db, HTTP, sync, app updates",
5
5
  "type": "module",
6
6
  "license": "MIT",