@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 };
|