@niceties/logger 1.1.12 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +173 -54
  2. package/appender-utils.js +30 -0
  3. package/console-appender.js +7 -0
  4. package/core.js +96 -0
  5. package/default-formatting.js +17 -0
  6. package/format-utils.js +21 -0
  7. package/global-appender.js +15 -0
  8. package/index.d.ts +362 -0
  9. package/index.d.ts.map +48 -0
  10. package/index.js +40 -0
  11. package/package.json +24 -37
  12. package/simple.js +42 -0
  13. package/types.js +17 -0
  14. package/appender-utils/package.json +0 -5
  15. package/console-appender/package.json +0 -5
  16. package/core/package.json +0 -5
  17. package/default-formatting/package.json +0 -5
  18. package/dist/appender-utils.cjs +0 -22
  19. package/dist/appender-utils.d.ts +0 -3
  20. package/dist/appender-utils.mjs +0 -19
  21. package/dist/appender-utils.umd.js +0 -2
  22. package/dist/appender-utils.umd.js.map +0 -1
  23. package/dist/console-appender.cjs +0 -9
  24. package/dist/console-appender.d.ts +0 -2
  25. package/dist/console-appender.mjs +0 -7
  26. package/dist/console-appender.umd.js +0 -2
  27. package/dist/console-appender.umd.js.map +0 -1
  28. package/dist/core.cjs +0 -72
  29. package/dist/core.d.ts +0 -7
  30. package/dist/core.mjs +0 -70
  31. package/dist/core.umd.js +0 -2
  32. package/dist/core.umd.js.map +0 -1
  33. package/dist/default-formatting.cjs +0 -17
  34. package/dist/default-formatting.d.ts +0 -5
  35. package/dist/default-formatting.mjs +0 -12
  36. package/dist/format-utils.cjs +0 -21
  37. package/dist/format-utils.d.ts +0 -3
  38. package/dist/format-utils.mjs +0 -18
  39. package/dist/global-appender.cjs +0 -12
  40. package/dist/global-appender.d.ts +0 -3
  41. package/dist/global-appender.mjs +0 -10
  42. package/dist/global-appender.umd.js +0 -2
  43. package/dist/global-appender.umd.js.map +0 -1
  44. package/dist/index.cjs +0 -19
  45. package/dist/index.d.ts +0 -4
  46. package/dist/index.mjs +0 -18
  47. package/dist/simple.cjs +0 -25
  48. package/dist/simple.d.ts +0 -4
  49. package/dist/simple.mjs +0 -23
  50. package/dist/simple.umd.js +0 -2
  51. package/dist/simple.umd.js.map +0 -1
  52. package/dist/types.d.ts +0 -51
  53. package/format-utils/package.json +0 -5
  54. package/global-appender/package.json +0 -5
  55. package/simple/package.json +0 -5
  56. package/types/package.json +0 -3
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Logger that can handle async tasks.
4
4
 
5
- - Provides normal logging API: log level, tag logger instance, custom log data
5
+ - Provides a normal logging API: log level, tagged logger instance, custom log data
6
6
 
7
- - Provides API for reporting async events that can be later handled by custom appender.
7
+ - Provides an API for reporting async events that can later be handled by a custom appender.
8
8
 
9
- - Provides a default appender that uses console for output.
9
+ - Provides a default appender that uses the console for output.
10
10
 
11
11
  - Modular and configurable
12
12
 
