@quanticdigit/web-logging 1.0.3

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/LICENSE.txt ADDED
@@ -0,0 +1,47 @@
1
+ COMMERCIAL SOFTWARE LICENSE AGREEMENT
2
+ Quantic Digit S.R.L. — Restricted Use License
3
+ Version 1.0 — © 2025 Quantic Digit S.R.L. All rights reserved.
4
+
5
+ IMPORTANT – READ CAREFULLY:
6
+ This Software (the “Software”) is provided under license, not sold.
7
+ By installing, copying or using the Software, you agree to the following terms.
8
+
9
+ 1. GRANT OF LICENSE
10
+ Quantic Digit S.R.L. (“the Author”) grants you a non-exclusive, non-transferable license to use the Software exclusively for:
11
+ - personal learning and experimentation,
12
+ - educational or training activities,
13
+ - software development, prototyping, or testing not intended for commercial release.
14
+
15
+ 2. PROHIBITED USES
16
+ Unless expressly authorized in writing by Quantic Digit S.R.L., it is strictly forbidden to:
17
+ - use the Software in any commercial activity;
18
+ - integrate the Software into products or services intended for end customers;
19
+ - deploy or distribute the Software within a company, organization, or enterprise environment;
20
+ - use the Software in applications, systems or processes with any revenue, profit, or commercial purpose;
21
+ - redistribute, sublicense, sell, rent, lease, or otherwise transfer the Software to third parties.
22
+
23
+ “Commercial use” includes, but is not limited to:
24
+ - internal business use by companies, organizations or professionals,
25
+ - inclusion in paid services or products,
26
+ - use within customer projects, consultancy, or software delivered to clients,
27
+ - any use that yields direct or indirect economic advantage.
28
+
29
+ 3. OWNERSHIP
30
+ The Software is licensed, not sold.
31
+ All intellectual property rights remain the sole property of Quantic Digit S.R.L.
32
+
33
+ 4. WARRANTY DISCLAIMER
34
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT ANY WARRANTY OF ANY KIND.
35
+ The Author disclaims all implied warranties, including merchantability, fitness for a particular purpose and non-infringement.
36
+
37
+ 5. LIMITATION OF LIABILITY
38
+ In no event shall Quantic Digit S.R.L. be liable for any damages, including direct, indirect, incidental, special or consequential damages arising from the use of the Software.
39
+
40
+ 6. TERMINATION
41
+ Any violation of this License immediately terminates your right to use the Software.
42
+ Upon termination, you must cease all use and destroy all copies of the Software.
43
+
44
+ 7. GOVERNING LAW
45
+ This Agreement is governed by the laws of Italy, unless otherwise agreed in a written contract with Quantic Digit S.R.L.
46
+
47
+ © 2025 Quantic Digit S.R.L. — All rights reserved.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @quanticdigit/web-logging
2
+
3
+ Application log for the client: component lifecycle, every call to the event and action handlers, and
4
+ errors — filtered by a configuration the server decides, delivered by a writer you provide.
5
+
6
+ The base objects in `@quanticdigit/web-component` and `@quanticdigit/web-errors` are already wired to
7
+ it. Screens get the log without a line of their own.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @quanticdigit/web-logging
13
+ ```
14
+
15
+ Peer dependency: `@angular/core`. Nothing else — this package talks to no backend, because it does
16
+ not know which one you have.
17
+
18
+ ## Turn it on
19
+
20
+ One provider. That is the whole integration.
21
+
22
+ ```ts
23
+ { provide: APPLICATION_LOG_WRITER, useClass: MyApplicationLogWriter }
24
+ ```
25
+
26
+ `IApplicationLogWriter` has a single method, `write(entry)`. What it does with the entry is yours:
27
+ queue it, batch it, POST it. It must not throw and must not make anyone wait — the caller is in the
28
+ middle of a screen.
29
+
30
+ Remove that provider and the log is off everywhere. The base objects look the writer up as optional,
31
+ so without it every log call is a comparison against `null` and nothing else.
32
+
33
+ ## Nothing is logged until you say so
34
+
35
+ The configuration lives in `sessionStorage` under `log.config`, as an array of
36
+
37
+ ```json
38
+ [{ "path": "app-reports", "startsWith": true, "endsWith": false, "level": "D", "disabled": false }]
39
+ ```
40
+
41
+ A path is matched by the four combinations of the two flags: both means equality, `startsWith` alone
42
+ means prefix, `endsWith` alone means suffix, neither means "contains". Among the rows that match, the
43
+ **most verbose** level wins, so a row at `D` for one screen does not require removing the general one
44
+ at `I`.
45
+
46
+ **No row that matches means no log.** Not "log everything" — nothing. That is what makes it safe to
47
+ ship the instrumentation long before deciding what to observe, and it is the same rule the server
48
+ applies to its own log configuration.
49
+
50
+ Levels are the server's one-letter codes, and lower is more severe: `F E W I D T`.
51
+
52
+ ## The path
53
+
54
+ The root is the component selector, `app-reports`, not the class name — class names are mangled in a
55
+ production build and a log you cannot read is worse than no log. Under it:
56
+
57
+ | path | what it is |
58
+ | --- | --- |
59
+ | `app-reports.lifecycle` | init and destroy, at `I` |
60
+ | `app-reports.eventHandler.onClickApplyFilters` | a template call, enter/exit at `D`, arguments and result at `T` |
61
+ | `app-reports.actionHandler.load` | the action behind it |
62
+ | `errors.http` | what the error collector received |
63
+ | `errors.unhandled` | what nobody caught, at `F` |
64
+
65
+ ## By hand
66
+
67
+ For code that is not a component:
68
+
69
+ ```ts
70
+ const logger = new ApplicationLogger(inject(APPLICATION_LOG_WRITER, { optional: true }), 'import-job');
71
+ logger.log('I', 'run', 'started', { file: name });
72
+ ```
73
+
74
+ To instrument an object of your own, `wrapWithLogging(target, logger)` returns a proxy that logs every
75
+ method call. The method still runs on the real object, so private fields keep working and the calls
76
+ the object makes to itself do not go through the proxy again.
77
+
78
+ ## The unhandled ones
79
+
80
+ `LoggingErrorHandler` replaces Angular's own and adds one line before it:
81
+
82
+ ```ts
83
+ { provide: ErrorHandler, useClass: LoggingErrorHandler }
84
+ ```
85
+
86
+ An error inside a `subscribe`, a `setTimeout` or a template binding passes through neither the service
87
+ layer nor a handler proxy. This is the only place it shows up.
88
+
89
+ ## The family
90
+
91
+ `web-utils`, `web-model`, `web-services`, `web-component`, `web-errors` and `web-logging` are released
92
+ together and always share the same version. Install them at the same version.
93
+
94
+ ## License
95
+
96
+ Commercial. See `LICENSE.txt` in the package.
@@ -0,0 +1,494 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, ErrorHandler, inject, Injectable } from '@angular/core';
3
+
4
+ /**
5
+ * Ordine di gravita': <b>piu' basso e' piu' grave</b>.
6
+ *
7
+ * @remarks
8
+ * Un livello si scrive quando il suo rango e' minore o uguale a quello configurato: configurare `I`
9
+ * fa passare `F`, `E`, `W` e `I` e non `D` ne' `T`. E' la stessa tabella che il motore usa lato
10
+ * server, e le due devono restare uguali o la stessa configurazione produrrebbe due filtri diversi.
11
+ */
12
+ const APP_LOG_LEVEL_RANK = {
13
+ F: 1,
14
+ E: 2,
15
+ W: 3,
16
+ I: 4,
17
+ D: 5,
18
+ T: 6,
19
+ };
20
+ /**
21
+ * Il gancio con cui si accende il log.
22
+ *
23
+ * @remarks
24
+ * <b>Opzionale per costruzione.</b> Gli oggetti base lo cercano con `inject(..., { optional: true })`:
25
+ * dichiarato, si logga; non dichiarato, ogni chiamata di log e' un confronto con `null` e nient'altro.
26
+ * Togliere una riga di provider spegne il log dell'intera applicazione senza toccare una classe.
27
+ */
28
+ const APPLICATION_LOG_WRITER = new InjectionToken('QuanticDigit.ApplicationLogWriter');
29
+ /**
30
+ * Chiave di `sessionStorage` sotto cui vive la configurazione.
31
+ *
32
+ * @remarks
33
+ * `sessionStorage` e non `localStorage`: la configurazione arriva al login ed e' dell'utente e della
34
+ * sessione: il `sessionStorage.clear()` dell'uscita la porta via senza che nessuno se ne occupi.
35
+ */
36
+ const APPLICATION_LOG_CONFIG_KEY = 'log.config';
37
+
38
+ /**
39
+ * La configurazione del log, letta da `sessionStorage`.
40
+ *
41
+ * @remarks
42
+ * <b>Statica e senza iniezione.</b> La interrogano oggetti che non sono servizi — un proxy su un
43
+ * gestore di eventi, un logger costruito a mano dentro il costruttore di un component — e che quindi
44
+ * non hanno un contesto da cui farsi dare nulla.
45
+ *
46
+ * <b>La regola e' quella del server.</b> Le stesse quattro combinazioni di `startsWith`/`endsWith` e
47
+ * la stessa scelta del livello piu' permissivo fra le righe che corrispondono: una configurazione
48
+ * copiata dal database si comporta qui come si comporterebbe la'. <b>Nessuna riga che corrisponde
49
+ * significa non loggare</b>, non "loggare tutto": e' cio' che rende innocuo distribuire il codice di
50
+ * log prima di aver deciso cosa osservare.
51
+ */
52
+ class ApplicationLogConfig {
53
+ /** Ultimo JSON letto, per non deserializzarlo a ogni evento. */
54
+ static lastRaw = null;
55
+ static lastParsed = [];
56
+ /** Scrive la configurazione appena ricevuta dal server. */
57
+ static store(items) {
58
+ try {
59
+ sessionStorage.setItem(APPLICATION_LOG_CONFIG_KEY, JSON.stringify(items ?? []));
60
+ }
61
+ catch {
62
+ // Uno storage pieno o negato non e' un problema dell'applicazione: si resta senza log.
63
+ }
64
+ ApplicationLogConfig.lastRaw = null;
65
+ }
66
+ /** Dimentica la configurazione: da qui in poi non si logga piu' nulla. */
67
+ static clear() {
68
+ try {
69
+ sessionStorage.removeItem(APPLICATION_LOG_CONFIG_KEY);
70
+ }
71
+ catch {
72
+ // Come sopra.
73
+ }
74
+ ApplicationLogConfig.lastRaw = null;
75
+ ApplicationLogConfig.lastParsed = [];
76
+ }
77
+ /** Le righe attive, cosi' come sono. */
78
+ static items() {
79
+ let raw = null;
80
+ try {
81
+ raw = sessionStorage.getItem(APPLICATION_LOG_CONFIG_KEY);
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ if (raw == null || raw === '') {
87
+ ApplicationLogConfig.lastRaw = null;
88
+ ApplicationLogConfig.lastParsed = [];
89
+ return [];
90
+ }
91
+ if (raw === ApplicationLogConfig.lastRaw) {
92
+ return ApplicationLogConfig.lastParsed;
93
+ }
94
+ let parsed = [];
95
+ try {
96
+ const read = JSON.parse(raw);
97
+ parsed = Array.isArray(read) ? read : [];
98
+ }
99
+ catch {
100
+ parsed = [];
101
+ }
102
+ ApplicationLogConfig.lastRaw = raw;
103
+ ApplicationLogConfig.lastParsed = parsed;
104
+ return parsed;
105
+ }
106
+ /**
107
+ * Fino a che livello si scrive per questo percorso, oppure `null` se non si scrive affatto.
108
+ *
109
+ * @remarks
110
+ * Fra piu' righe che corrispondono vince la <b>piu' verbosa</b>: chi aggiunge una riga a `D` per
111
+ * un solo percorso non deve prima togliere quella generale a `I`.
112
+ */
113
+ static maxLevelFor(path) {
114
+ const rows = ApplicationLogConfig.items();
115
+ let best = null;
116
+ let bestRank = 0;
117
+ for (const row of rows) {
118
+ if (row == null || row.disabled) {
119
+ continue;
120
+ }
121
+ if (!ApplicationLogConfig.matches(path, row)) {
122
+ continue;
123
+ }
124
+ const rank = APP_LOG_LEVEL_RANK[row.level] ?? 0;
125
+ if (rank > bestRank) {
126
+ bestRank = rank;
127
+ best = row.level;
128
+ }
129
+ }
130
+ return best;
131
+ }
132
+ /** Vero se il livello richiesto rientra in quello configurato per il percorso. */
133
+ static isLoggable(level, path) {
134
+ const max = ApplicationLogConfig.maxLevelFor(path);
135
+ if (max == null) {
136
+ return false;
137
+ }
138
+ return APP_LOG_LEVEL_RANK[level] <= APP_LOG_LEVEL_RANK[max];
139
+ }
140
+ /**
141
+ * Le quattro combinazioni di confronto.
142
+ *
143
+ * @remarks
144
+ * Entrambi i flag significa uguaglianza esatta, nessuno dei due significa "contiene": e' la
145
+ * lettura che ne fa `LoggerBase.FilterConfig` lato .NET, e non va reinventata.
146
+ */
147
+ static matches(path, row) {
148
+ const expected = row.path ?? '';
149
+ if (row.startsWith && row.endsWith) {
150
+ return path === expected;
151
+ }
152
+ if (row.startsWith) {
153
+ return path.startsWith(expected);
154
+ }
155
+ if (row.endsWith) {
156
+ return path.endsWith(expected);
157
+ }
158
+ return path.includes(expected);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Il punto da cui si logga.
164
+ *
165
+ * @remarks
166
+ * <b>Non e' un servizio.</b> Lo costruiscono gli oggetti base dentro il proprio costruttore, con il
167
+ * writer che hanno trovato — o non trovato — nel contesto di iniezione. Un logger senza writer non e'
168
+ * un errore: e' il caso normale di chi non ha acceso il log, e ogni sua chiamata costa un confronto.
169
+ *
170
+ * <b>Non lancia mai.</b> Un log che rompe la schermata che stava osservando e' peggio di nessun log:
171
+ * tutto cio' che sta dentro `log` e' avvolto, writer compreso.
172
+ */
173
+ class ApplicationLogger {
174
+ writer;
175
+ path;
176
+ constructor(writer, path) {
177
+ this.writer = writer;
178
+ this.path = path;
179
+ }
180
+ /**
181
+ * Vero se questo livello verrebbe scritto.
182
+ *
183
+ * @remarks
184
+ * Va chiamata <b>prima</b> di costruire i dati accessori: e' l'unico modo perche' serializzare gli
185
+ * argomenti di una chiamata costi zero quando il livello `T` non e' acceso.
186
+ */
187
+ isLoggable(level, subPath) {
188
+ if (this.writer == null) {
189
+ return false;
190
+ }
191
+ try {
192
+ return ApplicationLogConfig.isLoggable(level, this.fullPath(subPath));
193
+ }
194
+ catch {
195
+ return false;
196
+ }
197
+ }
198
+ /** Scrive un evento, se la configurazione lo prevede. */
199
+ log(level, subPath, message, params, trace) {
200
+ const writer = this.writer;
201
+ if (writer == null) {
202
+ return;
203
+ }
204
+ try {
205
+ const path = this.fullPath(subPath ?? undefined);
206
+ if (!ApplicationLogConfig.isLoggable(level, path)) {
207
+ return;
208
+ }
209
+ writer.write({
210
+ level,
211
+ path,
212
+ message: message ?? '',
213
+ params,
214
+ trace,
215
+ createdAt: new Date(),
216
+ });
217
+ }
218
+ catch {
219
+ // Un log che fallisce non ha niente da dire a nessuno.
220
+ }
221
+ }
222
+ /** Un logger sullo stesso writer, un segmento piu' in basso. */
223
+ child(segment) {
224
+ return new ApplicationLogger(this.writer, this.fullPath(segment));
225
+ }
226
+ fullPath(subPath) {
227
+ return subPath == null || subPath === '' ? this.path : `${this.path}.${subPath}`;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Il gestore di ultima istanza di Angular, con una riga di log in piu'.
233
+ *
234
+ * @remarks
235
+ * <b>Copre cio' che nessun altro vede.</b> Il raccoglitore di errori conosce quelli che arrivano dal
236
+ * livello di servizio; il proxy conosce quelli che nascono dentro un gestore. Un errore in un
237
+ * `subscribe`, in un `setTimeout` o in un binding del template non passa da nessuno dei due e finisce
238
+ * qui: e' l'unico punto in cui si scopre che una schermata si e' rotta senza dirlo.
239
+ *
240
+ * Livello `F` e percorso `errors.unhandled`: cio' che arriva qui non e' stato gestito da nessuno, per
241
+ * definizione.
242
+ *
243
+ * Si registra come si registra qualsiasi `ErrorHandler`, e il comportamento normale di Angular resta
244
+ * perche' `super.handleError` viene comunque chiamato.
245
+ */
246
+ class LoggingErrorHandler extends ErrorHandler {
247
+ logger = new ApplicationLogger(inject(APPLICATION_LOG_WRITER, { optional: true }), 'errors');
248
+ handleError(error) {
249
+ try {
250
+ const asError = error;
251
+ this.logger.log('F', 'unhandled', asError?.message ?? String(error), undefined, asError?.stack);
252
+ }
253
+ catch {
254
+ // Il gestore di ultima istanza non puo' essere quello che lancia.
255
+ }
256
+ super.handleError(error);
257
+ }
258
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LoggingErrorHandler, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
259
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LoggingErrorHandler });
260
+ }
261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LoggingErrorHandler, decorators: [{
262
+ type: Injectable
263
+ }] });
264
+
265
+ /** Profondita' oltre la quale un oggetto diventa una sigla. */
266
+ const DEFAULT_DEPTH = 2;
267
+ /** Caratteri stimati oltre i quali si smette di descrivere. */
268
+ const DEFAULT_BUDGET = 2048;
269
+ /** Lunghezza massima di una stringa descritta per intero. */
270
+ const MAX_TEXT_LENGTH = 512;
271
+ /**
272
+ * Riduce un valore qualsiasi a qualcosa che si puo' serializzare e mandare via.
273
+ *
274
+ * @remarks
275
+ * <b>Serve perche' gli argomenti veri non sono serializzabili.</b> Un gestore di eventi riceve
276
+ * l'evento di DevExtreme, che porta con se' il componente, l'elemento del DOM e da li' l'intera
277
+ * pagina: `JSON.stringify` ci gira in tondo o produce megabyte. Qui quell'oggetto diventa
278
+ * `{ type: 'Object' }` e la chiamata resta leggibile senza essere pericolosa.
279
+ *
280
+ * Tre limiti, tutti volontari: profondita', numero di caratteri, e riconoscimento delle cose che non
281
+ * vanno descritte (nodi, eventi, funzioni). Chi legge un log vuole sapere <i>che</i> e' stato passato
282
+ * un evento, non cosa contenga.
283
+ */
284
+ function safeValue(value, depth = DEFAULT_DEPTH, budget = DEFAULT_BUDGET) {
285
+ return reduce(value, depth, { left: budget }, new WeakSet());
286
+ }
287
+ function reduce(value, depth, budget, seen) {
288
+ if (budget.left <= 0) {
289
+ return '[truncated]';
290
+ }
291
+ if (value === null || value === undefined) {
292
+ return value ?? null;
293
+ }
294
+ const kind = typeof value;
295
+ if (kind === 'string') {
296
+ const text = value;
297
+ budget.left -= text.length;
298
+ return text.length > MAX_TEXT_LENGTH ? `${text.substring(0, MAX_TEXT_LENGTH)}…` : text;
299
+ }
300
+ if (kind === 'number' || kind === 'boolean') {
301
+ budget.left -= 8;
302
+ return value;
303
+ }
304
+ if (kind === 'bigint' || kind === 'symbol') {
305
+ budget.left -= 8;
306
+ return String(value);
307
+ }
308
+ if (kind === 'function') {
309
+ budget.left -= 10;
310
+ return '[function]';
311
+ }
312
+ if (value instanceof Date) {
313
+ budget.left -= 24;
314
+ return value.toISOString();
315
+ }
316
+ if (value instanceof Error) {
317
+ budget.left -= 32;
318
+ return { name: value.name, message: value.message };
319
+ }
320
+ const target = value;
321
+ if (seen.has(target)) {
322
+ return '[circular]';
323
+ }
324
+ if (isOpaque(target)) {
325
+ budget.left -= 16;
326
+ return { type: typeNameOf(target) };
327
+ }
328
+ if (depth <= 0) {
329
+ budget.left -= 16;
330
+ return { type: typeNameOf(target) };
331
+ }
332
+ seen.add(target);
333
+ try {
334
+ if (Array.isArray(value)) {
335
+ const list = [];
336
+ for (const item of value) {
337
+ if (budget.left <= 0) {
338
+ list.push('[truncated]');
339
+ break;
340
+ }
341
+ list.push(reduce(item, depth - 1, budget, seen));
342
+ }
343
+ return list;
344
+ }
345
+ const result = {};
346
+ for (const key of Object.keys(target)) {
347
+ if (budget.left <= 0) {
348
+ result['…'] = '[truncated]';
349
+ break;
350
+ }
351
+ budget.left -= key.length;
352
+ result[key] = reduce(target[key], depth - 1, budget, seen);
353
+ }
354
+ return result;
355
+ }
356
+ catch {
357
+ return '[unreadable]';
358
+ }
359
+ finally {
360
+ seen.delete(target);
361
+ }
362
+ }
363
+ /**
364
+ * Cose che non si descrivono mai: nodi del DOM, eventi, e gli argomenti di DevExtreme.
365
+ *
366
+ * @remarks
367
+ * Gli eventi di DevExtreme non ereditano da `Event`: sono oggetti semplici con `component` ed
368
+ * `element` dentro, e da li' si arriva a tutto. Si riconoscono dalla forma perche' non c'e' un tipo
369
+ * da cui dipendere, e questo pacchetto non dipende da DevExtreme.
370
+ */
371
+ function isOpaque(value) {
372
+ if (typeof Node !== 'undefined' && value instanceof Node) {
373
+ return true;
374
+ }
375
+ if (typeof Event !== 'undefined' && value instanceof Event) {
376
+ return true;
377
+ }
378
+ const candidate = value;
379
+ return candidate['component'] !== undefined && candidate['element'] !== undefined;
380
+ }
381
+ function typeNameOf(value) {
382
+ try {
383
+ return value.constructor?.name ?? 'Object';
384
+ }
385
+ catch {
386
+ return 'Object';
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Metodi che non si loggano mai.
392
+ *
393
+ * @remarks
394
+ * Sono l'impalcatura: la costruzione degli oggetti base e i ganci del ciclo di vita di Angular, che
395
+ * il component gia' registra per conto proprio. Loggarli raddoppierebbe le righe senza aggiungere
396
+ * nulla. Tutto cio' che inizia per `ng` e' escluso per la stessa ragione.
397
+ */
398
+ const DEFAULT_EXCLUDED = new Set(['constructor', 'init', 'onInit', 'getActionHandler']);
399
+ /**
400
+ * Avvolge un oggetto perche' ogni sua chiamata di metodo lasci traccia.
401
+ *
402
+ * @remarks
403
+ * <b>E' cio' che rende il log gratuito per chi scrive le schermate.</b> Il gestore di eventi e quello
404
+ * delle azioni non sanno di essere osservati e non hanno una riga in piu': l'oggetto base li consegna
405
+ * avvolti, e da quel momento ogni metodo chiamato dal template o dal gestore registra ingresso,
406
+ * uscita, durata ed eventuale eccezione.
407
+ *
408
+ * <b>Il metodo viene invocato sull'oggetto vero</b>, non sul proxy: i campi privati `#` continuano a
409
+ * funzionare, e le chiamate che l'oggetto fa a se' stesso non riattraversano il proxy. E' voluto —
410
+ * si vuole la chiamata che arriva da fuori, non la ricorsione interna.
411
+ *
412
+ * Senza writer o senza configurazione il costo e' la trap `get` piu' un confronto: nessun oggetto
413
+ * costruito, nessun argomento serializzato.
414
+ */
415
+ function wrapWithLogging(target, logger, excluded = DEFAULT_EXCLUDED) {
416
+ if (target == null) {
417
+ return target;
418
+ }
419
+ const wrappers = new Map();
420
+ return new Proxy(target, {
421
+ get(instance, prop) {
422
+ const value = Reflect.get(instance, prop, instance);
423
+ if (typeof prop === 'symbol' || typeof value !== 'function' || excluded.has(prop) || prop.startsWith('ng')) {
424
+ return value;
425
+ }
426
+ const cached = wrappers.get(prop);
427
+ if (cached != null) {
428
+ return cached;
429
+ }
430
+ const original = value;
431
+ const name = prop;
432
+ const wrapper = function (...args) {
433
+ if (!logger.isLoggable('D', name)) {
434
+ return original.apply(instance, args);
435
+ }
436
+ const detailed = logger.isLoggable('T', name);
437
+ logger.log('D', name, 'enter', detailed ? { args: args.map((A) => safeValue(A)) } : undefined);
438
+ const startedAt = now();
439
+ try {
440
+ const result = original.apply(instance, args);
441
+ if (result instanceof Promise) {
442
+ result.then((resolved) => logger.log('D', name, 'exit', detailed ? { durationMs: now() - startedAt, result: safeValue(resolved) } : { durationMs: now() - startedAt }), (rejection) => logger.log('E', name, 'error', { durationMs: now() - startedAt }, stackOf(rejection)));
443
+ return result;
444
+ }
445
+ logger.log('D', name, 'exit', detailed ? { durationMs: now() - startedAt, result: safeValue(result) } : { durationMs: now() - startedAt });
446
+ return result;
447
+ }
448
+ catch (error) {
449
+ logger.log('E', name, 'error', { durationMs: now() - startedAt }, stackOf(error));
450
+ // Il log osserva, non decide: l'eccezione prosegue verso chi la sa gestire.
451
+ throw error;
452
+ }
453
+ };
454
+ wrappers.set(prop, wrapper);
455
+ return wrapper;
456
+ },
457
+ });
458
+ }
459
+ function now() {
460
+ return typeof performance !== 'undefined' && performance.now != null ? performance.now() : Date.now();
461
+ }
462
+ function stackOf(error) {
463
+ const asError = error;
464
+ return asError?.stack ?? String(error);
465
+ }
466
+
467
+ /**
468
+ * Superficie pubblica di `@quanticdigit/web-logging`.
469
+ *
470
+ * Il log applicativo del client: ingresso e uscita dalle schermate, ogni chiamata ai gestori di
471
+ * eventi e di azioni, e gli errori — tutto filtrato da una configurazione che decide il server e
472
+ * recapitato da un writer che fornisce il prodotto.
473
+ *
474
+ * <b>Due sole cose vanno capite per usarlo.</b>
475
+ *
476
+ * 1. **il writer** — si dichiara `APPLICATION_LOG_WRITER` e il log si accende; non dichiararlo lo
477
+ * spegne ovunque, perche' gli oggetti base lo cercano come opzionale e senza di lui non fanno
478
+ * nulla. Questo pacchetto non parla con nessun backend: non sa quale sia.
479
+ * 2. **la configurazione** — sta in `sessionStorage` sotto `log.config` e arriva dal server al
480
+ * momento del login. Senza righe che corrispondono <b>non si logga</b>: il codice di log puo'
481
+ * essere distribuito molto prima di aver deciso cosa osservare, e non costa nulla finche' nessuno
482
+ * lo accende.
483
+ *
484
+ * Chi usa `@quanticdigit/web-component` e `@quanticdigit/web-errors` non deve fare altro: gli oggetti
485
+ * base sono gia' agganciati. Il resto di questa superficie serve a chi vuole loggare a mano
486
+ * (`ApplicationLogger`) o avvolgere oggetti propri (`wrapWithLogging`).
487
+ */
488
+
489
+ /**
490
+ * Generated bundle index. Do not edit.
491
+ */
492
+
493
+ export { APPLICATION_LOG_CONFIG_KEY, APPLICATION_LOG_WRITER, APP_LOG_LEVEL_RANK, ApplicationLogConfig, ApplicationLogger, DEFAULT_EXCLUDED, LoggingErrorHandler, safeValue, wrapWithLogging };
494
+ //# sourceMappingURL=quanticdigit-web-logging.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quanticdigit-web-logging.mjs","sources":["../../../projects/web-logging/src/lib/contracts.ts","../../../projects/web-logging/src/lib/application-log-config.ts","../../../projects/web-logging/src/lib/application-logger.ts","../../../projects/web-logging/src/lib/logging-error-handler.ts","../../../projects/web-logging/src/lib/safe-value.ts","../../../projects/web-logging/src/lib/logging-proxy.ts","../../../projects/web-logging/src/public-api.ts","../../../projects/web-logging/src/quanticdigit-web-logging.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * Livello di un evento di log, nella forma a una lettera che usa il server.\n *\n * @remarks\n * Sono i codici della tabella `Log.LogLevelType`: Fatal, Error, Warning, Info, Debug, Trace. Il\n * client li usa identici a come li scrive il database, cosi' il payload non ha bisogno di essere\n * tradotto da nessuna parte.\n */\nexport type AppLogLevelCode = 'F' | 'E' | 'W' | 'I' | 'D' | 'T';\n\n/**\n * Ordine di gravita': <b>piu' basso e' piu' grave</b>.\n *\n * @remarks\n * Un livello si scrive quando il suo rango e' minore o uguale a quello configurato: configurare `I`\n * fa passare `F`, `E`, `W` e `I` e non `D` ne' `T`. E' la stessa tabella che il motore usa lato\n * server, e le due devono restare uguali o la stessa configurazione produrrebbe due filtri diversi.\n */\nexport const APP_LOG_LEVEL_RANK: Readonly<Record<AppLogLevelCode, number>> = {\n F: 1,\n E: 2,\n W: 3,\n I: 4,\n D: 5,\n T: 6,\n};\n\n/** Un evento pronto per essere scritto. */\nexport interface IApplicationLogEntry {\n /** Gravita'. */\n level: AppLogLevelCode;\n /** Percorso completo, es. `app-reports.eventHandler.onClickApplyFilters`. */\n path: string;\n /** Cosa e' successo: `enter`, `exit`, `init`, il messaggio dell'errore. */\n message: string;\n /** Dati accessori gia' resi sicuri da `safeValue`. */\n params?: Record<string, unknown>;\n /** Stack, quando c'e'. */\n trace?: string;\n /** Momento in cui l'evento e' nato, non quello in cui viene spedito. */\n createdAt: Date;\n}\n\n/**\n * Dove finiscono gli eventi.\n *\n * @remarks\n * <b>E' la sola cosa che il prodotto deve fornire.</b> Questo pacchetto decide <i>se</i> e <i>cosa</i>\n * loggare; come recapitarlo — accodare, raggruppare, chiamare la propria WebApi — lo sa solo chi ha\n * un backend, e cambia da prodotto a prodotto.\n *\n * `write` non deve mai lanciare e non deve mai essere sincrona verso la rete: chi la chiama e' nel\n * mezzo di un metodo applicativo e non ha alcuna intenzione di aspettare.\n */\nexport interface IApplicationLogWriter {\n write(entry: IApplicationLogEntry): void;\n}\n\n/**\n * Il gancio con cui si accende il log.\n *\n * @remarks\n * <b>Opzionale per costruzione.</b> Gli oggetti base lo cercano con `inject(..., { optional: true })`:\n * dichiarato, si logga; non dichiarato, ogni chiamata di log e' un confronto con `null` e nient'altro.\n * Togliere una riga di provider spegne il log dell'intera applicazione senza toccare una classe.\n */\nexport const APPLICATION_LOG_WRITER = new InjectionToken<IApplicationLogWriter>('QuanticDigit.ApplicationLogWriter');\n\n/**\n * Una riga di configurazione, nella forma in cui il client la tiene.\n *\n * @remarks\n * Rispecchia `Log.ApplicationLogConfig`: un pezzo di percorso, come confrontarlo, fino a che livello\n * scrivere. `path` vuoto con `startsWith` vero e' la riga buona per tutto.\n */\nexport interface IApplicationLogConfigItem {\n path: string;\n startsWith: boolean;\n endsWith: boolean;\n level: AppLogLevelCode;\n disabled: boolean;\n}\n\n/**\n * Chiave di `sessionStorage` sotto cui vive la configurazione.\n *\n * @remarks\n * `sessionStorage` e non `localStorage`: la configurazione arriva al login ed e' dell'utente e della\n * sessione: il `sessionStorage.clear()` dell'uscita la porta via senza che nessuno se ne occupi.\n */\nexport const APPLICATION_LOG_CONFIG_KEY = 'log.config';\n","import { APPLICATION_LOG_CONFIG_KEY, APP_LOG_LEVEL_RANK, AppLogLevelCode, IApplicationLogConfigItem } from './contracts';\n\n/**\n * La configurazione del log, letta da `sessionStorage`.\n *\n * @remarks\n * <b>Statica e senza iniezione.</b> La interrogano oggetti che non sono servizi — un proxy su un\n * gestore di eventi, un logger costruito a mano dentro il costruttore di un component — e che quindi\n * non hanno un contesto da cui farsi dare nulla.\n *\n * <b>La regola e' quella del server.</b> Le stesse quattro combinazioni di `startsWith`/`endsWith` e\n * la stessa scelta del livello piu' permissivo fra le righe che corrispondono: una configurazione\n * copiata dal database si comporta qui come si comporterebbe la'. <b>Nessuna riga che corrisponde\n * significa non loggare</b>, non \"loggare tutto\": e' cio' che rende innocuo distribuire il codice di\n * log prima di aver deciso cosa osservare.\n */\nexport class ApplicationLogConfig {\n /** Ultimo JSON letto, per non deserializzarlo a ogni evento. */\n private static lastRaw: string | null = null;\n private static lastParsed: IApplicationLogConfigItem[] = [];\n\n /** Scrive la configurazione appena ricevuta dal server. */\n static store(items: IApplicationLogConfigItem[]): void {\n try {\n sessionStorage.setItem(APPLICATION_LOG_CONFIG_KEY, JSON.stringify(items ?? []));\n } catch {\n // Uno storage pieno o negato non e' un problema dell'applicazione: si resta senza log.\n }\n ApplicationLogConfig.lastRaw = null;\n }\n\n /** Dimentica la configurazione: da qui in poi non si logga piu' nulla. */\n static clear(): void {\n try {\n sessionStorage.removeItem(APPLICATION_LOG_CONFIG_KEY);\n } catch {\n // Come sopra.\n }\n ApplicationLogConfig.lastRaw = null;\n ApplicationLogConfig.lastParsed = [];\n }\n\n /** Le righe attive, cosi' come sono. */\n static items(): readonly IApplicationLogConfigItem[] {\n let raw: string | null = null;\n try {\n raw = sessionStorage.getItem(APPLICATION_LOG_CONFIG_KEY);\n } catch {\n return [];\n }\n\n if (raw == null || raw === '') {\n ApplicationLogConfig.lastRaw = null;\n ApplicationLogConfig.lastParsed = [];\n return [];\n }\n\n if (raw === ApplicationLogConfig.lastRaw) {\n return ApplicationLogConfig.lastParsed;\n }\n\n let parsed: IApplicationLogConfigItem[] = [];\n try {\n const read: unknown = JSON.parse(raw);\n parsed = Array.isArray(read) ? (read as IApplicationLogConfigItem[]) : [];\n } catch {\n parsed = [];\n }\n\n ApplicationLogConfig.lastRaw = raw;\n ApplicationLogConfig.lastParsed = parsed;\n return parsed;\n }\n\n /**\n * Fino a che livello si scrive per questo percorso, oppure `null` se non si scrive affatto.\n *\n * @remarks\n * Fra piu' righe che corrispondono vince la <b>piu' verbosa</b>: chi aggiunge una riga a `D` per\n * un solo percorso non deve prima togliere quella generale a `I`.\n */\n static maxLevelFor(path: string): AppLogLevelCode | null {\n const rows: readonly IApplicationLogConfigItem[] = ApplicationLogConfig.items();\n let best: AppLogLevelCode | null = null;\n let bestRank = 0;\n\n for (const row of rows) {\n if (row == null || row.disabled) {\n continue;\n }\n if (!ApplicationLogConfig.matches(path, row)) {\n continue;\n }\n const rank: number = APP_LOG_LEVEL_RANK[row.level] ?? 0;\n if (rank > bestRank) {\n bestRank = rank;\n best = row.level;\n }\n }\n\n return best;\n }\n\n /** Vero se il livello richiesto rientra in quello configurato per il percorso. */\n static isLoggable(level: AppLogLevelCode, path: string): boolean {\n const max: AppLogLevelCode | null = ApplicationLogConfig.maxLevelFor(path);\n if (max == null) {\n return false;\n }\n return APP_LOG_LEVEL_RANK[level] <= APP_LOG_LEVEL_RANK[max];\n }\n\n /**\n * Le quattro combinazioni di confronto.\n *\n * @remarks\n * Entrambi i flag significa uguaglianza esatta, nessuno dei due significa \"contiene\": e' la\n * lettura che ne fa `LoggerBase.FilterConfig` lato .NET, e non va reinventata.\n */\n private static matches(path: string, row: IApplicationLogConfigItem): boolean {\n const expected: string = row.path ?? '';\n if (row.startsWith && row.endsWith) {\n return path === expected;\n }\n if (row.startsWith) {\n return path.startsWith(expected);\n }\n if (row.endsWith) {\n return path.endsWith(expected);\n }\n return path.includes(expected);\n }\n}\n","import { ApplicationLogConfig } from './application-log-config';\nimport { AppLogLevelCode, IApplicationLogWriter } from './contracts';\n\n/**\n * Il punto da cui si logga.\n *\n * @remarks\n * <b>Non e' un servizio.</b> Lo costruiscono gli oggetti base dentro il proprio costruttore, con il\n * writer che hanno trovato — o non trovato — nel contesto di iniezione. Un logger senza writer non e'\n * un errore: e' il caso normale di chi non ha acceso il log, e ogni sua chiamata costa un confronto.\n *\n * <b>Non lancia mai.</b> Un log che rompe la schermata che stava osservando e' peggio di nessun log:\n * tutto cio' che sta dentro `log` e' avvolto, writer compreso.\n */\nexport class ApplicationLogger {\n constructor(\n private readonly writer: IApplicationLogWriter | null,\n readonly path: string\n ) {}\n\n /**\n * Vero se questo livello verrebbe scritto.\n *\n * @remarks\n * Va chiamata <b>prima</b> di costruire i dati accessori: e' l'unico modo perche' serializzare gli\n * argomenti di una chiamata costi zero quando il livello `T` non e' acceso.\n */\n isLoggable(level: AppLogLevelCode, subPath?: string): boolean {\n if (this.writer == null) {\n return false;\n }\n try {\n return ApplicationLogConfig.isLoggable(level, this.fullPath(subPath));\n } catch {\n return false;\n }\n }\n\n /** Scrive un evento, se la configurazione lo prevede. */\n log(level: AppLogLevelCode, subPath: string | null, message: string, params?: Record<string, unknown>, trace?: string): void {\n const writer: IApplicationLogWriter | null = this.writer;\n if (writer == null) {\n return;\n }\n try {\n const path: string = this.fullPath(subPath ?? undefined);\n if (!ApplicationLogConfig.isLoggable(level, path)) {\n return;\n }\n writer.write({\n level,\n path,\n message: message ?? '',\n params,\n trace,\n createdAt: new Date(),\n });\n } catch {\n // Un log che fallisce non ha niente da dire a nessuno.\n }\n }\n\n /** Un logger sullo stesso writer, un segmento piu' in basso. */\n child(segment: string): ApplicationLogger {\n return new ApplicationLogger(this.writer, this.fullPath(segment));\n }\n\n private fullPath(subPath?: string): string {\n return subPath == null || subPath === '' ? this.path : `${this.path}.${subPath}`;\n }\n}\n","import { ErrorHandler, Injectable, inject } from '@angular/core';\nimport { ApplicationLogger } from './application-logger';\nimport { APPLICATION_LOG_WRITER } from './contracts';\n\n/**\n * Il gestore di ultima istanza di Angular, con una riga di log in piu'.\n *\n * @remarks\n * <b>Copre cio' che nessun altro vede.</b> Il raccoglitore di errori conosce quelli che arrivano dal\n * livello di servizio; il proxy conosce quelli che nascono dentro un gestore. Un errore in un\n * `subscribe`, in un `setTimeout` o in un binding del template non passa da nessuno dei due e finisce\n * qui: e' l'unico punto in cui si scopre che una schermata si e' rotta senza dirlo.\n *\n * Livello `F` e percorso `errors.unhandled`: cio' che arriva qui non e' stato gestito da nessuno, per\n * definizione.\n *\n * Si registra come si registra qualsiasi `ErrorHandler`, e il comportamento normale di Angular resta\n * perche' `super.handleError` viene comunque chiamato.\n */\n@Injectable()\nexport class LoggingErrorHandler extends ErrorHandler {\n private readonly logger = new ApplicationLogger(inject(APPLICATION_LOG_WRITER, { optional: true }), 'errors');\n\n override handleError(error: unknown): void {\n try {\n const asError = error as Error | null;\n this.logger.log('F', 'unhandled', asError?.message ?? String(error), undefined, asError?.stack);\n } catch {\n // Il gestore di ultima istanza non puo' essere quello che lancia.\n }\n super.handleError(error);\n }\n}\n","/** Profondita' oltre la quale un oggetto diventa una sigla. */\nconst DEFAULT_DEPTH = 2;\n/** Caratteri stimati oltre i quali si smette di descrivere. */\nconst DEFAULT_BUDGET = 2048;\n/** Lunghezza massima di una stringa descritta per intero. */\nconst MAX_TEXT_LENGTH = 512;\n\n/**\n * Riduce un valore qualsiasi a qualcosa che si puo' serializzare e mandare via.\n *\n * @remarks\n * <b>Serve perche' gli argomenti veri non sono serializzabili.</b> Un gestore di eventi riceve\n * l'evento di DevExtreme, che porta con se' il componente, l'elemento del DOM e da li' l'intera\n * pagina: `JSON.stringify` ci gira in tondo o produce megabyte. Qui quell'oggetto diventa\n * `{ type: 'Object' }` e la chiamata resta leggibile senza essere pericolosa.\n *\n * Tre limiti, tutti volontari: profondita', numero di caratteri, e riconoscimento delle cose che non\n * vanno descritte (nodi, eventi, funzioni). Chi legge un log vuole sapere <i>che</i> e' stato passato\n * un evento, non cosa contenga.\n */\nexport function safeValue(value: unknown, depth: number = DEFAULT_DEPTH, budget: number = DEFAULT_BUDGET): unknown {\n return reduce(value, depth, { left: budget }, new WeakSet<object>());\n}\n\ninterface Budget {\n left: number;\n}\n\nfunction reduce(value: unknown, depth: number, budget: Budget, seen: WeakSet<object>): unknown {\n if (budget.left <= 0) {\n return '[truncated]';\n }\n\n if (value === null || value === undefined) {\n return value ?? null;\n }\n\n const kind: string = typeof value;\n\n if (kind === 'string') {\n const text: string = value as string;\n budget.left -= text.length;\n return text.length > MAX_TEXT_LENGTH ? `${text.substring(0, MAX_TEXT_LENGTH)}…` : text;\n }\n\n if (kind === 'number' || kind === 'boolean') {\n budget.left -= 8;\n return value;\n }\n\n if (kind === 'bigint' || kind === 'symbol') {\n budget.left -= 8;\n return String(value);\n }\n\n if (kind === 'function') {\n budget.left -= 10;\n return '[function]';\n }\n\n if (value instanceof Date) {\n budget.left -= 24;\n return value.toISOString();\n }\n\n if (value instanceof Error) {\n budget.left -= 32;\n return { name: value.name, message: value.message };\n }\n\n const target: object = value as object;\n\n if (seen.has(target)) {\n return '[circular]';\n }\n\n if (isOpaque(target)) {\n budget.left -= 16;\n return { type: typeNameOf(target) };\n }\n\n if (depth <= 0) {\n budget.left -= 16;\n return { type: typeNameOf(target) };\n }\n\n seen.add(target);\n try {\n if (Array.isArray(value)) {\n const list: unknown[] = [];\n for (const item of value) {\n if (budget.left <= 0) {\n list.push('[truncated]');\n break;\n }\n list.push(reduce(item, depth - 1, budget, seen));\n }\n return list;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(target)) {\n if (budget.left <= 0) {\n result['…'] = '[truncated]';\n break;\n }\n budget.left -= key.length;\n result[key] = reduce((target as Record<string, unknown>)[key], depth - 1, budget, seen);\n }\n return result;\n } catch {\n return '[unreadable]';\n } finally {\n seen.delete(target);\n }\n}\n\n/**\n * Cose che non si descrivono mai: nodi del DOM, eventi, e gli argomenti di DevExtreme.\n *\n * @remarks\n * Gli eventi di DevExtreme non ereditano da `Event`: sono oggetti semplici con `component` ed\n * `element` dentro, e da li' si arriva a tutto. Si riconoscono dalla forma perche' non c'e' un tipo\n * da cui dipendere, e questo pacchetto non dipende da DevExtreme.\n */\nfunction isOpaque(value: object): boolean {\n if (typeof Node !== 'undefined' && value instanceof Node) {\n return true;\n }\n if (typeof Event !== 'undefined' && value instanceof Event) {\n return true;\n }\n const candidate: Record<string, unknown> = value as Record<string, unknown>;\n return candidate['component'] !== undefined && candidate['element'] !== undefined;\n}\n\nfunction typeNameOf(value: object): string {\n try {\n return value.constructor?.name ?? 'Object';\n } catch {\n return 'Object';\n }\n}\n","import { ApplicationLogger } from './application-logger';\nimport { safeValue } from './safe-value';\n\n/**\n * Metodi che non si loggano mai.\n *\n * @remarks\n * Sono l'impalcatura: la costruzione degli oggetti base e i ganci del ciclo di vita di Angular, che\n * il component gia' registra per conto proprio. Loggarli raddoppierebbe le righe senza aggiungere\n * nulla. Tutto cio' che inizia per `ng` e' escluso per la stessa ragione.\n */\nexport const DEFAULT_EXCLUDED: ReadonlySet<string> = new Set<string>(['constructor', 'init', 'onInit', 'getActionHandler']);\n\n/**\n * Avvolge un oggetto perche' ogni sua chiamata di metodo lasci traccia.\n *\n * @remarks\n * <b>E' cio' che rende il log gratuito per chi scrive le schermate.</b> Il gestore di eventi e quello\n * delle azioni non sanno di essere osservati e non hanno una riga in piu': l'oggetto base li consegna\n * avvolti, e da quel momento ogni metodo chiamato dal template o dal gestore registra ingresso,\n * uscita, durata ed eventuale eccezione.\n *\n * <b>Il metodo viene invocato sull'oggetto vero</b>, non sul proxy: i campi privati `#` continuano a\n * funzionare, e le chiamate che l'oggetto fa a se' stesso non riattraversano il proxy. E' voluto —\n * si vuole la chiamata che arriva da fuori, non la ricorsione interna.\n *\n * Senza writer o senza configurazione il costo e' la trap `get` piu' un confronto: nessun oggetto\n * costruito, nessun argomento serializzato.\n */\nexport function wrapWithLogging<T extends object>(target: T, logger: ApplicationLogger, excluded: ReadonlySet<string> = DEFAULT_EXCLUDED): T {\n if (target == null) {\n return target;\n }\n\n const wrappers = new Map<string, (...args: unknown[]) => unknown>();\n\n return new Proxy(target, {\n get(instance: T, prop: string | symbol): unknown {\n const value: unknown = Reflect.get(instance, prop, instance);\n\n if (typeof prop === 'symbol' || typeof value !== 'function' || excluded.has(prop) || prop.startsWith('ng')) {\n return value;\n }\n\n const cached = wrappers.get(prop);\n if (cached != null) {\n return cached;\n }\n\n const original = value as (...args: unknown[]) => unknown;\n const name: string = prop;\n\n const wrapper = function (...args: unknown[]): unknown {\n if (!logger.isLoggable('D', name)) {\n return original.apply(instance, args);\n }\n\n const detailed: boolean = logger.isLoggable('T', name);\n logger.log('D', name, 'enter', detailed ? { args: args.map((A: unknown) => safeValue(A)) } : undefined);\n const startedAt: number = now();\n\n try {\n const result: unknown = original.apply(instance, args);\n\n if (result instanceof Promise) {\n result.then(\n (resolved: unknown) => logger.log('D', name, 'exit', detailed ? { durationMs: now() - startedAt, result: safeValue(resolved) } : { durationMs: now() - startedAt }),\n (rejection: unknown) => logger.log('E', name, 'error', { durationMs: now() - startedAt }, stackOf(rejection))\n );\n return result;\n }\n\n logger.log('D', name, 'exit', detailed ? { durationMs: now() - startedAt, result: safeValue(result) } : { durationMs: now() - startedAt });\n return result;\n } catch (error: unknown) {\n logger.log('E', name, 'error', { durationMs: now() - startedAt }, stackOf(error));\n // Il log osserva, non decide: l'eccezione prosegue verso chi la sa gestire.\n throw error;\n }\n };\n\n wrappers.set(prop, wrapper);\n return wrapper;\n },\n });\n}\n\nfunction now(): number {\n return typeof performance !== 'undefined' && performance.now != null ? performance.now() : Date.now();\n}\n\nfunction stackOf(error: unknown): string {\n const asError = error as Error | null;\n return asError?.stack ?? String(error);\n}\n","/**\n * Superficie pubblica di `@quanticdigit/web-logging`.\n *\n * Il log applicativo del client: ingresso e uscita dalle schermate, ogni chiamata ai gestori di\n * eventi e di azioni, e gli errori — tutto filtrato da una configurazione che decide il server e\n * recapitato da un writer che fornisce il prodotto.\n *\n * <b>Due sole cose vanno capite per usarlo.</b>\n *\n * 1. **il writer** — si dichiara `APPLICATION_LOG_WRITER` e il log si accende; non dichiararlo lo\n * spegne ovunque, perche' gli oggetti base lo cercano come opzionale e senza di lui non fanno\n * nulla. Questo pacchetto non parla con nessun backend: non sa quale sia.\n * 2. **la configurazione** — sta in `sessionStorage` sotto `log.config` e arriva dal server al\n * momento del login. Senza righe che corrispondono <b>non si logga</b>: il codice di log puo'\n * essere distribuito molto prima di aver deciso cosa osservare, e non costa nulla finche' nessuno\n * lo accende.\n *\n * Chi usa `@quanticdigit/web-component` e `@quanticdigit/web-errors` non deve fare altro: gli oggetti\n * base sono gia' agganciati. Il resto di questa superficie serve a chi vuole loggare a mano\n * (`ApplicationLogger`) o avvolgere oggetti propri (`wrapWithLogging`).\n */\n\nexport * from './lib/application-log-config';\nexport * from './lib/application-logger';\nexport * from './lib/contracts';\nexport * from './lib/logging-error-handler';\nexport * from './lib/logging-proxy';\nexport * from './lib/safe-value';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@quanticdigit/web-logging",
3
+ "version": "1.0.3",
4
+ "description": "Application log for the client: component lifecycle, handler calls and errors, filtered by a server-side configuration and sent through a writer you provide.",
5
+ "author": "Quantic Digit S.R.L.",
6
+ "license": "SEE LICENSE IN LICENSE.txt",
7
+ "homepage": "https://www.quanticdigit.it/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://gitlab.com/quanticdigit/library.git"
11
+ },
12
+ "sideEffects": false,
13
+ "peerDependencies": {
14
+ "@angular/core": "^21.2.0"
15
+ },
16
+ "dependencies": {
17
+ "tslib": "^2.3.0"
18
+ },
19
+ "module": "fesm2022/quanticdigit-web-logging.mjs",
20
+ "typings": "types/quanticdigit-web-logging.d.ts",
21
+ "exports": {
22
+ "./package.json": {
23
+ "default": "./package.json"
24
+ },
25
+ ".": {
26
+ "types": "./types/quanticdigit-web-logging.d.ts",
27
+ "default": "./fesm2022/quanticdigit-web-logging.mjs"
28
+ }
29
+ },
30
+ "type": "module"
31
+ }
@@ -0,0 +1,222 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, ErrorHandler } from '@angular/core';
3
+
4
+ /**
5
+ * Livello di un evento di log, nella forma a una lettera che usa il server.
6
+ *
7
+ * @remarks
8
+ * Sono i codici della tabella `Log.LogLevelType`: Fatal, Error, Warning, Info, Debug, Trace. Il
9
+ * client li usa identici a come li scrive il database, cosi' il payload non ha bisogno di essere
10
+ * tradotto da nessuna parte.
11
+ */
12
+ type AppLogLevelCode = 'F' | 'E' | 'W' | 'I' | 'D' | 'T';
13
+ /**
14
+ * Ordine di gravita': <b>piu' basso e' piu' grave</b>.
15
+ *
16
+ * @remarks
17
+ * Un livello si scrive quando il suo rango e' minore o uguale a quello configurato: configurare `I`
18
+ * fa passare `F`, `E`, `W` e `I` e non `D` ne' `T`. E' la stessa tabella che il motore usa lato
19
+ * server, e le due devono restare uguali o la stessa configurazione produrrebbe due filtri diversi.
20
+ */
21
+ declare const APP_LOG_LEVEL_RANK: Readonly<Record<AppLogLevelCode, number>>;
22
+ /** Un evento pronto per essere scritto. */
23
+ interface IApplicationLogEntry {
24
+ /** Gravita'. */
25
+ level: AppLogLevelCode;
26
+ /** Percorso completo, es. `app-reports.eventHandler.onClickApplyFilters`. */
27
+ path: string;
28
+ /** Cosa e' successo: `enter`, `exit`, `init`, il messaggio dell'errore. */
29
+ message: string;
30
+ /** Dati accessori gia' resi sicuri da `safeValue`. */
31
+ params?: Record<string, unknown>;
32
+ /** Stack, quando c'e'. */
33
+ trace?: string;
34
+ /** Momento in cui l'evento e' nato, non quello in cui viene spedito. */
35
+ createdAt: Date;
36
+ }
37
+ /**
38
+ * Dove finiscono gli eventi.
39
+ *
40
+ * @remarks
41
+ * <b>E' la sola cosa che il prodotto deve fornire.</b> Questo pacchetto decide <i>se</i> e <i>cosa</i>
42
+ * loggare; come recapitarlo — accodare, raggruppare, chiamare la propria WebApi — lo sa solo chi ha
43
+ * un backend, e cambia da prodotto a prodotto.
44
+ *
45
+ * `write` non deve mai lanciare e non deve mai essere sincrona verso la rete: chi la chiama e' nel
46
+ * mezzo di un metodo applicativo e non ha alcuna intenzione di aspettare.
47
+ */
48
+ interface IApplicationLogWriter {
49
+ write(entry: IApplicationLogEntry): void;
50
+ }
51
+ /**
52
+ * Il gancio con cui si accende il log.
53
+ *
54
+ * @remarks
55
+ * <b>Opzionale per costruzione.</b> Gli oggetti base lo cercano con `inject(..., { optional: true })`:
56
+ * dichiarato, si logga; non dichiarato, ogni chiamata di log e' un confronto con `null` e nient'altro.
57
+ * Togliere una riga di provider spegne il log dell'intera applicazione senza toccare una classe.
58
+ */
59
+ declare const APPLICATION_LOG_WRITER: InjectionToken<IApplicationLogWriter>;
60
+ /**
61
+ * Una riga di configurazione, nella forma in cui il client la tiene.
62
+ *
63
+ * @remarks
64
+ * Rispecchia `Log.ApplicationLogConfig`: un pezzo di percorso, come confrontarlo, fino a che livello
65
+ * scrivere. `path` vuoto con `startsWith` vero e' la riga buona per tutto.
66
+ */
67
+ interface IApplicationLogConfigItem {
68
+ path: string;
69
+ startsWith: boolean;
70
+ endsWith: boolean;
71
+ level: AppLogLevelCode;
72
+ disabled: boolean;
73
+ }
74
+ /**
75
+ * Chiave di `sessionStorage` sotto cui vive la configurazione.
76
+ *
77
+ * @remarks
78
+ * `sessionStorage` e non `localStorage`: la configurazione arriva al login ed e' dell'utente e della
79
+ * sessione: il `sessionStorage.clear()` dell'uscita la porta via senza che nessuno se ne occupi.
80
+ */
81
+ declare const APPLICATION_LOG_CONFIG_KEY = "log.config";
82
+
83
+ /**
84
+ * La configurazione del log, letta da `sessionStorage`.
85
+ *
86
+ * @remarks
87
+ * <b>Statica e senza iniezione.</b> La interrogano oggetti che non sono servizi — un proxy su un
88
+ * gestore di eventi, un logger costruito a mano dentro il costruttore di un component — e che quindi
89
+ * non hanno un contesto da cui farsi dare nulla.
90
+ *
91
+ * <b>La regola e' quella del server.</b> Le stesse quattro combinazioni di `startsWith`/`endsWith` e
92
+ * la stessa scelta del livello piu' permissivo fra le righe che corrispondono: una configurazione
93
+ * copiata dal database si comporta qui come si comporterebbe la'. <b>Nessuna riga che corrisponde
94
+ * significa non loggare</b>, non "loggare tutto": e' cio' che rende innocuo distribuire il codice di
95
+ * log prima di aver deciso cosa osservare.
96
+ */
97
+ declare class ApplicationLogConfig {
98
+ /** Ultimo JSON letto, per non deserializzarlo a ogni evento. */
99
+ private static lastRaw;
100
+ private static lastParsed;
101
+ /** Scrive la configurazione appena ricevuta dal server. */
102
+ static store(items: IApplicationLogConfigItem[]): void;
103
+ /** Dimentica la configurazione: da qui in poi non si logga piu' nulla. */
104
+ static clear(): void;
105
+ /** Le righe attive, cosi' come sono. */
106
+ static items(): readonly IApplicationLogConfigItem[];
107
+ /**
108
+ * Fino a che livello si scrive per questo percorso, oppure `null` se non si scrive affatto.
109
+ *
110
+ * @remarks
111
+ * Fra piu' righe che corrispondono vince la <b>piu' verbosa</b>: chi aggiunge una riga a `D` per
112
+ * un solo percorso non deve prima togliere quella generale a `I`.
113
+ */
114
+ static maxLevelFor(path: string): AppLogLevelCode | null;
115
+ /** Vero se il livello richiesto rientra in quello configurato per il percorso. */
116
+ static isLoggable(level: AppLogLevelCode, path: string): boolean;
117
+ /**
118
+ * Le quattro combinazioni di confronto.
119
+ *
120
+ * @remarks
121
+ * Entrambi i flag significa uguaglianza esatta, nessuno dei due significa "contiene": e' la
122
+ * lettura che ne fa `LoggerBase.FilterConfig` lato .NET, e non va reinventata.
123
+ */
124
+ private static matches;
125
+ }
126
+
127
+ /**
128
+ * Il punto da cui si logga.
129
+ *
130
+ * @remarks
131
+ * <b>Non e' un servizio.</b> Lo costruiscono gli oggetti base dentro il proprio costruttore, con il
132
+ * writer che hanno trovato — o non trovato — nel contesto di iniezione. Un logger senza writer non e'
133
+ * un errore: e' il caso normale di chi non ha acceso il log, e ogni sua chiamata costa un confronto.
134
+ *
135
+ * <b>Non lancia mai.</b> Un log che rompe la schermata che stava osservando e' peggio di nessun log:
136
+ * tutto cio' che sta dentro `log` e' avvolto, writer compreso.
137
+ */
138
+ declare class ApplicationLogger {
139
+ private readonly writer;
140
+ readonly path: string;
141
+ constructor(writer: IApplicationLogWriter | null, path: string);
142
+ /**
143
+ * Vero se questo livello verrebbe scritto.
144
+ *
145
+ * @remarks
146
+ * Va chiamata <b>prima</b> di costruire i dati accessori: e' l'unico modo perche' serializzare gli
147
+ * argomenti di una chiamata costi zero quando il livello `T` non e' acceso.
148
+ */
149
+ isLoggable(level: AppLogLevelCode, subPath?: string): boolean;
150
+ /** Scrive un evento, se la configurazione lo prevede. */
151
+ log(level: AppLogLevelCode, subPath: string | null, message: string, params?: Record<string, unknown>, trace?: string): void;
152
+ /** Un logger sullo stesso writer, un segmento piu' in basso. */
153
+ child(segment: string): ApplicationLogger;
154
+ private fullPath;
155
+ }
156
+
157
+ /**
158
+ * Il gestore di ultima istanza di Angular, con una riga di log in piu'.
159
+ *
160
+ * @remarks
161
+ * <b>Copre cio' che nessun altro vede.</b> Il raccoglitore di errori conosce quelli che arrivano dal
162
+ * livello di servizio; il proxy conosce quelli che nascono dentro un gestore. Un errore in un
163
+ * `subscribe`, in un `setTimeout` o in un binding del template non passa da nessuno dei due e finisce
164
+ * qui: e' l'unico punto in cui si scopre che una schermata si e' rotta senza dirlo.
165
+ *
166
+ * Livello `F` e percorso `errors.unhandled`: cio' che arriva qui non e' stato gestito da nessuno, per
167
+ * definizione.
168
+ *
169
+ * Si registra come si registra qualsiasi `ErrorHandler`, e il comportamento normale di Angular resta
170
+ * perche' `super.handleError` viene comunque chiamato.
171
+ */
172
+ declare class LoggingErrorHandler extends ErrorHandler {
173
+ private readonly logger;
174
+ handleError(error: unknown): void;
175
+ static ɵfac: i0.ɵɵFactoryDeclaration<LoggingErrorHandler, never>;
176
+ static ɵprov: i0.ɵɵInjectableDeclaration<LoggingErrorHandler>;
177
+ }
178
+
179
+ /**
180
+ * Metodi che non si loggano mai.
181
+ *
182
+ * @remarks
183
+ * Sono l'impalcatura: la costruzione degli oggetti base e i ganci del ciclo di vita di Angular, che
184
+ * il component gia' registra per conto proprio. Loggarli raddoppierebbe le righe senza aggiungere
185
+ * nulla. Tutto cio' che inizia per `ng` e' escluso per la stessa ragione.
186
+ */
187
+ declare const DEFAULT_EXCLUDED: ReadonlySet<string>;
188
+ /**
189
+ * Avvolge un oggetto perche' ogni sua chiamata di metodo lasci traccia.
190
+ *
191
+ * @remarks
192
+ * <b>E' cio' che rende il log gratuito per chi scrive le schermate.</b> Il gestore di eventi e quello
193
+ * delle azioni non sanno di essere osservati e non hanno una riga in piu': l'oggetto base li consegna
194
+ * avvolti, e da quel momento ogni metodo chiamato dal template o dal gestore registra ingresso,
195
+ * uscita, durata ed eventuale eccezione.
196
+ *
197
+ * <b>Il metodo viene invocato sull'oggetto vero</b>, non sul proxy: i campi privati `#` continuano a
198
+ * funzionare, e le chiamate che l'oggetto fa a se' stesso non riattraversano il proxy. E' voluto —
199
+ * si vuole la chiamata che arriva da fuori, non la ricorsione interna.
200
+ *
201
+ * Senza writer o senza configurazione il costo e' la trap `get` piu' un confronto: nessun oggetto
202
+ * costruito, nessun argomento serializzato.
203
+ */
204
+ declare function wrapWithLogging<T extends object>(target: T, logger: ApplicationLogger, excluded?: ReadonlySet<string>): T;
205
+
206
+ /**
207
+ * Riduce un valore qualsiasi a qualcosa che si puo' serializzare e mandare via.
208
+ *
209
+ * @remarks
210
+ * <b>Serve perche' gli argomenti veri non sono serializzabili.</b> Un gestore di eventi riceve
211
+ * l'evento di DevExtreme, che porta con se' il componente, l'elemento del DOM e da li' l'intera
212
+ * pagina: `JSON.stringify` ci gira in tondo o produce megabyte. Qui quell'oggetto diventa
213
+ * `{ type: 'Object' }` e la chiamata resta leggibile senza essere pericolosa.
214
+ *
215
+ * Tre limiti, tutti volontari: profondita', numero di caratteri, e riconoscimento delle cose che non
216
+ * vanno descritte (nodi, eventi, funzioni). Chi legge un log vuole sapere <i>che</i> e' stato passato
217
+ * un evento, non cosa contenga.
218
+ */
219
+ declare function safeValue(value: unknown, depth?: number, budget?: number): unknown;
220
+
221
+ export { APPLICATION_LOG_CONFIG_KEY, APPLICATION_LOG_WRITER, APP_LOG_LEVEL_RANK, ApplicationLogConfig, ApplicationLogger, DEFAULT_EXCLUDED, LoggingErrorHandler, safeValue, wrapWithLogging };
222
+ export type { AppLogLevelCode, IApplicationLogConfigItem, IApplicationLogEntry, IApplicationLogWriter };