@matter/general 0.13.0-alpha.0-20250318-c1aa38b08 → 0.13.0-alpha.0-20250323-770919c6a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/codec/DnsCodec.js +1 -1
- package/dist/cjs/codec/DnsCodec.js.map +1 -1
- package/dist/cjs/log/Console.d.ts +21 -0
- package/dist/cjs/log/Console.d.ts.map +1 -0
- package/dist/cjs/log/Console.js +58 -0
- package/dist/cjs/log/Console.js.map +6 -0
- package/dist/cjs/log/Diagnostic.d.ts +6 -0
- package/dist/cjs/log/Diagnostic.d.ts.map +1 -1
- package/dist/cjs/log/Diagnostic.js +16 -0
- package/dist/cjs/log/Diagnostic.js.map +1 -1
- package/dist/cjs/log/LogDestination.d.ts +67 -0
- package/dist/cjs/log/LogDestination.d.ts.map +1 -0
- package/dist/cjs/log/LogDestination.js +65 -0
- package/dist/cjs/log/LogDestination.js.map +6 -0
- package/dist/cjs/log/LogFormat.d.ts +18 -12
- package/dist/cjs/log/LogFormat.d.ts.map +1 -1
- package/dist/cjs/log/LogFormat.js +17 -24
- package/dist/cjs/log/LogFormat.js.map +1 -1
- package/dist/cjs/log/LogLevel.d.ts +19 -9
- package/dist/cjs/log/LogLevel.d.ts.map +1 -1
- package/dist/cjs/log/LogLevel.js +40 -25
- package/dist/cjs/log/LogLevel.js.map +1 -1
- package/dist/cjs/log/Logger.d.ts +165 -109
- package/dist/cjs/log/Logger.d.ts.map +1 -1
- package/dist/cjs/log/Logger.js +307 -254
- package/dist/cjs/log/Logger.js.map +2 -2
- package/dist/cjs/log/index.d.ts +1 -0
- package/dist/cjs/log/index.d.ts.map +1 -1
- package/dist/cjs/log/index.js +1 -0
- package/dist/cjs/log/index.js.map +1 -1
- package/dist/cjs/time/Time.d.ts +1 -0
- package/dist/cjs/time/Time.d.ts.map +1 -1
- package/dist/cjs/time/Time.js +3 -0
- package/dist/cjs/time/Time.js.map +1 -1
- package/dist/cjs/transaction/Participant.d.ts +3 -3
- package/dist/cjs/transaction/Participant.d.ts.map +1 -1
- package/dist/cjs/transaction/Transaction.d.ts +20 -20
- package/dist/cjs/transaction/Tx.js +3 -3
- package/dist/cjs/transaction/Tx.js.map +1 -1
- package/dist/cjs/util/Construction.js +2 -2
- package/dist/cjs/util/Construction.js.map +1 -1
- package/dist/cjs/util/DataReadQueue.d.ts +4 -0
- package/dist/cjs/util/DataReadQueue.d.ts.map +1 -1
- package/dist/cjs/util/DataReadQueue.js +6 -0
- package/dist/cjs/util/DataReadQueue.js.map +1 -1
- package/dist/cjs/util/FormattedText.js +4 -2
- package/dist/cjs/util/FormattedText.js.map +1 -1
- package/dist/cjs/util/Observable.d.ts +137 -12
- package/dist/cjs/util/Observable.d.ts.map +1 -1
- package/dist/cjs/util/Observable.js +297 -35
- package/dist/cjs/util/Observable.js.map +2 -2
- package/dist/esm/codec/DnsCodec.js +1 -1
- package/dist/esm/codec/DnsCodec.js.map +1 -1
- package/dist/esm/log/Console.d.ts +21 -0
- package/dist/esm/log/Console.d.ts.map +1 -0
- package/dist/esm/log/Console.js +38 -0
- package/dist/esm/log/Console.js.map +6 -0
- package/dist/esm/log/Diagnostic.d.ts +6 -0
- package/dist/esm/log/Diagnostic.d.ts.map +1 -1
- package/dist/esm/log/Diagnostic.js +16 -0
- package/dist/esm/log/Diagnostic.js.map +1 -1
- package/dist/esm/log/LogDestination.d.ts +67 -0
- package/dist/esm/log/LogDestination.d.ts.map +1 -0
- package/dist/esm/log/LogDestination.js +45 -0
- package/dist/esm/log/LogDestination.js.map +6 -0
- package/dist/esm/log/LogFormat.d.ts +18 -12
- package/dist/esm/log/LogFormat.d.ts.map +1 -1
- package/dist/esm/log/LogFormat.js +17 -24
- package/dist/esm/log/LogFormat.js.map +1 -1
- package/dist/esm/log/LogLevel.d.ts +19 -9
- package/dist/esm/log/LogLevel.d.ts.map +1 -1
- package/dist/esm/log/LogLevel.js +40 -25
- package/dist/esm/log/LogLevel.js.map +1 -1
- package/dist/esm/log/Logger.d.ts +165 -109
- package/dist/esm/log/Logger.d.ts.map +1 -1
- package/dist/esm/log/Logger.js +307 -254
- package/dist/esm/log/Logger.js.map +2 -2
- package/dist/esm/log/index.d.ts +1 -0
- package/dist/esm/log/index.d.ts.map +1 -1
- package/dist/esm/log/index.js +1 -0
- package/dist/esm/log/index.js.map +1 -1
- package/dist/esm/time/Time.d.ts +1 -0
- package/dist/esm/time/Time.d.ts.map +1 -1
- package/dist/esm/time/Time.js +3 -0
- package/dist/esm/time/Time.js.map +1 -1
- package/dist/esm/transaction/Participant.d.ts +3 -3
- package/dist/esm/transaction/Participant.d.ts.map +1 -1
- package/dist/esm/transaction/Transaction.d.ts +20 -20
- package/dist/esm/transaction/Tx.js +3 -3
- package/dist/esm/transaction/Tx.js.map +1 -1
- package/dist/esm/util/Construction.js +2 -2
- package/dist/esm/util/Construction.js.map +1 -1
- package/dist/esm/util/DataReadQueue.d.ts +4 -0
- package/dist/esm/util/DataReadQueue.d.ts.map +1 -1
- package/dist/esm/util/DataReadQueue.js +6 -0
- package/dist/esm/util/DataReadQueue.js.map +1 -1
- package/dist/esm/util/FormattedText.js +4 -2
- package/dist/esm/util/FormattedText.js.map +1 -1
- package/dist/esm/util/Observable.d.ts +137 -12
- package/dist/esm/util/Observable.d.ts.map +1 -1
- package/dist/esm/util/Observable.js +297 -35
- package/dist/esm/util/Observable.js.map +2 -2
- package/package.json +2 -2
- package/src/codec/DnsCodec.ts +1 -1
- package/src/log/Console.ts +52 -0
- package/src/log/Diagnostic.ts +21 -0
- package/src/log/LogDestination.ts +113 -0
- package/src/log/LogFormat.ts +39 -36
- package/src/log/LogLevel.ts +55 -25
- package/src/log/Logger.ts +394 -314
- package/src/log/index.ts +1 -0
- package/src/time/Time.ts +4 -0
- package/src/transaction/Participant.ts +3 -3
- package/src/transaction/Transaction.ts +1 -1
- package/src/transaction/Tx.ts +3 -3
- package/src/util/Construction.ts +2 -2
- package/src/util/DataReadQueue.ts +7 -0
- package/src/util/FormattedText.ts +4 -2
- package/src/util/Observable.ts +453 -47
package/src/log/Logger.ts
CHANGED
|
@@ -10,97 +10,264 @@ import { ImplementationError, NotImplementedError } from "../MatterError.js";
|
|
|
10
10
|
import { Time } from "../time/Time.js";
|
|
11
11
|
import { Bytes } from "../util/Bytes.js";
|
|
12
12
|
import { Diagnostic } from "./Diagnostic.js";
|
|
13
|
+
import { LogDestination, LogDestinations } from "./LogDestination.js";
|
|
13
14
|
import { LogFormat } from "./LogFormat.js";
|
|
14
15
|
import { LogLevel } from "./LogLevel.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*/
|
|
19
|
-
export function consoleLogger(level: LogLevel, formattedLog: string) {
|
|
20
|
-
const console = (<any>consoleLogger).console;
|
|
21
|
-
switch (level) {
|
|
22
|
-
case LogLevel.DEBUG:
|
|
23
|
-
console.debug(formattedLog);
|
|
24
|
-
break;
|
|
25
|
-
case LogLevel.INFO:
|
|
26
|
-
console.info(formattedLog);
|
|
27
|
-
break;
|
|
28
|
-
case LogLevel.NOTICE:
|
|
29
|
-
console.info(formattedLog);
|
|
30
|
-
break;
|
|
31
|
-
case LogLevel.WARN:
|
|
32
|
-
console.warn(formattedLog);
|
|
33
|
-
break;
|
|
34
|
-
case LogLevel.ERROR:
|
|
35
|
-
console.error(formattedLog);
|
|
36
|
-
break;
|
|
37
|
-
case LogLevel.FATAL:
|
|
38
|
-
console.error(formattedLog);
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const globalConsole = console;
|
|
44
|
-
export namespace consoleLogger {
|
|
45
|
-
/**
|
|
46
|
-
* The target for consoleLogger.
|
|
47
|
-
*/
|
|
48
|
-
// eslint-disable-next-line prefer-const
|
|
49
|
-
export let console = globalConsole;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create a log formatter for a given format.
|
|
54
|
-
*/
|
|
55
|
-
function logFormatterFor(formatName: string): LoggerDefinition["logFormatter"] {
|
|
56
|
-
const format = LogFormat(formatName);
|
|
57
|
-
|
|
58
|
-
return (now, level, facility, prefix, ...values) =>
|
|
59
|
-
format(Diagnostic.message({ now, level, facility, prefix, values }));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Definition of one registered Logger.
|
|
64
|
-
*/
|
|
65
|
-
type LoggerDefinition = {
|
|
66
|
-
logIdentifier: string;
|
|
67
|
-
logFormatter: (now: Date, level: LogLevel, facility: string, prefix: string, ...values: any[]) => string;
|
|
68
|
-
log: (level: LogLevel, formattedLog: string) => void;
|
|
69
|
-
defaultLogLevel: LogLevel;
|
|
70
|
-
logLevels: { [facility: string]: LogLevel };
|
|
71
|
-
context?: Diagnostic.Context;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Logger that can be used to emit traces.
|
|
76
|
-
*
|
|
77
|
-
* The class supports adding multiple loggers for different targets. A default logger (identifier "default") is added on
|
|
78
|
-
* startup which logs to "console".
|
|
18
|
+
* matter.js logging API
|
|
79
19
|
*
|
|
80
20
|
* Usage:
|
|
81
21
|
*
|
|
82
|
-
* const
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* The configuration of the default logger can be adjusted by using the static properties of the Logger class:
|
|
22
|
+
* const logger = Logger.get("loggerName");
|
|
23
|
+
* logger.debug("My debug message", "my extra value to log");
|
|
86
24
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* - Logger.format = Format.ANSI enables colorization via ANSI escape sequences in default formatter
|
|
25
|
+
* matter.js writes logs to each {@link LogDestination} in {@link Logger.destinations}. By default a single destination
|
|
26
|
+
* named "default" writes to the JS console.
|
|
90
27
|
*
|
|
91
|
-
*
|
|
92
|
-
* configuration of these can be adjusted using static methods with the identifier as first parameter:
|
|
28
|
+
* You may adjust log verbosity and format by modifying the properties on destinations. For example:
|
|
93
29
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
30
|
+
* `Logger.format = LogFormat.PLAIN` sets all destinations to write plaintext
|
|
31
|
+
* `Logger.destinations.default.format = LogFormat.format.ansi` sets one destination to write ANSI
|
|
32
|
+
* `Logger.level = LogLevel.NOTICE` sets "notice" as the minimum level for all destinations
|
|
33
|
+
* `Logger.destinations.default.level = LogLevel.NOTICE` sets "notice" as level for one destination
|
|
97
34
|
*/
|
|
98
35
|
export class Logger {
|
|
99
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Log destinations.
|
|
38
|
+
*
|
|
39
|
+
* By default there is a single destination named "default". You can create new destinations using
|
|
40
|
+
* {@link LogDestination}. Add or remove destinations by modifying this object.
|
|
41
|
+
*
|
|
42
|
+
* Throws an error if you access a destination that doesn't exist.
|
|
43
|
+
*/
|
|
44
|
+
static destinations = LogDestinations();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The number of indents to print with messages.
|
|
48
|
+
*/
|
|
100
49
|
static nestingLevel: number;
|
|
50
|
+
|
|
101
51
|
readonly #name: string;
|
|
102
52
|
|
|
103
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Create a new logger for a facility.
|
|
55
|
+
*
|
|
56
|
+
* @param name the name of the facility
|
|
57
|
+
* @returns a new facility
|
|
58
|
+
*/
|
|
59
|
+
static get(name: string) {
|
|
60
|
+
return new Logger(name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the default log level.
|
|
65
|
+
*/
|
|
66
|
+
static get level() {
|
|
67
|
+
return LogDestination.defaults.level;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set log level as name or number for all destinations.
|
|
72
|
+
*/
|
|
73
|
+
static set level(level: LogLevel | string) {
|
|
74
|
+
level = LogLevel(level);
|
|
75
|
+
|
|
76
|
+
LogDestination.defaults.level = level;
|
|
77
|
+
|
|
78
|
+
for (const name in this.destinations) {
|
|
79
|
+
this.destinations[name].level = level;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the default facility levels.
|
|
85
|
+
*/
|
|
86
|
+
static get facilityLevels() {
|
|
87
|
+
return LogDestination.defaults.facilityLevels;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Set log level as name or number for facilities in all destinations.
|
|
92
|
+
*
|
|
93
|
+
* Existing levels that are not named in {@link levels} will remain unchanged.
|
|
94
|
+
*/
|
|
95
|
+
static set facilityLevels(levels: Record<string, LogLevel | string>) {
|
|
96
|
+
for (const name in levels) {
|
|
97
|
+
levels[name] = LogLevel(levels[name]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Object.assign(LogDestination.defaults.facilityLevels, levels);
|
|
101
|
+
|
|
102
|
+
for (const name in this.destinations) {
|
|
103
|
+
Object.assign(this.destinations[name].facilityLevels, levels);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the default format name.
|
|
109
|
+
*/
|
|
110
|
+
static get format(): string {
|
|
111
|
+
return LogDestination.defaults.format.name;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set the format for all destinations.
|
|
116
|
+
*/
|
|
117
|
+
static set format(format: string | LogFormat.Formatter) {
|
|
118
|
+
format = LogFormat(format);
|
|
119
|
+
|
|
120
|
+
LogDestination.defaults.format = format;
|
|
121
|
+
|
|
122
|
+
for (const name in this.destinations) {
|
|
123
|
+
this.destinations[name].format = format;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Mask a string with a given character. If unmaskedLength is provided then these number of characters will be
|
|
129
|
+
* shown unmasked.
|
|
130
|
+
*
|
|
131
|
+
* @param str String to mask
|
|
132
|
+
* @param maskChar character to mask with
|
|
133
|
+
* @param unmaskedLength number of characters to show unmasked in the beginning
|
|
134
|
+
*/
|
|
135
|
+
static maskString(str: string, maskChar = "*", unmaskedLength?: number) {
|
|
136
|
+
return str.substring(0, unmaskedLength ?? 0) + str.substring(unmaskedLength ?? 0).replace(/./g, maskChar);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Perform operations in a nested logging context. Messages will be
|
|
141
|
+
* indented while the context executes.
|
|
142
|
+
*/
|
|
143
|
+
static nest<T>(context: () => T): T {
|
|
144
|
+
this.nestingLevel++;
|
|
145
|
+
try {
|
|
146
|
+
return context();
|
|
147
|
+
} finally {
|
|
148
|
+
this.nestingLevel--;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Async version of nest().
|
|
154
|
+
*/
|
|
155
|
+
static async nestAsync(context: () => Promise<any>) {
|
|
156
|
+
this.nestingLevel++;
|
|
157
|
+
try {
|
|
158
|
+
return await context();
|
|
159
|
+
} finally {
|
|
160
|
+
this.nestingLevel--;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Unhandled error reporter.
|
|
166
|
+
*
|
|
167
|
+
* Some environments do not report full error details such as {@link Error#cause} and {@link AggregateError#errors}.
|
|
168
|
+
*
|
|
169
|
+
* To ensure these details are always recorded somewhere, unhandled errors may be reported here.
|
|
170
|
+
*
|
|
171
|
+
* To disable this behavior replace this function.
|
|
172
|
+
*/
|
|
173
|
+
static reportUnhandledError(error: Error) {
|
|
174
|
+
try {
|
|
175
|
+
Logger.get("Logger").fatal("Unhandled error detected:", error);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// We do not want to cause yet another error so if logging fails for any reason it goes unreported
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
constructor(name: string) {
|
|
182
|
+
this.#name = name;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
debug(...values: unknown[]) {
|
|
186
|
+
this.#log(LogLevel.DEBUG, values);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
info(...values: unknown[]) {
|
|
190
|
+
this.#log(LogLevel.INFO, values);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
notice(...values: unknown[]) {
|
|
194
|
+
this.#log(LogLevel.NOTICE, values);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
warn(...values: unknown[]) {
|
|
198
|
+
this.#log(LogLevel.WARN, values);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
error(...values: unknown[]) {
|
|
202
|
+
this.#log(LogLevel.ERROR, values);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fatal(...values: unknown[]) {
|
|
206
|
+
this.#log(LogLevel.FATAL, values);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
log(level: LogLevel, ...values: unknown[]) {
|
|
210
|
+
this.#log(level, values);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#log(level: LogLevel, values: unknown[]) {
|
|
214
|
+
for (const name in Logger.destinations) {
|
|
215
|
+
const dest = Logger.destinations[name];
|
|
216
|
+
|
|
217
|
+
if (level < (dest.facilityLevels?.[this.#name] ?? dest.level)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!dest.context) {
|
|
222
|
+
dest.context = Diagnostic.Context();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
dest.context.run(() =>
|
|
226
|
+
dest.add(
|
|
227
|
+
Diagnostic.message({
|
|
228
|
+
now: Time.now(),
|
|
229
|
+
facility: this.#name,
|
|
230
|
+
level,
|
|
231
|
+
prefix: nestingPrefix(),
|
|
232
|
+
values,
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//
|
|
240
|
+
// DEPRECATED API SURFACE FOLLOWS
|
|
241
|
+
//
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Stringify a value (BigInt aware) as JSON.
|
|
245
|
+
*
|
|
246
|
+
* @param data the value to stringify
|
|
247
|
+
* @returns the stringified value
|
|
248
|
+
*
|
|
249
|
+
* @deprecated use {@link Diagnostic.json}
|
|
250
|
+
*/
|
|
251
|
+
static toJSON(data: any) {
|
|
252
|
+
return JSON.stringify(data, (_, value) => {
|
|
253
|
+
if (typeof value === "bigint") {
|
|
254
|
+
return value.toString();
|
|
255
|
+
}
|
|
256
|
+
if (value instanceof Uint8Array) {
|
|
257
|
+
return Bytes.toHex(value);
|
|
258
|
+
}
|
|
259
|
+
if (value === undefined) {
|
|
260
|
+
return "undefined";
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add additional logger to the list of loggers including the default configuration.
|
|
268
|
+
*
|
|
269
|
+
* @deprecated use {@link destinations}
|
|
270
|
+
*/
|
|
104
271
|
public static addLogger(
|
|
105
272
|
identifier: string,
|
|
106
273
|
logger: (level: LogLevel, formattedLog: string) => void,
|
|
@@ -110,79 +277,70 @@ export class Logger {
|
|
|
110
277
|
logFormat?: string;
|
|
111
278
|
},
|
|
112
279
|
) {
|
|
113
|
-
if (
|
|
114
|
-
throw new
|
|
280
|
+
if (identifier in this.destinations) {
|
|
281
|
+
throw new ImplementationError(`Logger "${identifier}" already exists`);
|
|
115
282
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
defaultLogLevel
|
|
121
|
-
|
|
122
|
-
|
|
283
|
+
const dest = LogDestination({ name: identifier });
|
|
284
|
+
const legacy = adaptDestinationToLegacy(dest);
|
|
285
|
+
legacy.log = logger;
|
|
286
|
+
if (options?.defaultLogLevel !== undefined) {
|
|
287
|
+
legacy.defaultLogLevel = options.defaultLogLevel;
|
|
288
|
+
}
|
|
289
|
+
if (options?.logLevels !== undefined) {
|
|
290
|
+
legacy.logLevels = options.logLevels;
|
|
291
|
+
}
|
|
292
|
+
if (options?.logFormat !== undefined) {
|
|
293
|
+
legacy.logFormatter = logFormatterFor(options.logFormat);
|
|
294
|
+
}
|
|
295
|
+
this.destinations[identifier] = dest;
|
|
123
296
|
}
|
|
124
297
|
|
|
298
|
+
/**
|
|
299
|
+
* @deprecated use {@link destinations}
|
|
300
|
+
*/
|
|
125
301
|
public static removeLogger(identifier: string) {
|
|
126
|
-
|
|
127
|
-
if (index === -1) {
|
|
302
|
+
if (!(identifier in this.destinations)) {
|
|
128
303
|
throw new NotImplementedError(`Logger "${identifier}" does not exist`);
|
|
129
304
|
}
|
|
130
|
-
|
|
305
|
+
delete this.destinations[identifier];
|
|
131
306
|
}
|
|
132
307
|
|
|
133
308
|
/**
|
|
134
|
-
*
|
|
309
|
+
* Check if a logger with the matching identifier exists.
|
|
135
310
|
* @param identifier The identifier of the logger
|
|
311
|
+
*
|
|
312
|
+
* @deprecated use {@link destinations}
|
|
136
313
|
*/
|
|
137
|
-
public static
|
|
138
|
-
|
|
139
|
-
if (logger === undefined) {
|
|
140
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
141
|
-
}
|
|
142
|
-
return logger;
|
|
314
|
+
public static hasLoggerForIdentifier(identifier: string) {
|
|
315
|
+
return identifier in this.destinations;
|
|
143
316
|
}
|
|
144
317
|
|
|
145
318
|
/**
|
|
146
|
-
*
|
|
319
|
+
* Get the logger with the matching identifier.
|
|
320
|
+
* @param identifier The identifier of the logger
|
|
321
|
+
*
|
|
322
|
+
* @deprecated use {@link destinations}
|
|
147
323
|
*/
|
|
148
|
-
static
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let levelNum;
|
|
154
|
-
if (typeof level === "string") {
|
|
155
|
-
if (level.match(/^\d+$/)) {
|
|
156
|
-
levelNum = Number.parseInt(level);
|
|
157
|
-
} else {
|
|
158
|
-
levelNum = (LogLevel as unknown as Record<string, number | undefined>)[level.toUpperCase()];
|
|
159
|
-
if (levelNum === undefined) {
|
|
160
|
-
throw new ImplementationError(`Unsupported log level "${level}"`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
levelNum = level;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (LogLevel[levelNum] === undefined) {
|
|
168
|
-
throw new ImplementationError(`Unsupported log level "${level}"`);
|
|
324
|
+
public static getLoggerForIdentifier(identifier: string) {
|
|
325
|
+
const dest = this.destinations[identifier];
|
|
326
|
+
if (dest === undefined) {
|
|
327
|
+
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
169
328
|
}
|
|
170
|
-
|
|
171
|
-
Logger.defaultLogLevel = levelNum;
|
|
329
|
+
return adaptDestinationToLegacy(dest);
|
|
172
330
|
}
|
|
173
331
|
|
|
174
332
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* @param format the name of the formatter (see Format enum)
|
|
333
|
+
* @deprecated use {@link destinations}
|
|
178
334
|
*/
|
|
179
|
-
static
|
|
180
|
-
|
|
335
|
+
public static getLoggerforIdentifier(identifier: string) {
|
|
336
|
+
return this.getLoggerForIdentifier(identifier);
|
|
181
337
|
}
|
|
182
338
|
|
|
183
339
|
/**
|
|
184
340
|
* Set facility loglevels for the default logger.
|
|
185
341
|
* @param levels The levels to set
|
|
342
|
+
*
|
|
343
|
+
* @deprecated use {@link destinations}
|
|
186
344
|
*/
|
|
187
345
|
public static set logLevels(levels: { [facility: string]: LogLevel }) {
|
|
188
346
|
Logger.setLogLevelsForLogger("default", levels);
|
|
@@ -190,15 +348,19 @@ export class Logger {
|
|
|
190
348
|
|
|
191
349
|
/**
|
|
192
350
|
* Get facility loglevels for the default logger.
|
|
351
|
+
*
|
|
352
|
+
* @deprecated use {@link Logger.facilityLevels}
|
|
193
353
|
*/
|
|
194
354
|
public static get logLevels() {
|
|
195
|
-
return Logger.
|
|
355
|
+
return Logger.getLoggerForIdentifier("default").logLevels;
|
|
196
356
|
}
|
|
197
357
|
|
|
198
358
|
/**
|
|
199
359
|
* Set default loglevel for the default logger.
|
|
200
360
|
*
|
|
201
361
|
* @param level The level to set
|
|
362
|
+
*
|
|
363
|
+
* @deprecated use {@link Logger.level}
|
|
202
364
|
*/
|
|
203
365
|
public static set defaultLogLevel(level: LogLevel) {
|
|
204
366
|
Logger.setDefaultLoglevelForLogger("default", level);
|
|
@@ -206,31 +368,39 @@ export class Logger {
|
|
|
206
368
|
|
|
207
369
|
/**
|
|
208
370
|
* Get default loglevel for the default logger.
|
|
371
|
+
*
|
|
372
|
+
* @deprecated use {@link destinations}
|
|
209
373
|
*/
|
|
210
374
|
public static get defaultLogLevel() {
|
|
211
|
-
return Logger.
|
|
375
|
+
return Logger.getLoggerForIdentifier("default").defaultLogLevel;
|
|
212
376
|
}
|
|
213
377
|
|
|
214
378
|
/**
|
|
215
379
|
* Set the log function for the default logger.
|
|
216
380
|
*
|
|
217
381
|
* @param log The log function to set
|
|
382
|
+
*
|
|
383
|
+
* @deprecated use {@link destinations}
|
|
218
384
|
*/
|
|
219
|
-
public static set log(log: (level: LogLevel, formattedLog: string) => void) {
|
|
385
|
+
public static set log(log: (level: LogLevel, formattedLog: string, facility?: string) => void) {
|
|
220
386
|
Logger.setLogger("default", log);
|
|
221
387
|
}
|
|
222
388
|
|
|
223
389
|
/**
|
|
224
390
|
* Get the log function for the default logger.
|
|
391
|
+
*
|
|
392
|
+
* @deprecated use {@link destinations}
|
|
225
393
|
*/
|
|
226
394
|
public static get log() {
|
|
227
|
-
return Logger.
|
|
395
|
+
return Logger.getLoggerForIdentifier("default").log;
|
|
228
396
|
}
|
|
229
397
|
|
|
230
398
|
/**
|
|
231
399
|
* Set the log formatter for the default logger.
|
|
232
400
|
*
|
|
233
401
|
* @param logFormatter
|
|
402
|
+
*
|
|
403
|
+
* @deprecated use {@link destinations}
|
|
234
404
|
*/
|
|
235
405
|
public static set logFormatter(
|
|
236
406
|
logFormatter: (now: Date, level: LogLevel, facility: string, nestingPrefix: string, values: any[]) => string,
|
|
@@ -240,9 +410,11 @@ export class Logger {
|
|
|
240
410
|
|
|
241
411
|
/**
|
|
242
412
|
* Get the log formatter for the default logger.
|
|
413
|
+
*
|
|
414
|
+
* @deprecated use {@link destinations}
|
|
243
415
|
*/
|
|
244
416
|
public static get logFormatter() {
|
|
245
|
-
return Logger.
|
|
417
|
+
return Logger.getLoggerForIdentifier("default").logFormatter;
|
|
246
418
|
}
|
|
247
419
|
|
|
248
420
|
/**
|
|
@@ -250,14 +422,11 @@ export class Logger {
|
|
|
250
422
|
*
|
|
251
423
|
* @param identifier The identifier of the logger
|
|
252
424
|
* @param format the name of the formatter (see Format enum)
|
|
425
|
+
*
|
|
426
|
+
* @deprecated use {@link destinations}
|
|
253
427
|
*/
|
|
254
428
|
public static setFormatForLogger(identifier: string, format: string) {
|
|
255
|
-
|
|
256
|
-
if (logger) {
|
|
257
|
-
logger.logFormatter = logFormatterFor(format);
|
|
258
|
-
} else {
|
|
259
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
260
|
-
}
|
|
429
|
+
this.getLoggerForIdentifier(identifier).logFormatter = logFormatterFor(format);
|
|
261
430
|
}
|
|
262
431
|
|
|
263
432
|
/**
|
|
@@ -265,14 +434,11 @@ export class Logger {
|
|
|
265
434
|
*
|
|
266
435
|
* @param identifier The identifier of the logger
|
|
267
436
|
* @param level The level to set
|
|
437
|
+
*
|
|
438
|
+
* @deprecated use {@link destinations}
|
|
268
439
|
*/
|
|
269
440
|
public static setDefaultLoglevelForLogger(identifier: string, level: LogLevel) {
|
|
270
|
-
|
|
271
|
-
if (logger) {
|
|
272
|
-
logger.defaultLogLevel = level;
|
|
273
|
-
} else {
|
|
274
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
275
|
-
}
|
|
441
|
+
this.getLoggerForIdentifier(identifier).defaultLogLevel = level;
|
|
276
442
|
}
|
|
277
443
|
|
|
278
444
|
/**
|
|
@@ -280,14 +446,11 @@ export class Logger {
|
|
|
280
446
|
*
|
|
281
447
|
* @param identifier The identifier of the logger
|
|
282
448
|
* @param levels The levels to set
|
|
449
|
+
*
|
|
450
|
+
* @deprecated use {@link destinations}
|
|
283
451
|
*/
|
|
284
452
|
public static setLogLevelsForLogger(identifier: string, levels: { [facility: string]: LogLevel }) {
|
|
285
|
-
|
|
286
|
-
if (logger) {
|
|
287
|
-
logger.logLevels = levels;
|
|
288
|
-
} else {
|
|
289
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
290
|
-
}
|
|
453
|
+
this.getLoggerForIdentifier(identifier).logLevels = levels;
|
|
291
454
|
}
|
|
292
455
|
|
|
293
456
|
/**
|
|
@@ -295,14 +458,14 @@ export class Logger {
|
|
|
295
458
|
*
|
|
296
459
|
* @param identifier The identifier of the logger
|
|
297
460
|
* @param log The log function to set
|
|
461
|
+
*
|
|
462
|
+
* @deprecated use {@link destinations}
|
|
298
463
|
*/
|
|
299
|
-
public static setLogger(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
305
|
-
}
|
|
464
|
+
public static setLogger(
|
|
465
|
+
identifier: string,
|
|
466
|
+
log: (level: LogLevel, formattedLog: string, facility?: string) => void,
|
|
467
|
+
) {
|
|
468
|
+
this.getLoggerForIdentifier(identifier).log = log;
|
|
306
469
|
}
|
|
307
470
|
|
|
308
471
|
/**
|
|
@@ -310,164 +473,14 @@ export class Logger {
|
|
|
310
473
|
*
|
|
311
474
|
* @param identifier The identifier of the logger
|
|
312
475
|
* @param logFormatter The log formatter to set
|
|
476
|
+
*
|
|
477
|
+
* @deprecated use {@link destinations}
|
|
313
478
|
*/
|
|
314
479
|
static setLogFormatterForLogger(
|
|
315
480
|
identifier: string,
|
|
316
481
|
logFormatter: (now: Date, level: LogLevel, facility: string, nestingPrefix: string, values: any[]) => string,
|
|
317
482
|
) {
|
|
318
|
-
|
|
319
|
-
if (logger) {
|
|
320
|
-
logger.logFormatter = logFormatter;
|
|
321
|
-
} else {
|
|
322
|
-
throw new NotImplementedError(`Unknown logger "${identifier}"`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Create a new facility.
|
|
328
|
-
*
|
|
329
|
-
* @param name the name of the facility
|
|
330
|
-
* @returns a new facility
|
|
331
|
-
*/
|
|
332
|
-
static get(name: string) {
|
|
333
|
-
return new Logger(name);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Stringify a value (BigInt aware) as JSON.
|
|
338
|
-
*
|
|
339
|
-
* @param data the value to stringify
|
|
340
|
-
* @returns the stringified value
|
|
341
|
-
*/
|
|
342
|
-
static toJSON(data: any) {
|
|
343
|
-
return JSON.stringify(data, (_, value) => {
|
|
344
|
-
if (typeof value === "bigint") {
|
|
345
|
-
return value.toString();
|
|
346
|
-
}
|
|
347
|
-
if (value instanceof Uint8Array) {
|
|
348
|
-
return Bytes.toHex(value);
|
|
349
|
-
}
|
|
350
|
-
if (value === undefined) {
|
|
351
|
-
return "undefined";
|
|
352
|
-
}
|
|
353
|
-
return value;
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Mask a string with a given character. If unmaskedLength is provided then these number of characters will be
|
|
359
|
-
* shown unmasked.
|
|
360
|
-
*
|
|
361
|
-
* @param str String to mask
|
|
362
|
-
* @param maskChar character to mask with
|
|
363
|
-
* @param unmaskedLength number of characters to show unmasked in the beginning
|
|
364
|
-
*/
|
|
365
|
-
static maskString(str: string, maskChar = "*", unmaskedLength?: number) {
|
|
366
|
-
return str.substring(0, unmaskedLength ?? 0) + str.substring(unmaskedLength ?? 0).replace(/./g, maskChar);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Perform operations in a nested logging context. Messages will be
|
|
371
|
-
* indented while the context executes.
|
|
372
|
-
*/
|
|
373
|
-
static nest<T>(context: () => T): T {
|
|
374
|
-
this.nestingLevel++;
|
|
375
|
-
try {
|
|
376
|
-
return context();
|
|
377
|
-
} finally {
|
|
378
|
-
this.nestingLevel--;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Async version of nest().
|
|
384
|
-
*/
|
|
385
|
-
static async nestAsync(context: () => Promise<any>) {
|
|
386
|
-
this.nestingLevel++;
|
|
387
|
-
try {
|
|
388
|
-
return await context();
|
|
389
|
-
} finally {
|
|
390
|
-
this.nestingLevel--;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Unhandled error reporter.
|
|
396
|
-
*
|
|
397
|
-
* Some environments do not report full error details such as {@link Error#cause} and {@link AggregateError#errors}.
|
|
398
|
-
*
|
|
399
|
-
* To ensure these details are always recorded somewhere, unhandled errors may be reported here.
|
|
400
|
-
*
|
|
401
|
-
* To disable this behavior replace this function.
|
|
402
|
-
*/
|
|
403
|
-
static reportUnhandledError(error: Error) {
|
|
404
|
-
try {
|
|
405
|
-
Logger.get("Logger").fatal("Unhandled error detected:", error);
|
|
406
|
-
} catch (e) {
|
|
407
|
-
// We do not want to cause yet another error so if logging fails for any reason it goes unreported
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Invoke logic and return any log messages produced.
|
|
413
|
-
*/
|
|
414
|
-
static capture(fn: () => void, fromLogger = "default") {
|
|
415
|
-
if (!Logger) {
|
|
416
|
-
throw new Error("No logger loaded, cannot capture logs");
|
|
417
|
-
}
|
|
418
|
-
const logger = Logger.getLoggerforIdentifier(fromLogger);
|
|
419
|
-
const actualLogSettings = {
|
|
420
|
-
logFormatter: logger.logFormatter,
|
|
421
|
-
log: logger.log,
|
|
422
|
-
defaultLogLevel: logger.defaultLogLevel,
|
|
423
|
-
logLevels: { ...logger.logLevels },
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
Logger.setFormatForLogger(fromLogger, LogFormat.PLAIN);
|
|
428
|
-
const captured = new Array<{ level: LogLevel; message: string }>();
|
|
429
|
-
Logger.setLogger(fromLogger, (level, message) =>
|
|
430
|
-
captured.push({
|
|
431
|
-
level,
|
|
432
|
-
message: message.replace(/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/, "xxxx-xx-xx xx:xx:xx.xxx"),
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
fn();
|
|
436
|
-
return captured;
|
|
437
|
-
} finally {
|
|
438
|
-
Logger.setLogFormatterForLogger(fromLogger, actualLogSettings.logFormatter);
|
|
439
|
-
Logger.setDefaultLoglevelForLogger(fromLogger, actualLogSettings.defaultLogLevel);
|
|
440
|
-
Logger.setLogLevelsForLogger(fromLogger, actualLogSettings.logLevels);
|
|
441
|
-
Logger.setLogger(fromLogger, actualLogSettings.log);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
constructor(name: string) {
|
|
446
|
-
this.#name = name;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
debug = (...values: any[]) => this.#log(LogLevel.DEBUG, values);
|
|
450
|
-
info = (...values: any[]) => this.#log(LogLevel.INFO, values);
|
|
451
|
-
notice = (...values: any[]) => this.#log(LogLevel.NOTICE, values);
|
|
452
|
-
warn = (...values: any[]) => this.#log(LogLevel.WARN, values);
|
|
453
|
-
error = (...values: any[]) => this.#log(LogLevel.ERROR, values);
|
|
454
|
-
fatal = (...values: any[]) => this.#log(LogLevel.FATAL, values);
|
|
455
|
-
log = (level: LogLevel, ...values: any[]) => this.#log(level, values);
|
|
456
|
-
|
|
457
|
-
#log(level: LogLevel, values: any[]) {
|
|
458
|
-
for (const logger of Logger.logger) {
|
|
459
|
-
if (level < (logger.logLevels[this.#name] ?? logger.defaultLogLevel)) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (!logger.context) {
|
|
464
|
-
logger.context = Diagnostic.Context();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
logger.context.run(() =>
|
|
468
|
-
logger.log(level, logger.logFormatter(Time.now(), level, this.#name, nestingPrefix(), values)),
|
|
469
|
-
);
|
|
470
|
-
}
|
|
483
|
+
this.getLoggerForIdentifier(identifier).logFormatter = logFormatter;
|
|
471
484
|
}
|
|
472
485
|
}
|
|
473
486
|
|
|
@@ -479,13 +492,7 @@ function nestingPrefix() {
|
|
|
479
492
|
}
|
|
480
493
|
|
|
481
494
|
Boot.init(() => {
|
|
482
|
-
Logger.
|
|
483
|
-
logIdentifier: "default",
|
|
484
|
-
logFormatter: LogFormat.plain,
|
|
485
|
-
log: consoleLogger,
|
|
486
|
-
defaultLogLevel: LogLevel.DEBUG,
|
|
487
|
-
logLevels: {},
|
|
488
|
-
});
|
|
495
|
+
Logger.destinations = LogDestinations();
|
|
489
496
|
Logger.nestingLevel = 0;
|
|
490
497
|
|
|
491
498
|
// Hook for testing frameworks
|
|
@@ -495,3 +502,76 @@ Boot.init(() => {
|
|
|
495
502
|
});
|
|
496
503
|
|
|
497
504
|
CancelablePromise.logger = Logger.get("CancelablePromise");
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Create a log formatter for a given format.
|
|
508
|
+
*
|
|
509
|
+
* @deprecated
|
|
510
|
+
*/
|
|
511
|
+
function logFormatterFor(formatName: string): LoggerDefinition["logFormatter"] {
|
|
512
|
+
const format = LogFormat(formatName);
|
|
513
|
+
|
|
514
|
+
return (now, level, facility, prefix, ...values) =>
|
|
515
|
+
format(Diagnostic.message({ now, level, facility, prefix, values }));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Definition of one registered Logger.
|
|
520
|
+
*
|
|
521
|
+
* @deprecated
|
|
522
|
+
*/
|
|
523
|
+
type LoggerDefinition = {
|
|
524
|
+
logIdentifier: string;
|
|
525
|
+
logFormatter: (now: Date, level: LogLevel, facility: string, prefix: string, values: any[]) => string;
|
|
526
|
+
log: (level: LogLevel, formattedLog: string, facility?: string) => void;
|
|
527
|
+
defaultLogLevel: LogLevel;
|
|
528
|
+
logLevels: { [facility: string]: LogLevel };
|
|
529
|
+
context?: Diagnostic.Context;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @deprecated
|
|
534
|
+
*/
|
|
535
|
+
function adaptDestinationToLegacy(destination: LogDestination): LoggerDefinition {
|
|
536
|
+
return {
|
|
537
|
+
get logIdentifier() {
|
|
538
|
+
return destination.name;
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
get logFormatter() {
|
|
542
|
+
return (now: Date, level: LogLevel, facility: string, prefix: string, values: any[]) =>
|
|
543
|
+
destination.format(Diagnostic.message({ now, level, facility, prefix, values }));
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
set logFormatter(logFormatter: LoggerDefinition["logFormatter"]) {
|
|
547
|
+
destination.format = (message: Diagnostic.Message) =>
|
|
548
|
+
logFormatter(message.now, message.level, message.facility, message.prefix, message.values);
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
get log() {
|
|
552
|
+
return (level: LogLevel, formattedLog: string, facility?: string) =>
|
|
553
|
+
destination.write(formattedLog, Diagnostic.message({ level, facility }));
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
set log(log: LoggerDefinition["log"]) {
|
|
557
|
+
destination.write = (text: string, message: Diagnostic.Message) =>
|
|
558
|
+
log(message.level, text, message.facility);
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
get defaultLogLevel() {
|
|
562
|
+
return destination.level;
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
set defaultLogLevel(level: LogLevel) {
|
|
566
|
+
destination.level = level;
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
get logLevels() {
|
|
570
|
+
return destination.facilityLevels;
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
set logLevels(levels: Record<string, LogLevel>) {
|
|
574
|
+
destination.facilityLevels = levels;
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
}
|