axe 9.0.0 → 10.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 (5) hide show
  1. package/README.md +723 -179
  2. package/dist/axe.js +1151 -6809
  3. package/dist/axe.min.js +1 -1
  4. package/lib/index.js +253 -115
  5. package/package.json +20 -15
package/README.md CHANGED
@@ -6,164 +6,488 @@
6
6
  [![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://github.com/lassjs/lass)
7
7
  [![license](https://img.shields.io/github/license/cabinjs/axe.svg)](LICENSE)
8
8
 
9
- > Logging add-on to send logs over HTTP to your server in Node and Browser environments. Works with any logger! Chop up your logs consistently! Made for [Cabin][].
9
+ > Axe is a logger-agnostic wrapper that normalizes logs regardless of argument style. Great for large development teams, old and new projects, and works with Pino, Bunyan, Winston, console, and more. It is lightweight, performant, highly-configurable, and automatically adds OS, CPU, and Git information to your logs. It supports hooks (useful for masking sensitive data) and dot-notation remapping, omitting, and picking of log metadata properties. Made for [Forward Email][forward-email], [Lad][], and [Cabin][].
10
10
 
11
11
 
12
12
  ## Table of Contents
13
13
 
14
+ * [Foreword](#foreword)
15
+ * [Application Metadata and Information](#application-metadata-and-information)
14
16
  * [Install](#install)
15
17
  * [Node](#node)
16
18
  * [Browser](#browser)
17
- * [Approach](#approach)
18
- * [Application Information](#application-information)
19
19
  * [Usage](#usage)
20
+ * [Options](#options)
21
+ * [Supported Platforms](#supported-platforms)
20
22
  * [Node](#node-1)
21
23
  * [Browser](#browser-1)
22
24
  * [Custom logger](#custom-logger)
23
- * [Custom endpoint](#custom-endpoint)
24
- * [Suppress logs](#suppress-logs)
25
+ * [Silent Logging](#silent-logging)
25
26
  * [Stack Traces and Error Handling](#stack-traces-and-error-handling)
26
- * [Options](#options)
27
- * [DEPRECATED](#deprecated)
28
- * [Aliases](#aliases)
29
- * [Methods](#methods)
30
- * [Send Logs To Slack](#send-logs-to-slack)
27
+ * [Hooks](#hooks)
28
+ * [Remapping](#remapping)
29
+ * [Omitting](#omitting)
30
+ * [Picking](#picking)
31
+ * [Aliases](#aliases)
32
+ * [Methods](#methods)
33
+ * [Examples](#examples)
34
+ * [Send Logs to HTTP Endpoint](#send-logs-to-http-endpoint)
35
+ * [Send Logs to Slack](#send-logs-to-slack)
36
+ * [Suppress Logger Data](#suppress-logger-data)
31
37
  * [Contributors](#contributors)
32
- * [Trademark Notice](#trademark-notice)
33
38
  * [License](#license)
34
39
 
35
40
 
36
- ## Install
41
+ ## Foreword
37
42
 
38
- ### Node
43
+ Axe was built to provide consistency among development teams when it comes to logging. You not only have to worry about your development team using the same approach to writing logs and debugging applications, but you also have to consider that open-source maintainers implement logging differently in their packages.
39
44
 
40
- [npm][]:
45
+ There is no industry standard as to logging style, and developers mix and match arguments without consistency. For example, one developer may use the approach of `console.log('someVariable', someVariable)` and another developer will simply write `console.log(someVariable)`. Even if both developers wrote in the style of `console.log('someVariable', someVariable)`, there still could be an underlying third-party package that logs differently, or uses an entirely different approach. Furthermore, by default there is no consistency of logs with stdout or using any third-party hosted logging dashboard solution. It will also be almost impossible to spot logging outliers as it would be too time intensive.
41
46
 
42
- ```sh
43
- npm install axe
44
- ```
47
+ No matter how your team or underlying packages style arguments when invoked with logger methods, Axe will clean it up and normalize it for you. This is especially helpful as you can see outliers much more easily in your logging dashboards, and pinpoint where in your application you need to do a better job of logging at. Axe makes your logs consistent and organized.
45
48
 
46
- ### Browser
49
+ Axe is highly configurable and has built-in functionality to remap, omit, and pick metadata fields with dot-notation support. Instead of using [slow functions](https://medium.com/nerd-for-tech/replacing-lodash-omit-using-object-restructuring-and-the-spread-syntax-d7af1607a390) like `lodash`'s `omit`, we use a more performant approach.
47
50
 
48
- See [Browser](#browser-1) usage below for more information.
49
-
50
-
51
- ## Approach
52
-
53
- We adhere to the [Log4j][log4j] standard. This means that you can use any [custom logger](#custom-logger) (or the default `console`), but we strictly support the following log levels:
51
+ Axe adheres to the [Log4j][log4j] log levels, which have been established for 21+ years (since 2001). This means that you can use any [custom logger](#custom-logger) (or the default `console`), but we strictly support the following log levels:
54
52
 
55
53
  * `trace`
56
54
  * `debug`
57
55
  * `info`
58
56
  * `warn`
59
57
  * `error`
60
- * `fatal` (uses `error`)
58
+ * `fatal`
59
+
60
+ Axe normalizes invocation of logger methods to be called with *only* two arguments: a String or Error as the first argument and an Object as the second argument. These two arguments are referred to as "message" and "meta" respectively. For example, if you're simply logging a message and some other information:
61
+
62
+ ```js
63
+ logger.info('Hello world', { beep: 'boop', foo: true });
64
+ // Hello world { beep: 'boop', foo: true }
65
+ ```
66
+
67
+ Or if you're logging a user, or a variable in general:
68
+
69
+ ```js
70
+ logger.info('user', { user: { id: '1' } });
71
+ // user { user: { id: '1' } }
72
+ ```
73
+
74
+ ```js
75
+ logger.info('someVariable', { someVariable: true });
76
+ // someVariable { someVariable: true }
77
+ ```
78
+
79
+ You might write logs with three arguments `(level, message, meta)` using the `log` method of Axe's returned `logger` instance:
80
+
81
+ ```js
82
+ logger.log('info', 'Hello world', { beep: 'boop', foo: true });
83
+ // Hello world { beep: 'boop', foo: true }
84
+ ```
61
85
 
62
- We highly recommend that you follow this approach when logging `(message, meta)`:
86
+ Logging errors is just the same as you might do now:
63
87
 
64
88
  ```js
65
- const message = 'Hello world';
66
- const meta = { beep: 'boop', foo: true };
67
- axe.info(message, meta);
89
+ logger.error(new Error('Oops!'));
90
+
91
+ // Error: Oops!
92
+ // at REPL3:1:14
93
+ // at Script.runInThisContext (node:vm:129:12)
94
+ // at REPLServer.defaultEval (node:repl:566:29)
95
+ // at bound (node:domain:421:15)
96
+ // at REPLServer.runBound [as eval] (node:domain:432:12)
97
+ // at REPLServer.onLine (node:repl:893:10)
98
+ // at REPLServer.emit (node:events:539:35)
99
+ // at REPLServer.emit (node:domain:475:12)
100
+ // at REPLServer.Interface._onLine (node:readline:487:10)
101
+ // at REPLServer.Interface._line (node:readline:864:8)
68
102
  ```
69
103
 
70
- You can also make logs with three arguments `(level, message, meta)`:
104
+ You might log errors like this:
71
105
 
72
106
  ```js
73
- const level = 'info';
74
- const message = 'Hello world';
75
- const meta = { beep: 'boop', foo: true };
76
- axe.log(level, message, meta);
107
+ logger.error(new Error('Oops!'), new Error('Another Error!'));
108
+
109
+ // Error: Oops!
110
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:5:14)
111
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
112
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
113
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
114
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
115
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
116
+ // at node:internal/main/run_main_module:17:47
117
+ //
118
+ // Error: Another Error!
119
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:5:34)
120
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
121
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
122
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
123
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
124
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
125
+ // at node:internal/main/run_main_module:17:47
77
126
  ```
78
127
 
79
- You should also log errors like this:
128
+ Or even multiple errors:
80
129
 
81
130
  ```js
82
- const err = new Error('Oops!');
83
- axe.error(err);
131
+ logger.error(new Error('Oops!'), new Error('Another Error!'), new Error('Woah!'));
132
+
133
+ // Error: Oops!
134
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:6:3)
135
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
136
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
137
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
138
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
139
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
140
+ // at node:internal/main/run_main_module:17:47
141
+ //
142
+ // Error: Another Error!
143
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:7:3)
144
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
145
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
146
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
147
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
148
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
149
+ // at node:internal/main/run_main_module:17:47
150
+ //
151
+ // Error: Woah!
152
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:8:3)
153
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
154
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
155
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
156
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
157
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
158
+ // at node:internal/main/run_main_module:17:47
84
159
  ```
85
160
 
86
- **To recap:** The first argument `message` should be a String, and the second `meta` should be an optional Object.
161
+ As you can see, Axe combines multiple errors into one for an easy to read stack trace.
87
162
 
88
- If you simply use `axe.log`, then the log level used will be `info`, but it will still use the logger's native `log` method (as opposed to using `info`).
163
+ If you simply use `logger.log`, then the log level used will be `info`, but it will still use the logger's native `log` method (as opposed to using `info`). If you invoke `logger.log` (or any other logging method, e.g. `logger.info`, `logger.warn`, or `logger.error`), then it will consistently invoke the internal logger with these two arguments.
89
164
 
90
- If you invoke `axe.log` (or any other logging method, e.g. `info`), then it will return a consistent value no matter the edge case.
165
+ ```js
166
+ logger.log('hello world');
167
+ // hello world
168
+ ```
91
169
 
92
- For example, if you log `axe.log('hello world')`, it will output with `console.log` (or your custom logger's `log` method) and `return` the Object:
170
+ ```js
171
+ logger.info('hello world');
172
+ // hello world
173
+ ```
93
174
 
94
175
  ```js
95
- { message: 'hello world', meta: { level: 'info' } }
176
+ logger.warn('uh oh!', { amount_spent: 50 });
177
+ // uh oh! { amount_spent: 50 }
96
178
  ```
97
179
 
98
- And if you were to log `axe.info('hello world')`, it will output with `console.info` (or your custom logger's `info` method) and `return` the Object:
180
+ As you can see - this is exactly what you'd want your logger output to look like. Axe doesn't change anything out of the ordinary. Now here is where Axe is handy - **it will automatically normalize argument style for you:**
181
+
182
+ ```js
183
+ logger.warn({ hello: 'world' }, 'uh oh');
184
+ // uh oh { hello: 'world' }
185
+ ```
186
+
187
+ ```js
188
+ logger.warn('uh oh', 'foo bar', 'beep boop');
189
+ // uh oh foo bar beep boop
190
+ ```
191
+
192
+ ```js
193
+ logger.warn('hello', new Error('uh oh!'));
194
+
195
+ // Error: uh oh!
196
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:5:22)
197
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
198
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
199
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
200
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
201
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
202
+ // at node:internal/main/run_main_module:17:47
203
+ ```
99
204
 
100
205
  ```js
101
- { message: 'hello world', meta: { level: 'info' } }
206
+ logger.warn(new Error('uh oh!'), 'hello');
207
+
208
+ // Error: uh oh!
209
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:9:13)
210
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
211
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
212
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
213
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
214
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
215
+ // at node:internal/main/run_main_module:17:47
102
216
  ```
103
217
 
104
- Lastly if you were to log `axe.warn('uh oh!', { amount_spent: 50 })`, it will output with `console.warn` (or your custom logger's `warn` method) and `return` the Object:
218
+ Axe has support for format specifiers, and you can even use format specifiers in the browser (uses [format-util][] has limited number of format specifiers) and Node (uses the built-in [util.format][] method supports all format specifiers). This feature is built-in thanks to smart detection using [format-specifiers][].
105
219
 
106
220
  ```js
107
- { message: 'uh oh!', meta: { amount_spent: 50, level: 'warn' } }
221
+ logger.info('favorite color is %s', 'blue');
222
+ // favorite color is blue
108
223
  ```
109
224
 
110
- These returned values will be automatically sent to the endpoint (by default to your [Cabin][] account associated with your API key).
225
+ As you can see, Axe makes your logs consistent in both Node and browser environments.
226
+
227
+ Axe's goal is to allow you to log in any style, but make your log output more readable, organized, and clean.
228
+
229
+ The **most impactful feature of Axe** is that it **makes logger output human-friendly and readable** when there are multiple errors.
230
+
231
+ Normally `console` output (and most other loggers) by default will output the following unreadable stack trace:
232
+
233
+ ```sh
234
+ > console.log(new Error('hello'), new Error('world'));
235
+ Error: hello
236
+ at REPL6:1:13
237
+ at Script.runInThisContext (node:vm:129:12)
238
+ at REPLServer.defaultEval (node:repl:566:29)
239
+ at bound (node:domain:421:15)
240
+ at REPLServer.runBound [as eval] (node:domain:432:12)
241
+ at REPLServer.onLine (node:repl:893:10)
242
+ at REPLServer.emit (node:events:539:35)
243
+ at REPLServer.emit (node:domain:475:12)
244
+ at REPLServer.Interface._onLine (node:readline:487:10)
245
+ at REPLServer.Interface._line (node:readline:864:8) Error: world
246
+ at REPL6:1:33
247
+ at Script.runInThisContext (node:vm:129:12)
248
+ at REPLServer.defaultEval (node:repl:566:29)
249
+ at bound (node:domain:421:15)
250
+ at REPLServer.runBound [as eval] (node:domain:432:12)
251
+ at REPLServer.onLine (node:repl:893:10)
252
+ at REPLServer.emit (node:events:539:35)
253
+ at REPLServer.emit (node:domain:475:12)
254
+ at REPLServer.Interface._onLine (node:readline:487:10)
255
+ at REPLServer.Interface._line (node:readline:864:8)
256
+ ```
257
+
258
+ However with Axe, errors and stack traces are much more readable (we use [maybe-combine-errors][] under the hood):
259
+
260
+ ```sh
261
+ > logger.log(new Error('hello'), new Error('world'));
262
+ Error: hello
263
+ at REPL7:1:12
264
+ at Script.runInThisContext (node:vm:129:12)
265
+ at REPLServer.defaultEval (node:repl:566:29)
266
+ at bound (node:domain:421:15)
267
+ at REPLServer.runBound [as eval] (node:domain:432:12)
268
+ at REPLServer.onLine (node:repl:893:10)
269
+ at REPLServer.emit (node:events:539:35)
270
+ at REPLServer.emit (node:domain:475:12)
271
+ at REPLServer.Interface._onLine (node:readline:487:10)
272
+ at REPLServer.Interface._line (node:readline:864:8)
273
+
274
+ Error: world
275
+ at REPL7:1:32
276
+ at Script.runInThisContext (node:vm:129:12)
277
+ at REPLServer.defaultEval (node:repl:566:29)
278
+ at bound (node:domain:421:15)
279
+ at REPLServer.runBound [as eval] (node:domain:432:12)
280
+ at REPLServer.onLine (node:repl:893:10)
281
+ at REPLServer.emit (node:events:539:35)
282
+ at REPLServer.emit (node:domain:475:12)
283
+ at REPLServer.Interface._onLine (node:readline:487:10)
284
+ at REPLServer.Interface._line (node:readline:864:8)
285
+ ```
286
+
287
+ Lastly, Axe works in both server-side and client-side environments (with Node and the browser).
288
+
111
289
 
112
- You can also use format specifiers in the browser (uses [format-util][] – has limited number of format specifiers) and Node (uses the built-in [util.format][] method – supports all format specifiers). This feature is built-in thanks to smart detection using [format-specifiers][].
290
+ ## Application Metadata and Information
113
291
 
114
- **This consistency among server and browser environments is the beauty of Axe – and when used in combination with [Cabin][], your logs will be beautiful with HTTP request information, user metadata, IP address, User-Agent, and more!**
292
+ If you've read the [Foreword](#foreword), you'll know that Axe invokes logger methods with two normalized arguments, `message` (String or Error) and `meta` (Object).
115
293
 
294
+ Axe will automatically add the following metadata and information to the `meta` Object argument passed to logger methods:
116
295
 
117
- ## Application Information
296
+ | Property | Type | Description |
297
+ | ------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
298
+ | `meta.args` | Array | The original arguments passed to the logger method when invoked. **Note that this is hidden by default via `meta.omittedFields` option**. |
299
+ | `meta.level` | String | The log level invoked (e.g. `"info"`). |
300
+ | `meta.err` | Object | Parsed error information using [parse-err][]. |
301
+ | `meta.original_err` | Object | If and only if `meta.err` already existed, this field is preserved as `meta.original_err` on the metadata object. |
302
+ | `meta.original_meta` | Object | If and only if `meta` already existed as an argument and was not an Object (e.g. an Array), this field is preserved as `meta.original_meta` on the metadata object. |
303
+ | `meta.app` | Object | Application information parsed using [parse-app-info][]. **This is not added in Browser environments.** See below nested properties. |
304
+ | `meta.app.name` | String | Name of the app from `package.json`. |
305
+ | `meta.app.version` | String | Version of the app `package.json`. |
306
+ | `meta.app.node` | String | Version if node.js running the app. |
307
+ | `meta.app.hash` | String | The latest Git commit hash; not available when not in a Git repository or if there is no Git commit hash. |
308
+ | `meta.app.tag` | String | The latest Git tag; not available when not in a Git repository or if there is no Git tag. |
309
+ | `meta.app.environment` | String | The value of `process.env.NODE_ENV`. |
310
+ | `meta.app.hostname` | String | Name of the computer. |
311
+ | `meta.app.pid` | Number | Process ID as in `process.pid`. |
312
+ | `meta.app.cluster` | Object | Node [cluster](https://nodejs.org/api/cluster.html) information. |
313
+ | `meta.app.os` | Object | Node [os](https://nodejs.org/api/os.html) information. |
314
+ | `meta.app.worker_threads` | Object | Node [worker_threads](https://nodejs.org/api/worker_threads.html) information. |
118
315
 
119
- By default a `meta.app` property is populated in all logs for you using [parse-app-info][].
316
+ :warning: **Note that by default,** **<u>Axe will not output this additional information for you</u>** (since we set the `meta.omittedFields` option to `[ 'level', 'err', 'app', 'args' ]` by default).
120
317
 
121
- At a glance, here are the properties that are automatically populated for you:
318
+ Axe will omit from metadata all properties via the default Array from `meta.omittedFields` option (see [Options](#options) below for more insight).
122
319
 
123
- | Property | Description |
124
- | ----------- | ----------------------------------- |
125
- | environment | The value of NODE_ENV |
126
- | hostname | Name of the computer |
127
- | name | Name of the app from `package.json` |
128
- | node | Version if node.js running the app |
129
- | pid | Process ID as in `process.pid` |
130
- | version | Version of the app `package.json` |
320
+ If the argument "meta" is an empty object, then it will not be passed as an argument to logger methods \*ndash; because you don't want to see an empty `{}` polluting your log metadata. Axe keeps your log output tidy.
131
321
 
132
- Additional properties when the app is in a git repository
322
+ If you set `meta.omittedFields` to an empty Array, or alternatively use the environment variable `AXE_OMIT_META_FIELDS=""`, then application information will be visible:
133
323
 
134
- | Property | Description |
135
- | -------- | ------------------------------------------------------------------ |
136
- | hash | git hash of latest commit if the app |
137
- | tag | the latest git tag. Property is not available when there is no tag |
324
+ ```js
325
+ const Axe = require('axe');
326
+
327
+ const logger = new Axe({
328
+ meta: {
329
+ omittedFields: []
330
+ // NOTE: the default is `[ 'level', 'err', 'app', 'args' ]`
331
+ }
332
+ });
333
+
334
+ // hello world {
335
+ // args: [ 'info', 'hello world' ],
336
+ // level: 'info',
337
+ // app: {
338
+ // name: 'axe',
339
+ // version: '10.0.0',
340
+ // node: 'v16.15.1',
341
+ // hash: '5ecd389b2523a8e810416f6c4e3ffa0ba6573dc2',
342
+ // tag: 'v10.0.0',
343
+ // environment: 'development',
344
+ // hostname: 'users-MacBook-Air.local',
345
+ // pid: 3477,
346
+ // cluster: { isMaster: true, isWorker: false, schedulingPolicy: 2 },
347
+ // os: {
348
+ // arch: 'arm64',
349
+ // cpus: [Array],
350
+ // endianness: 'LE',
351
+ // freemem: 271433728,
352
+ // priority: 0,
353
+ // homedir: '/Users/user',
354
+ // hostname: 'users-MacBook-Air.local',
355
+ // loadavg: [Array],
356
+ // network_interfaces: [Object],
357
+ // platform: 'darwin',
358
+ // release: '21.3.0',
359
+ // tmpdir: '/var/folders/rl/gz_3j8fx4s98k2kb0hknfygm0000gn/T',
360
+ // totalmem: 17179869184,
361
+ // type: 'Darwin',
362
+ // uptime: 708340,
363
+ // user: [Object],
364
+ // version: 'Darwin Kernel Version 21.3.0: Wed Dec 8 00:40:46 PST 2021; root:xnu-8019.80.11.111.1~1/RELEASE_ARM64_T8101'
365
+ // },
366
+ // worker_threads: {
367
+ // isMainThread: true,
368
+ // resourceLimits: {},
369
+ // threadId: 0,
370
+ // workerData: null
371
+ // }
372
+ // }
373
+ // }
374
+ ```
375
+
376
+ We recommend that you set `meta.omittedFields` to an empty Array in production environments for verbosity.
377
+
378
+ Note that you can also combine `meta.omittedFields` with `meta.pickedFields` and `meta.remappedFields` (in case you want to output specific properties from `meta.app` and exclude others – see [Options](#options) for more insight).
379
+
380
+
381
+ ## Install
382
+
383
+ ### Node
384
+
385
+ [npm][]:
386
+
387
+ ```sh
388
+ npm install axe
389
+ ```
390
+
391
+ ### Browser
392
+
393
+ See [Browser](#browser-1) usage below for more information.
138
394
 
139
395
 
140
396
  ## Usage
141
397
 
142
- We highly recommend to simply use [Cabin][] as this package is built-in!
398
+ ### Options
399
+
400
+ | Property | Type | Default Value | Description | |
401
+ | ----------------------- | ----------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - |
402
+ | `showStack` | Boolean | `true` | Attempts to parse a boolean value from `process.env.AXE_SHOW_STACK`). **If this value is `true`, then if `message` is an instance of an Error, it will be invoked as the first argument to logger methods. If this is `false`, then only the `err.message` will be invoked as the first argument to logger methods.** Basically if `true` it will call `logger.method(err)` and if `false` it will call `logger.method(err.message)`. If you pass `err` as the first argument to a logger method, then it will show the stack trace via `err.stack` typically. | |
403
+ | `meta` | Object | See below | Stores all meta config information (see the following nested properties below). | |
404
+ | `meta.show` | Boolean | `true` | Attempts to parse a boolean value from `process.env.AXE_SHOW_META` – meaning you can pass a flag `SHOW_META=true node app.js` when needed for debugging), whether or not to output metadata to logger methods. If set to `false`, then fields will not be omitted nor picked; the entire meta object will be hidden from logger output. | |
405
+ | `meta.remappedFields` | Object | `{}` | Attempts to parse an Object mapping from `process.env.AXE_REMAPPED_META_FIELDS` (`,` and `:` delimited, e.g. `REMAPPED_META_FIELDS=foo:bar,beep.boop:beepBoop` to remap `meta.foo` to `meta.bar` and `meta.beep.boop` to `meta.beepBoop`). Note that this will clean up empty objects by default unless you set the option `meta.cleanupRemapping` to `false`). Supports dot-notation. | |
406
+ | `meta.omittedFields` | Array | `['level','err','app', 'args']` | Attempts to parse an array value from `process.env.AXE_OMIT_META_FIELDS` (`,` delimited) - meaning you can pass a flag `OMIT_META_FIELDS=user,id node app.js`), determining which fields to omit in the metadata passed to logger methods. Supports dot-notation. | |
407
+ | `meta.pickedFields` | Array | `[]` | Attempts to parse an array value from `process.env.AXE_PICK_META_FIELDS` (`,` delimited) - meaning you can pass a flag, e.g. `PICK_META_FIELDS=request.headers,response.headers node app.js` which would pick from `meta.request` and `meta.response` *only* `meta.request.headers` and `meta.response.headers`), **This takes precedence after fields are omitted, which means this acts as a whitelist.** Supports dot-notation. | |
408
+ | `meta.cleanupRemapping` | Boolean | `true` | Whether or not to cleanup empty objects after remapping operations are completed) | |
409
+ | `silent` | Boolean | `false` | Whether or not to invoke logger methods. Pre and post hooks will still run even if this option is set to `false`. | |
410
+ | `logger` | Object | `console` | Defaults to `console` with [console-polyfill][] added automatically, though **you can bring your own logger**. See [custom logger](#custom-logger) – you can pass an instance of `pino`, `signale`, `winston`, `bunyan`, etc. | |
411
+ | `name` | String or Boolean | `false` | The default name for the logger (defaults to `false`, which does not set `logger.name`). If you wish to pass a name such as `os.hostname()`, then set `name: os.hostname()` – this is useful if you are using a logger like `pino` which prefixes log output with the name set here. | |
412
+ | `level` | String | `"info"` | The default level of logging to invoke `logger` methods for (defaults to `info`, which includes all logs including info and higher in severity (e.g. `info`, `warn`, `error`, `fatal`) | |
413
+ | `levels` | Array | `['info','warn','error','fatal']` | An Array of logging levels to support. You usually shouldn't change this unless you want to prevent logger methods from being invoked or prevent hooks from being run for a certain log level. If an invalid log level is attempted to be invoked, and if it is not in this Array, then no hooks and no logger methods will be invoked. | |
414
+ | `appInfo` | Boolean | `true` | Attempts to parse a boolean value from `process.env.AXE_APP_INFO`) - whether or not to parse application information (using [parse-app-info][]). | |
415
+
416
+ ### Supported Platforms
417
+
418
+ * Node: v14+
419
+ * Browsers (see [.browserslistrc](.browserslistrc)):
420
+
421
+ ```sh
422
+ npx browserslist
423
+ ```
424
+
425
+ ```sh
426
+ and_chr 102
427
+ and_ff 101
428
+ and_qq 10.4
429
+ and_uc 12.12
430
+ android 101
431
+ chrome 103
432
+ chrome 102
433
+ chrome 101
434
+ chrome 100
435
+ edge 103
436
+ edge 102
437
+ edge 101
438
+ firefox 101
439
+ firefox 100
440
+ firefox 91
441
+ ios_saf 15.5
442
+ ios_saf 15.4
443
+ ios_saf 15.2-15.3
444
+ ios_saf 15.0-15.1
445
+ ios_saf 14.5-14.8
446
+ ios_saf 14.0-14.4
447
+ ios_saf 12.2-12.5
448
+ kaios 2.5
449
+ op_mini all
450
+ op_mob 64
451
+ opera 86
452
+ opera 85
453
+ safari 15.5
454
+ safari 15.4
455
+ samsung 17.0
456
+ samsung 16.0
457
+ ```
143
458
 
144
459
  ### Node
145
460
 
146
461
  ```js
147
462
  const Axe = require('axe');
148
463
 
149
- const axe = new Axe({ key: 'YOUR-CABIN-API-KEY' });
464
+ const logger = new Axe();
150
465
 
151
- axe.info('hello world');
466
+ logger.info('hello world');
152
467
  ```
153
468
 
154
469
  ### Browser
155
470
 
471
+ This package requires Promise support, therefore you will need to polyfill if you are using an unsupported browser (namely Opera mini).
472
+
473
+ **We no longer support IE as of Axe v10.0.0+.**
474
+
156
475
  #### VanillaJS
157
476
 
158
- **The browser-ready bundle is only 36 KB (minified and gzipped)**.
477
+ **The browser-ready bundle is only 18 KB when minified and 6 KB when gzipped**.
159
478
 
160
479
  ```html
161
- <script src="https://polyfill.io/v3/polyfill.min.js?features=WeakRef,BigInt"></script>
480
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=Promise"></script>
162
481
  <script src="https://unpkg.com/axe"></script>
163
482
  <script type="text/javascript">
164
- (function() {
165
- var Axe = new Axe({ key: 'YOUR-CABIN-API-KEY' });
166
- axe.info('hello world');
483
+ (function () {
484
+ // make a new logger instance
485
+ const logger = new Axe();
486
+ logger.info('hello world');
487
+
488
+ // or you can override console everywhere
489
+ console = new Axe();
490
+ console.info('hello world');
167
491
  });
168
492
  </script>
169
493
  ```
@@ -173,11 +497,10 @@ axe.info('hello world');
173
497
  We recommend using <https://polyfill.io> (specifically with the bundle mentioned in [VanillaJS](#vanillajs) above):
174
498
 
175
499
  ```html
176
- <script src="https://polyfill.io/v3/polyfill.min.js?features=WeakRef,BigInt"></script>
500
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=Promise"></script>
177
501
  ```
178
502
 
179
- * WeakRef is not supported in Opera 85, iOS Safari 12.2-12.5
180
- * BigInt is not supported in iOS Safari 12.2-12.5
503
+ * Promise is not supported in op\_mini all
181
504
 
182
505
  #### Bundler
183
506
 
@@ -204,9 +527,9 @@ Loggers supported include, but are not limited to:
204
527
  const signale = require('signale');
205
528
  const Axe = require('axe');
206
529
 
207
- const axe = new Axe({ logger: signale, key: 'YOUR-CABIN-API-KEY' });
530
+ const logger = new Axe({ logger: signale });
208
531
 
209
- axe.info('hello world');
532
+ logger.info('hello world');
210
533
  ```
211
534
 
212
535
  In [Lad][], we have an approach similar to the following, where non-production environments use [consola][], and production environments use [pino][].
@@ -239,79 +562,260 @@ const logger = new Axe({
239
562
  logger.info('hello world');
240
563
  ```
241
564
 
242
- ### Custom endpoint
565
+ ### Silent Logging
566
+
567
+ Silent logging is useful when you need to disable logging in certain environments for privacy reasons or to simply clean up output on stdout.
568
+
569
+ For example when you're running tests you can set `logger.config.silent = true`.
570
+
571
+ ```js
572
+ const Axe = require('axe');
573
+
574
+ const logger = new Axe({ silent: true });
575
+
576
+ logger.info('hello world');
577
+ ```
578
+
579
+ ### Stack Traces and Error Handling
580
+
581
+ Please see Cabin's documentation for [stack traces and error handling](https://github.com/cabinjs/cabin#stack-traces-and-error-handling) for more information.
582
+
583
+ > If you're not using `cabin`, you can simply replace instances of the word `cabin` with `axe` in the documentation examples linked above.
584
+
585
+ ### Hooks
586
+
587
+ You can add synchronous "pre" hooks and/or asynchronous/synchronous "post" hooks with Axe. Both pre and post hooks accept four arguments (`level`, `err`, `message`, and `meta`). Pre hooks are required to be synchronous.
243
588
 
244
- By default we built-in support such that if you provide your [Cabin][] API key, then your logs will be uploaded automatically for you in both server and browser environments.
589
+ Both pre and post hooks execute serially and while pre hooks are blocking, post-hooks will run in the background after logger methods are invoked (you can have a post hook that's a Promise or async function).
245
590
 
246
- If you decide to [self-host your own Cabin API][cabin-api] (or roll your own logging service) then you can specify your own endpoint under `config.endpoint`.
591
+ Pre hooks require an Array to be returned of `[ err, message, meta ]`.
247
592
 
248
- See [Options](#options) below for more information.
593
+ Pre hooks allow you to manipulate the arguments `err`, `message`, and `meta` that are passed to the internal logger methods. This is useful for masking sensitive data or doing additional custom logic before writing logs.
249
594
 
250
- ### Suppress logs
595
+ Post hooks are useful if you want to send logging information to a third-party, store them into a database, or do any sort of custom processing.
251
596
 
252
- This is useful when you want need logging turned off in certain environments.
597
+ You should properly handle any errors in your pre hooks, otherwise they will be thrown and logger methods will not be invoked.
253
598
 
254
- For example when you're running tests you can set `axe.config.silent = true`.
599
+ We will catch errors for post hooks by default and log them as errors with your logger methods' `logger.error` method).
600
+
601
+ Hooks can be defined in the options passed to an instance of Axe, e.g. `new Axe({ hooks: { pre: [ fn ], post: [ fn ] } });` and/or with the method `logger.pre(level, fn)` or `logger.post(level, fn)`. Here are a few examples below:
255
602
 
256
603
  ```js
257
604
  const Axe = require('axe');
258
605
 
259
- const axe = new Axe({ silent: true, key: 'YOUR-CABIN-API-KEY' });
606
+ const logger = new Axe({
607
+ hooks: {
608
+ pre: [
609
+ function (level, err, message, meta) {
610
+ message = message.replace(/world/gi, 'planet earth');
611
+ return [err, message, meta];
612
+ }
613
+ ]
614
+ }
615
+ });
260
616
 
261
- axe.info('hello world');
617
+ logger.info('hello world');
618
+
619
+ // hello planet earth
262
620
  ```
263
621
 
264
- ### Stack Traces and Error Handling
622
+ ```js
623
+ const Axe = require('axe');
265
624
 
266
- Please see Cabin's documentation for [stack traces and error handling](https://github.com/cabinjs/cabin#stack-traces-and-error-handling) for more information.
625
+ const logger = new Axe();
267
626
 
268
- > If you're not using `cabin`, you can simply replace instances of the word `cabin` with `axe` in the documentation examples linked above.
627
+ logger.pre('error', (err, message, meta) => {
628
+ if (err instanceof Error) err.is_beep_boop = true;
629
+ return [err, message, meta];
630
+ });
631
+
632
+ logger.error(new Error('oops'));
633
+
634
+ // Error: oops
635
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:39:14)
636
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
637
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
638
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
639
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
640
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
641
+ // at node:internal/main/run_main_module:17:47 {
642
+ // is_beep_boop: true
643
+ // }
644
+ ```
645
+
646
+ For more examples of hooks, see our below sections on [Send Logs to HTTP Endpoint](#send-logs-to-http-endpoint), [Send Logs to Slack](#send-logs-to-slack)), and [Suppress Logger Data](#suppress-logger-data) below.
647
+
648
+ ### Remapping
649
+
650
+ If you would like to remap fields, such as `response.headers` to `responseHeaders`, then you can use environment variables or pass an object with configuration mapping.
651
+
652
+ ```js
653
+ const logger = new Axe({
654
+ meta: {
655
+ remappedFields: {
656
+ 'response.headers': 'responseHeaders'
657
+ }
658
+ }
659
+ });
660
+
661
+ logger.info('foo bar', {
662
+ response: {
663
+ headers: {
664
+ 'X-Hello-World': true
665
+ }
666
+ }
667
+ });
668
+
669
+ // foo bar { responseHeaders: { 'X-Hello-World': true } }
670
+ ```
671
+
672
+ ### Omitting
673
+
674
+ If you would like to omit fields, such as `response.headers` from a response, so you are only left with the status code:
269
675
 
676
+ ```js
677
+ const logger = new Axe({
678
+ meta: {
679
+ omittedFields: ['level', 'err', 'app', 'args', 'response.headers']
680
+ }
681
+ });
270
682
 
271
- ## Options
683
+ logger.info('foo bar', {
684
+ response: {
685
+ status: 200,
686
+ headers: {
687
+ 'X-Hello-World': true
688
+ }
689
+ }
690
+ });
272
691
 
273
- * `key` (String) - defaults to an empty string, so BasicAuth is not used – **this is your Cabin API key**, which you can get for free at [Cabin][] (note you could provide your own API key here if you are self-hosting or rolling your own logging service)
274
- * `endpoint` (String) - defaults to `https://api.cabinjs.com`
275
- * `headers` (Object) - HTTP headers to send along with log to the `endpoint`
276
- * `timeout` (Number) - defaults to `5000`, number of milliseconds to wait for a response
277
- * `retry` (Number) - defaults to `3`, number of attempts to retry sending log over HTTP
278
- * `showStack` (Boolean) - defaults to `true` (attempts to parse a boolean value from `process.env.SHOW_STACK`) - whether or not to output a stack trace
279
- * `meta` (Object) - stores all meta config information
280
- * `show` (Boolean) - defaults to `true` (attempts to parse a boolean value from `process.env.SHOW_META` – meaning you can pass a flag `SHOW_META=true node app.js` when needed for debugging), whether or not to output metadata to logger methods.
281
- * `showApp` (Boolean) - defaults to `false` (attempts to parse a boolean value from `process.env.SHOW_META_APP` – meaning you can pass a flag `SHOW_META_APP=true node app.js` when needed for debugging), whether or not to output `appInfo` in the metadata to logger methods.
282
- * `omittedFields` (Array) - defaults to `[]` (attempts to parse an array value from `process.env.OMIT_META_FIELDS` (`,` delimited) - meaning you can pass a flag `OMIT_META_FIELDS=user,id node app.js`), determining which fields to omit in the metadata passed to logger methods.
283
- * `silent` (Boolean) - defaults to `false`, whether or not to suppress log output to console
284
- * `logger` (Object) - defaults to `console` (with [console-polyfill][] added automatically), but you may wish to use a [custom logger](#custom-logger)
285
- * `name` (String) - the default name for the logger (defaults to `false`, which does not set `logger.name`). If you wish to pass a name such as `os.hostname()`, then set `name: os.hostname()` – this is useful if you are using a logger like `pino` which prefixes log output with the name set here.
286
- * `level` (String) - the default level of logging to capture (defaults to `info`, which includes all logs including info and higher in severity (e.g. `info`, `warn`, `error`, `fatal`)
287
- * `capture` (Boolean) - defaults to `false`, whether or not to `POST` logs to the `endpoint` (takes into consideration the `config.level` to only send valid capture levels)
288
- * `callback` (Function) - defaults to `false`, but if it is a `Function`, then it will be called with `callback(level, message, meta)` – this is super useful for [sending messages to Slack when errors occur (see below)](#send-logs-to-slack). Note that if you specify `{ callback: false }` in the meta object when logging, it will prevent the callback function from being invoked (e.g. `axe.error(new Error('Slack callback failed'), { callback: false })` ‐ see below example). The `callback` property is always purged from `meta` object for sanity.
289
- * `appInfo` (Boolean) - defaults to `true` (attempts to parse a boolean value from `process.env.APP_INFO`) - whether or not to parse application information (using [parse-app-info][]).
692
+ // foo bar { response: { status: 200 } }
693
+ ```
694
+
695
+ ### Picking
696
+
697
+ If you would like to pick certain fields, such as `response.status` from a response:
290
698
 
291
- ### DEPRECATED
699
+ ```js
700
+ const logger = new Axe({
701
+ meta: {
702
+ pickedFields: [ 'response.status' ]
703
+ }
704
+ });
292
705
 
293
- * `showMeta` (Boolean) - defaults to `true` (attempts to parse a boolean value from `process.env.SHOW_META` – meaning you can pass a flag `SHOW_META=true node app.js` when needed for debugging), whether or not to output metadata to logger methods.
294
- * This will be automatically assigned to `meta.show` when passed as part of config.
706
+ logger.info('foo bar', {
707
+ response: {
708
+ status: 200,
709
+ headers: {
710
+ 'X-Hello-World': true
711
+ }
712
+ }
713
+ });
295
714
 
715
+ // foo bar { response: { status: 200 } }
716
+ ```
296
717
 
297
- ## Aliases
718
+ ### Aliases
298
719
 
299
720
  We have provided helper/safety aliases for `logger.warn` and `logger.error` of `logger.warning` and `logger.err` respectively.
300
721
 
722
+ ### Methods
723
+
724
+ A few extra methods are available, which were inspired by [Slack's logger][slack-logger] and added for compatibility:
725
+
726
+ * `logger.setLevel(level)` - sets the log `level` (String) severity to invoke `logger` methods for (must be valid enumerable level)
727
+ * `logger.getNormalizedLevel(level)` - gets the normalized log `level` (String) severity (normalizes to known logger levels, e.g. "warning" => "warn", "err" => "error", "log" => "info")
728
+ * `logger.setName(name)` - sets the `name` (String) property (some loggers like `pino` will prefix logs with the name set here)
301
729
 
302
- ## Methods
303
730
 
304
- Two extra methods are available, which were inspired by [Slack's logger][slack-logger] and added for compatibility:
731
+ ## Examples
305
732
 
306
- * `axe.setLevel(level)` - sets the log `level` (String) severity to capture (must be valid enumerable level)
307
- * `axe.getNormalizedLevel(level)` - gets the normalized log `level` (String) severity (normalizes to known logger levels, e.g. "warning" => "warn", "err" => "error", "log" => "info")
308
- * `axe.setName(name)` - sets the `name` (String) property (some loggers like `pino` will prefix logs with the name set here)
309
- * `axe.setCallback(callback)` - sets the `callback` (Function) property (see `callback` option above and [Slack example below](#send-logs-to-slack)
733
+ ### Send Logs to HTTP Endpoint
310
734
 
735
+ This is an example of using hooks to send a POST request to an HTTP endpoint with logs of the "fatal" and "error" levels that occur in your application:
311
736
 
312
- ## Send Logs To Slack
737
+ We recommend [superagent](https://github.com/visionmedia/superagent), however there are plenty of alternatives such as [axios](https://github.com/axios/axios) and [ky](https://github.com/sindresorhus/ky).
313
738
 
314
- This is just an example of using the `callback` option to send a message to Slack with errors that occur in your application:
739
+ 1. You will need to install the `superagent`, `cuid`, and `fast-safe-stringify` packages:
740
+
741
+ ```sh
742
+ npm install superagent cuid fast-safe-stringify
743
+ ```
744
+
745
+ 2. Implementation example is provided below:
746
+
747
+ ```js
748
+ const os = require('os');
749
+
750
+ const Axe = require('axe');
751
+ const superagent = require('superagent');
752
+ const cuid = require('cuid');
753
+ const safeStringify = require('fast-safe-stringify');
754
+
755
+ // create our application logger that uses hooks
756
+ const logger = new Axe({
757
+ logger: console, // optional (e.g. pino, signale, consola),
758
+ level: 'info', // optional (defaults to info)
759
+ name: process.env.HOSTNAME || os.hostname() // optional
760
+ });
761
+
762
+ async function hook(next, message, meta) {
763
+ //
764
+ // return early if we wish to ignore this
765
+ // (this prevents recursion; see end of this fn)
766
+ //
767
+ if (meta.ignore_emit) return next();
768
+
769
+ try {
770
+ //
771
+ // set the body used in the HTTP request to be consistent object with
772
+ // two properties sent in the payload of `message` and `meta`
773
+ // (and also remove circular references)
774
+ //
775
+ const body = safeStringify({ message, meta });
776
+
777
+ //
778
+ // send to Cabin or your own custom endpoint here
779
+ // https://cabinjs.com
780
+ //
781
+ const request = superagent
782
+ .post('https://api.cabinjs.com')
783
+ .set('X-Request-Id', cuid()) // normalize server/browser request id formatting
784
+ .timeout(5000);
785
+
786
+ request.set('User-Agent', `axe/${logger.version}`);
787
+
788
+ // add basic auth header (e.g. if you use Cabin)
789
+ // if (config.key) request.auth('INSERT-YOUR-KEY');
790
+
791
+ // set any additional headers if necessary
792
+ // request.set({ ... });
793
+
794
+ const response = await request
795
+ .type('application/json')
796
+ .retry(3)
797
+ .send(body);
798
+
799
+ logger.info('log sent over HTTP', { response });
800
+ } catch (err) {
801
+ logger.fatal(err, { ignore_emit: true });
802
+ }
803
+
804
+ // move along to the next hook
805
+ next();
806
+ }
807
+
808
+ // bind custom hooks for "fatal" and "error" log levels
809
+ logger.post('error', hook);
810
+ logger.post('fatal', hook);
811
+
812
+ // test out the HTTP integration
813
+ logger.error(new Error('Uh oh something went wrong!'));
814
+ ```
815
+
816
+ ### Send Logs to Slack
817
+
818
+ This is an example of using hooks to send a message to Slack with logs of the "fatal" and "error" levels that occur in your application:
315
819
 
316
820
  1. You will need to install the `@slack/web-api` package locally:
317
821
 
@@ -327,58 +831,37 @@ This is just an example of using the `callback` option to send a message to Slac
327
831
 
328
832
  ```js
329
833
  const os = require('os');
834
+
330
835
  const Axe = require('axe');
331
836
  const { WebClient } = require('@slack/web-api');
332
- const signale = require('signale');
333
- const pino = require('pino')({
334
- customLevels: {
335
- log: 30
336
- },
337
- hooks: {
338
- // <https://github.com/pinojs/pino/blob/master/docs/api.md#logmethod>
339
- logMethod(inputArgs, method) {
340
- return method.call(this, {
341
- // <https://github.com/pinojs/pino/issues/854>
342
- // message: inputArgs[0],
343
- msg: inputArgs[0],
344
- meta: inputArgs[1]
345
- });
346
- }
347
- }
348
- });
349
-
350
- const isProduction = process.env.NODE_ENV === 'production';
351
-
352
- const config = {
353
- logger: isProduction ? pino : signale,
354
- level: isProduction ? 'warn' : 'info',
355
- name: process.env.HOSTNAME || os.hostname()
356
- };
357
837
 
358
- // custom logger for Slack that inherits our Axe config
359
- // (with the exception of a `callback` function for logging to Slack)
360
- const slackLogger = new Axe(config);
838
+ // create our application logger that uses hooks
839
+ const logger = new Axe({
840
+ logger: console, // optional (e.g. pino, signale, consola)
841
+ level: 'info', // optional (defaults to info)
842
+ name: process.env.HOSTNAME || os.hostname() // optional
843
+ });
361
844
 
362
845
  // create an instance of the Slack Web Client API for posting messages
363
846
  const web = new WebClient('INSERT-YOUR-TOKEN', {
364
847
  // <https://slack.dev/node-slack-sdk/web-api#logging>
365
- logger: slackLogger,
366
- logLevel: config.level
848
+ logger,
849
+ logLevel: logger.config.level
367
850
  });
368
851
 
369
- // create our application logger that uses a custom callback function
370
- const axe = new Axe({ ...config });
852
+ async function hook(next, message, meta) {
853
+ //
854
+ // return early if we wish to ignore this
855
+ // (this prevents recursion; see end of this fn)
856
+ //
857
+ if (meta.ignore_emit) return next();
371
858
 
372
- axe.setCallback(async (level, message, meta) => {
859
+ // otherwise post a message to the slack channel
373
860
  try {
374
- // if it was not an error then return early
375
- if (!['error','fatal'].includes(level)) return;
376
-
377
- // otherwise post a message to the slack channel
378
861
  const result = await web.chat.postMessage({
379
- channel: 'general',
380
- username: 'Cabin',
381
- icon_emoji: ':evergreen_tree:',
862
+ channel: 'monitoring',
863
+ username: 'Axe',
864
+ icon_emoji: ':axe:',
382
865
  attachments: [
383
866
  {
384
867
  title: meta.err && meta.err.message ? meta.err.message : message,
@@ -411,29 +894,86 @@ This is just an example of using the `callback` option to send a message to Slac
411
894
  });
412
895
 
413
896
  // finally log the result from slack
414
- axe.info('web.chat.postMessage', { result, callback: false });
897
+ logger.info('slack message sent', { result });
415
898
  } catch (err) {
416
- axe.error(err, { callback: false });
899
+ logger.fatal(err, { ignore_emit: true });
417
900
  }
418
- });
419
901
 
420
- axe.error(new Error('Uh oh something went wrong!'));
902
+ // move along to the next hook
903
+ next();
904
+ }
905
+
906
+ // bind custom hooks for "fatal" and "error" log levels
907
+ logger.post('error', hook);
908
+ logger.post('fatal', hook);
909
+
910
+ // test out the slack integration
911
+ logger.error(new Error('Uh oh something went wrong!'));
421
912
  ```
422
913
 
914
+ ### Suppress Logger Data
423
915
 
424
- ## Contributors
916
+ This is an example of using a custom hook to manipulate logger arguments to suppress sensitive data.
425
917
 
426
- | Name | Website |
427
- | ---------------- | ------------------------- |
428
- | **Nick Baugh** | <http://niftylettuce.com> |
429
- | **Alexis Tyler** | <https://wvvw.me/> |
918
+ ```js
919
+ const Axe = require('.');
920
+
921
+ const logger = new Axe();
922
+
923
+ for (const level of logger.config.levels) {
924
+ const fn = logger.config.logger[level];
925
+ logger.config.logger[level] = function (message, meta) {
926
+ // replace any messages "beep" -> "boop"
927
+ if (typeof message === 'string') message = message.replace(/beep/g, 'boop');
928
+
929
+ // mask the property "beep" in the meta object "data"
930
+ if (meta?.data?.beep)
931
+ meta.data.beep = Array.from({ length: meta.data.beep.length })
932
+ .fill('*')
933
+ .join('');
430
934
 
935
+ return Reflect.apply(fn, this, [message, meta]);
936
+ };
937
+ }
431
938
 
432
- ## Trademark Notice
939
+ logger.warn('hello world beep');
433
940
 
434
- Axe, Lad, Lass, and their respective logos are trademarks of Niftylettuce LLC.
435
- These trademarks may not be reproduced, distributed, transmitted, or otherwise used, except with the prior written permission of Niftylettuce LLC.
436
- If you are seeking permission to use these trademarks, then please [contact us](mailto:niftylettuce@gmail.com).
941
+ // hello world boop
942
+
943
+ logger.info('start', {
944
+ data: {
945
+ foo: 'bar',
946
+ beep: 'boop' // <--- we're suppressing "beep" -> "****"
947
+ }
948
+ });
949
+
950
+ // start { data: { foo: 'bar', beep: '****' } }
951
+
952
+ logger.error(new Error('oops!'), {
953
+ data: {
954
+ beep: 'beep-boop-beep' // this becomes "**************"
955
+ }
956
+ });
957
+
958
+ // Error: oops!
959
+ // at Object.<anonymous> (/Users/user/Projects/axe/test.js:30:14)
960
+ // at Module._compile (node:internal/modules/cjs/loader:1105:14)
961
+ // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
962
+ // at Module.load (node:internal/modules/cjs/loader:981:32)
963
+ // at Function.Module._load (node:internal/modules/cjs/loader:822:12)
964
+ // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
965
+ // at node:internal/main/run_main_module:17:47 { data: { beep: '**************' } }
966
+ ```
967
+
968
+
969
+ ## Contributors
970
+
971
+ | Name | Website |
972
+ | ------------------ | --------------------------------- |
973
+ | **Nick Baugh** | <http://niftylettuce.com> |
974
+ | **Alexis Tyler** | <https://wvvw.me/> |
975
+ | **shadowgate15** | <https://github.com/shadowgate15> |
976
+ | **Spencer Snyder** | <https://spencersnyder.io> |
437
977
 
438
978
 
439
979
  ## License
@@ -457,8 +997,6 @@ If you are seeking permission to use these trademarks, then please [contact us](
457
997
 
458
998
  [signale]: https://github.com/klauscfhq/signale
459
999
 
460
- [high-console]: https://github.com/tusharf5/high-console
461
-
462
1000
  [pino]: https://github.com/pinojs/pino
463
1001
 
464
1002
  [winston]: https://github.com/winstonjs/winston
@@ -467,11 +1005,9 @@ If you are seeking permission to use these trademarks, then please [contact us](
467
1005
 
468
1006
  [console-polyfill]: https://github.com/paulmillr/console-polyfill
469
1007
 
470
- [cabin-api]: https://github.com/cabinjs/api.cabinjs.com
471
-
472
1008
  [consola]: https://github.com/nuxt/consola
473
1009
 
474
- [log4j]: https://en.wikipedia.org/wiki/Log4
1010
+ [log4j]: https://en.wikipedia.org/wiki/Log4j#Log4j_log_levels
475
1011
 
476
1012
  [parse-app-info]: https://github.com/cabinjs/parse-app-info
477
1013
 
@@ -481,4 +1017,12 @@ If you are seeking permission to use these trademarks, then please [contact us](
481
1017
 
482
1018
  [util.format]: https://nodejs.org/api/util.html#util_util_format_format_args
483
1019
 
484
- [format-specifiers]: https://github.com/niftylettuce/format-specifiers
1020
+ [format-specifiers]: https://github.com/cabinjs/format-specifiers
1021
+
1022
+ [forward-email]: https://forwardemail.net
1023
+
1024
+ [high-console]: https://github.com/tusharf5/high-console
1025
+
1026
+ [maybe-combine-errors]: https://github.com/vweevers/maybe-combine-errors
1027
+
1028
+ [parse-err]: https://github.com/cabinjs/parse-err