@@ -43,46 +43,66 @@ try {
43
43
  Logger factory:
44
44
 
45
45
  ```typescript
46
- function createLogger<ErrorContext = Error>(...args: [] | [string | Identity | undefined] | [string, Identity]): ((message: string, loglevel?: LogLevel, context?: ErrorContext | undefined) => void) & {
47
- start(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
48
- update(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
49
- finish(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
50
- appender(appender?: Appender<ErrorContext> | undefined): (message: LogMessage<ErrorContext>) => void;
46
+ function createLogger<ErrorContext = Error>(
47
+ ...args: [] | [string | Identity | undefined] | [string, Identity]
48
+ ): ((
49
+ message: string,
50
+ loglevel?: LogLevel,
51
+ context?: ErrorContext | undefined,
52
+ ) => void) & {
53
+ start(
54
+ message: string,
55
+ loglevel?: LogLevel | undefined,
56
+ context?: ErrorContext | undefined,
57
+ ): void;
58
+ update(
59
+ message: string,
60
+ loglevel?: LogLevel | undefined,
61
+ context?: ErrorContext | undefined,
62
+ ): void;
63
+ finish(
64
+ message: string,
65
+ loglevel?: LogLevel | undefined,
66
+ context?: ErrorContext | undefined,
67
+ ): void;
68
+ appender(
69
+ appender?: Appender<ErrorContext> | undefined,
70
+ ): (message: LogMessage<ErrorContext>) => void;
51
71
  };
52
72
  ```
53
73
 
54
- Will return a logger instance that can be viewed as an entry for a single async task.
74
+ Returns a logger instance that can be viewed as an entry for a single async task.
55
75
 
56
76
  ```typescript
57
- const logger = createLogger('tag');
77
+ const logger = createLogger("tag");
58
78
  const logger2 = createLogger(logger);
59
- const logger3 = createLogger('tag2', logger);
79
+ const logger3 = createLogger("tag2", logger);
60
80
  ```
61
81
 
62
82
  `tag` can be used to distinguish between async tasks (will be provided to appender).
63
- logger can be used as parent of another logger (will be provided as parentId to appender).
83
+ Logger can be used as a parent of another logger (will be provided as parentId to appender).
64
84
 
65
85
  ```typescript
66
86
  const log = createLogger();
67
87
 
68
88
  try {
69
89
  // some code
70
- log('some message');
90
+ log("some message");
71
91
  } catch (e) {
72
- log('some message', 1 /* LogLevel.info */, e);
92
+ log("some message", 1 /* LogLevel.info */, e);
73
93
  }
74
94
  ```
75
95
 
76
- Logger can be used as a function that logs message or error with context. Context type can be defined during creation of the logger (only in typescript).
96
+ Logger can be used as a function that logs a message or error with context. Context type can be defined during creation of the logger (only in TypeScript).
77
97
 
78
98
  ```typescript
79
99
  const log = createLogger<Context>();
80
100
 
81
101
  try {
82
102
  // some code
83
- log('some message');
103
+ log("some message");
84
104
  } catch (e: Context) {
85
- log('some message', LogLevel.info, e);
105
+ log("some message", LogLevel.info, e);
86
106
  }
87
107
  ```
88
108
 
@@ -90,19 +110,19 @@ try {
90
110
  start(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
91
111
  ```
92
112
 
93
- Emits a start event inside a logger. If loglevel provided it will be remembered and used as default loglevel in subsequent events in the same logger instance. Default loglevel (if argument is not provided) is `info`.
113
+ Emits a start event inside a logger. If a loglevel is provided, it will be remembered and used as the default loglevel in subsequent events in the same logger instance. Default loglevel (if the argument is not provided) is `info`.
94
114
 
95
115
  ```typescript
96
116
  update(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
97
117
  ```
98
118
 
99
- Emits update event. Can be used to inform an user that we are doing something else in the same async task. loglevel used to redefine default loglevel.
119
+ Emits an update event. Can be used to inform a user that we are doing something else in the same async task. The loglevel parameter is used to redefine the default loglevel.
100
120
 
101
121
  ```typescript
102
122
  finish(message: string, loglevel?: LogLevel | undefined, context?: ErrorContext | undefined): void;
103
123
  ```
104
124
 
105
- Emits finish event. Can be used to inform an user that the task finished. loglevel is optional and equals initial loglevel if omitted.
125
+ Emits a finish event. Can be used to inform a user that the task finished. loglevel is optional and equals the initial loglevel if omitted.
106
126
 
107
127
  ```typescript
108
128
  const logger = createLogger();
@@ -111,6 +131,34 @@ logger.appender(someFancyAppender);
111
131
 
112
132
  Sets a different appender for the specific instance of the logger.
113
133
 
134
+ ## Appender API
135
+
136
+ Appenders can expose additional methods to the logger instance via the `api` property. When an appender with an `api` property is set on a logger, the `api` object is installed into the logger's prototype chain. This means the methods are accessible directly on the logger instance:
137
+
138
+ ```javascript
139
+ import { createLogger } from "@niceties/logger";
140
+ import { filterMessages } from "@niceties/logger/appender-utils";
141
+
142
+ let minLevel = 1;
143
+ const filtered = filterMessages(
144
+ (msg) => msg.loglevel >= minLevel,
145
+ someAppender,
146
+ );
147
+ filtered.api = {
148
+ setMinLevel(level) {
149
+ minLevel = level;
150
+ },
151
+ };
152
+
153
+ const logger = createLogger();
154
+ logger.appender(filtered);
155
+
156
+ // Method is available directly on the logger:
157
+ logger.setMinLevel(0);
158
+ ```
159
+
160
+ When the appender is swapped, old `api` methods are automatically cleaned up - they are removed from the logger and replaced with the new appender's `api` (if any).
161
+
114
162
  ```typescript
115
163
  const logger = createLogger();
116
164
  const appender = logger.appender();
@@ -125,7 +173,7 @@ const enum LogLevel {
125
173
  verbose, // for debugging logs, not for displaying on screen in normal cases
126
174
  info, // should be printed to user but not an error
127
175
  warn, // something is probably wrong, but we can continue
128
- error // operation completely failed
176
+ error, // operation completely failed
129
177
  }
130
178
  ```
131
179
 
@@ -134,39 +182,71 @@ const enum LogLevel {
134
182
  User or another library can set another appender by calling:
135
183
 
136
184
  ```typescript
137
- function appender<ErrorContext = Error>(appender?: Appender<ErrorContext>): Appender<any>;
185
+ function appender<ErrorContext = Error>(
186
+ appender?: Appender<ErrorContext>,
187
+ ): Appender<any>;
188
+ ```
189
+
190
+ If the appender has an `api` property, its methods are installed into the `appender` function's prototype chain, making them directly accessible:
191
+
192
+ ```javascript
193
+ import { appender } from "@niceties/logger";
194
+ import { filterMessages } from "@niceties/logger/appender-utils";
195
+
196
+ let minLevel = 1;
197
+ const filtered = filterMessages(
198
+ (msg) => msg.loglevel >= minLevel,
199
+ someAppender,
200
+ );
201
+ filtered.api = {
202
+ setMinLevel(level) {
203
+ minLevel = level;
204
+ },
205
+ };
206
+ appender(filtered);
207
+
208
+ // Method is available directly on the appender function:
209
+ appender.setMinLevel(0);
138
210
  ```
139
211
 
140
- where appender is a function with following type
212
+ When a new global appender is set, old `api` methods are automatically cleaned up and replaced.
213
+
214
+ The appender is a function with the following type:
141
215
 
142
216
  ```typescript
143
- (message: LogMessage<ErrorContext>) => void;
217
+ type Appender<ErrorContext = Error> = ((
218
+ message: LogMessage<ErrorContext>,
219
+ ) => void) & {
220
+ api?: object;
221
+ };
144
222
 
145
223
  const enum Action {
146
224
  start,
147
225
  update,
148
- success,
149
- fail
226
+ finish,
227
+ log,
150
228
  }
151
229
 
152
- type LogMessage<ErrorContext = Error> = {
153
- inputId: number;
154
- loglevel: LogLevel;
155
- message: string;
156
- action: Action.start | Action.update | Action.finish;
157
- tag?: string;
158
- parentId?: string;
159
- ref: WeakRef<never>;
160
- } | {
161
- inputId?: number;
162
- loglevel: LogLevel;
163
- message: string;
164
- action: Action.log;
165
- tag?: string;
166
- parentId?: string;
167
- ref?: WeakRef<never>;
168
- context?: ErrorContext;
169
- };
230
+ type LogMessage<ErrorContext = Error> =
231
+ | {
232
+ inputId: number;
233
+ loglevel: LogLevel;
234
+ message: string;
235
+ action: Action.start | Action.update | Action.finish;
236
+ tag?: string;
237
+ parentId?: string;
238
+ ref: WeakRef<never>;
239
+ }
240
+ | {
241
+ inputId?: number;
242
+ loglevel: LogLevel;
243
+ message: string;
244
+ action: Action.log;
245
+ tag?: string;
246
+ parentId?: string;
247
+ ref?: WeakRef<never>;
248
+ context?: ErrorContext;
249
+ };
170
250
  ```
171
251
 
172
252
  Same appender function without arguments can be used to get the current appender.
@@ -175,7 +255,7 @@ Same appender function without arguments can be used to get the current appender
175
255
 
176
256
  ## Can I use more than 4 log levels
177
257
 
178
- Despite the fact loglevel defined as an enum it is just a number. Logger does not make assumptions about loglevels besides defining default loglevel as 1 (LogLevel.info).
258
+ Despite the fact that loglevel is defined as an enum, it is just a number. Logger does not make assumptions about loglevels besides defining the default loglevel as 1 (LogLevel.info).
179
259
 
180
260
  It is generally safe to expand loglevels into both positive and negative range (finer debug messages) as far as appender takes them into account.
181
261
 
@@ -184,10 +264,10 @@ As an example:
184
264
  ```typescript
185
265
  const log = createLogger();
186
266
 
187
- log('some message', -1);
267
+ log("some message", -1);
188
268
  ```
189
269
 
190
- will send a log message with finer loglevel than verbose through appender but default appender will ignore it.
270
+ will send a log message with a finer loglevel than verbose through the appender, but the default appender will ignore it.
191
271
 
192
272
  ## Can I use multiple appenders?
193
273
 
@@ -200,22 +280,63 @@ import { combineAppenders } from "@niceties/logger/appender-utils";
200
280
  appender(combineAppenders(appender(), appender2));
201
281
  ```
202
282
 
203
- ## Can I set filter for certain loglevel
283
+ If any of the combined appenders have an `api` property, their methods are merged into the combined appender's `api`.
284
+
285
+ ## Can I set a filter for a certain loglevel?
204
286
 
205
287
  It is possible using filterMessages and appender functions:
206
288
 
207
289
  ```javascript
290
+ import { appender } from "@niceties/logger";
208
291
  import { filterMessages } from "@niceties/logger/appender-utils";
209
292
 
210
293
  let desiredLoglevel = 0;
211
294
 
212
- appender(filterMessages((msg) => msg.loglevel >= desiredLoglevel, appender()));
295
+ const filtered = filterMessages(
296
+ (msg) => msg.loglevel >= desiredLoglevel,
297
+ appender(),
298
+ );
299
+ filtered.api = {
300
+ setLoglevel(loglevel) {
301
+ desiredLoglevel = loglevel;
302
+ },
303
+ };
304
+ appender(filtered);
213
305
 
214
- function setLoglevel(loglevel) {
215
- desiredLoglevel = loglevel;
216
- }
306
+ // Available directly on the appender function:
307
+ appender.setLoglevel(0);
217
308
  ```
218
309
 
310
+ ## How does `filterMessages` handle the `api` property?
311
+
312
+ `filterMessages` automatically forwards the `api` property from the inner (wrapped) appender. If the inner appender has an `api`, the filtered appender will inherit it. You can extend it further by setting your own `api` on the result:
313
+
314
+ ```javascript
315
+ const inner = createConsoleAppender(formatter);
316
+ inner.api = {
317
+ someMethod() {
318
+ /* ... */
319
+ },
320
+ };
321
+
322
+ const filtered = filterMessages((msg) => msg.loglevel >= 1, inner);
323
+ // filtered.api === inner.api (forwarded automatically)
324
+
325
+ // To add more methods while preserving the inner api:
326
+ filtered.api = {
327
+ ...filtered.api,
328
+ anotherMethod() {
329
+ /* ... */
330
+ },
331
+ };
332
+ ```
333
+
334
+ ## Appender API: authoring guidelines
335
+
336
+ When an appender exposes an `api` object, the `api` is installed into the prototype chain of the logger instance or the global `appender` function. Because built-in logger methods (`start`, `update`, `finish`, `appender`) and properties (`id`) are own properties of the logger, they naturally take precedence over prototype properties. This means that even if an `api` object accidentally contains a property with the same name as a built-in, the built-in will not be overwritten.
337
+
338
+ However, appender authors should still avoid using built-in names in the `api` object, as the `api` methods would be silently shadowed and inaccessible.
339
+
219
340
  # Sub-packages
220
341
 
221
342
  Default sub-package `'@niceties/logger'` exports types, `createLogger()` factory and `appender()` function.
@@ -234,8 +355,6 @@ Sub-package `'@niceties/logger/global-appender'` exports `appender()` and `globa
234
355
 
235
356
  Sub-package `'@niceties/logger/appender-utils'` exports `combineAppenders()` and `filterMessages()`.
236
357
 
237
- `simple` (default), `core` and `console-appender` exist as umd packages as well but probably require some effort to consume them.
238
-
239
358
  # Prior art
240
359
 
241
360
  - [loglevel](https://github.com/pimterry/loglevel)
@@ -0,0 +1,30 @@
1
+ export const filterMessages = (predicate, appender) => {
2
+
3
+ const result = logMessage => {
4
+ if (predicate(logMessage)) {
5
+ appender(logMessage);
6
+ }
7
+ };
8
+ if (appender.api) {
9
+ result.api = appender.api;
10
+ }
11
+ return result;
12
+ };
13
+
14
+ export const combineAppenders = (...appenders) => {
15
+
16
+ const result = ( message) => {
17
+ for (const appender of appenders) {
18
+ try {
19
+ appender(message);
20
+ } catch {
21
+
22
+ }
23
+ }
24
+ };
25
+ const apis = appenders.map(a => a.api).filter(Boolean);
26
+ if (apis.length > 0) {
27
+ result.api = (Object.assign({}, ...apis));
28
+ }
29
+ return result;
30
+ };
@@ -0,0 +1,7 @@
1
+ import { Action } from './types.js';
2
+
3
+ export const createConsoleAppender = formatter => {
4
+ return ( message) => {
5
+ console.log(formatter(message, message.action === Action.finish));
6
+ };
7
+ };
package/core.js ADDED
@@ -0,0 +1,96 @@
1
+ import { globalAppender } from './global-appender.js';
2
+ import { Action, LogLevel } from './types.js';
3
+
4
+ let globalInputId = 0;
5
+
6
+ const getOptions = options => {
7
+
8
+ let parentId;
9
+
10
+ let tag;
11
+ if (options.length === 1) {
12
+ if (typeof options[0] === 'string') {
13
+ tag = options[0];
14
+ } else {
15
+ parentId = options[0]?.id;
16
+ }
17
+ } else if (options.length === 2) {
18
+ tag = options[0];
19
+ parentId = options[1]?.id;
20
+ }
21
+ return { parentId, tag };
22
+ };
23
+
24
+ export const createLogger = (...args) => {
25
+ let initialLogLevel = LogLevel.info;
26
+
27
+ let myAppender = message => {
28
+ globalAppender?.(message);
29
+ };
30
+
31
+ const inputId = globalInputId++;
32
+
33
+ const { tag, parentId } = getOptions( (args));
34
+
35
+ const append = (message, action, loglevel, context) => {
36
+ myAppender?.(
37
+ ({
38
+ action,
39
+ inputId,
40
+ message,
41
+ loglevel,
42
+ tag,
43
+ parentId,
44
+ ref,
45
+ context,
46
+ })
47
+ );
48
+ };
49
+
50
+ const loggerInstance = Object.assign(
51
+
52
+ (message, loglevel = LogLevel.info, context) => {
53
+ append(message, Action.log, loglevel, context);
54
+ },
55
+ {
56
+
57
+ start(message, loglevel, context) {
58
+ if (loglevel !== undefined) {
59
+ initialLogLevel = loglevel;
60
+ }
61
+ append(message, Action.start, initialLogLevel, context);
62
+ },
63
+
64
+ update(message, loglevel, context) {
65
+ append(message, Action.update, loglevel ?? initialLogLevel, context);
66
+ },
67
+
68
+ finish(message, loglevel, context) {
69
+ append(message, Action.finish, loglevel ?? initialLogLevel, context);
70
+ },
71
+
72
+ appender(appender) {
73
+ if (appender !== undefined) {
74
+ myAppender = appender;
75
+ const api = appender.api;
76
+ if (api != null) {
77
+ Object.setPrototypeOf(api, Function.prototype);
78
+ Object.setPrototypeOf(loggerInstance, api);
79
+ } else {
80
+ Object.setPrototypeOf(loggerInstance, Function.prototype);
81
+ }
82
+ }
83
+ return myAppender;
84
+ },
85
+ }
86
+ );
87
+
88
+ Object.defineProperty(loggerInstance, 'id', {
89
+ value: inputId,
90
+ writable: false,
91
+ });
92
+
93
+ const ref = new WeakRef(loggerInstance);
94
+
95
+ return (loggerInstance);
96
+ };
@@ -0,0 +1,17 @@
1
+ import kleur from 'kleur';
2
+
3
+ const { green, red, yellow, blue, cyan, grey } = kleur;
4
+
5
+ export const unicodePrefixes = [`${green('✓')}`, `${green('✓')}`, '⚠', '✕'];
6
+
7
+ export const asciiPrefixes = [`${green('+')}`, `${green('+')}`, '!', 'x'];
8
+
9
+ export const unicodeLogPrefixes = [grey('ℹ'), `${cyan('ℹ')}`, 'ℹ', 'ℹ'];
10
+
11
+ export const asciiLogPrefixes = [grey('i'), `${cyan('i')}`, 'i', 'i'];
12
+
13
+ export const colors = [, , yellow, red];
14
+
15
+ export const tagFactory = tag => {
16
+ return `[${blue(tag)}]`;
17
+ };
@@ -0,0 +1,21 @@
1
+ import { Action, LogLevel } from './types.js';
2
+
3
+ export const createFormatter = (colors, prefixes, logPrefixes, tagFactory) => {
4
+ return ({ loglevel, message, context, action, tag }, usePrefix, indentation = 0) => {
5
+ const prefix =
6
+ usePrefix === true
7
+ ? `${prefixes[loglevel]} `
8
+ : typeof usePrefix === 'string'
9
+ ? `${usePrefix} `
10
+ : action === Action.log && logPrefixes[loglevel]
11
+ ? `${logPrefixes[loglevel]} `
12
+ : '';
13
+ const color = colors[loglevel];
14
+ const text = `${prefix}${loglevel === LogLevel.verbose && action === Action.log && tag !== undefined ? `${tagFactory(tag)} ` : ''}${message}${context != null ? ` ${context}` : ''}`;
15
+ return `${' '.repeat(indentation)}${color ? color(text) : text}`;
16
+ };
17
+ };
18
+
19
+ export const terminalSupportsUnicode = () => {
20
+ return process.platform !== 'win32' || process.env.TERM_PROGRAM === 'vscode' || !!process.env.WT_SESSION;
21
+ };
@@ -0,0 +1,15 @@
1
+ export let globalAppender;
2
+
3
+ export const appender = newAppender => {
4
+ if (newAppender !== undefined) {
5
+ globalAppender = (newAppender);
6
+ const api = newAppender != null ? newAppender.api : undefined;
7
+ if (api != null) {
8
+ Object.setPrototypeOf(api, Function.prototype);
9
+ Object.setPrototypeOf(appender, api);
10
+ } else {
11
+ Object.setPrototypeOf(appender, Function.prototype);
12
+ }
13
+ }
14
+ return globalAppender;
15
+ };