axe 8.1.2 → 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 -206
  2. package/dist/axe.js +1298 -7130
  3. package/dist/axe.min.js +1 -1
  4. package/lib/index.js +357 -276
  5. package/package.json +45 -88
package/README.md CHANGED
@@ -1,176 +1,493 @@
1
1
  # Axe
2
2
 
3
- [![build status](https://img.shields.io/travis/cabinjs/axe.svg)](https://travis-ci.org/cabinjs/axe)
4
- [![code coverage](https://img.shields.io/codecov/c/github/cabinjs/axe.svg)](https://codecov.io/gh/cabinjs/axe)
3
+ [![build status](https://github.com/cabinjs/axe/actions/workflows/ci.yml/badge.svg)](https://github.com/cabinjs/axe/actions/workflows/ci.yml)
5
4
  [![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
6
5
  [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
7
6
  [![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://github.com/lassjs/lass)
8
7
  [![license](https://img.shields.io/github/license/cabinjs/axe.svg)](LICENSE)
9
8
 
10
- > 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][].
11
10
 
12
11
 
13
12
  ## Table of Contents
14
13
 
14
+ * [Foreword](#foreword)
15
+ * [Application Metadata and Information](#application-metadata-and-information)
15
16
  * [Install](#install)
16
17
  * [Node](#node)
17
18
  * [Browser](#browser)
18
- * [Approach](#approach)
19
- * [Application Information](#application-information)
20
19
  * [Usage](#usage)
20
+ * [Options](#options)
21
+ * [Supported Platforms](#supported-platforms)
21
22
  * [Node](#node-1)
22
23
  * [Browser](#browser-1)
23
24
  * [Custom logger](#custom-logger)
24
- * [Custom endpoint](#custom-endpoint)
25
- * [Suppress logs](#suppress-logs)
25
+ * [Silent Logging](#silent-logging)
26
26
  * [Stack Traces and Error Handling](#stack-traces-and-error-handling)
27
- * [Options](#options)
28
- * [DEPRECATED](#deprecated)
29
- * [Aliases](#aliases)
30
- * [Methods](#methods)
31
- * [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)
32
37
  * [Contributors](#contributors)
33
- * [Trademark Notice](#trademark-notice)
34
38
  * [License](#license)
35
39
 
36
40
 
37
- ## Install
41
+ ## Foreword
38
42
 
39
- ### 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.
40
44
 
41
- [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.
42
46
 
43
- ```sh
44
- npm install axe
45
- ```
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.
46
48
 
47
- [yarn][]:
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.
48
50
 
49
- ```sh
50
- yarn add axe
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:
52
+
53
+ * `trace`
54
+ * `debug`
55
+ * `info`
56
+ * `warn`
57
+ * `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 }
51
65
  ```
52
66
 
53
- ### Browser
67
+ Or if you're logging a user, or a variable in general:
54
68
 
55
- See [Browser](#browser-1) usage below for more information.
69
+ ```js
70
+ logger.info('user', { user: { id: '1' } });
71
+ // user { user: { id: '1' } }
72
+ ```
56
73
 
74
+ ```js
75
+ logger.info('someVariable', { someVariable: true });
76
+ // someVariable { someVariable: true }
77
+ ```
57
78
 
58
- ## Approach
79
+ You might write logs with three arguments `(level, message, meta)` using the `log` method of Axe's returned `logger` instance:
59
80
 
60
- 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:
81
+ ```js
82
+ logger.log('info', 'Hello world', { beep: 'boop', foo: true });
83
+ // Hello world { beep: 'boop', foo: true }
84
+ ```
61
85
 
62
- * `trace`
63
- * `debug`
64
- * `info`
65
- * `warn`
66
- * `error`
67
- * `fatal` (uses `error`)
86
+ Logging errors is just the same as you might do now:
68
87
 
69
- We highly recommend that you follow this approach when logging `(message, meta)`:
88
+ ```js
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)
102
+ ```
103
+
104
+ You might log errors like this:
70
105
 
71
106
  ```js
72
- const message = 'Hello world';
73
- const meta = { beep: 'boop', foo: true };
74
- axe.info(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
75
126
  ```
76
127
 
77
- You can also make logs with three arguments `(level, message, meta)`:
128
+ Or even multiple errors:
78
129
 
79
130
  ```js
80
- const level = 'info';
81
- const message = 'Hello world';
82
- const meta = { beep: 'boop', foo: true };
83
- axe.log(level, message, meta);
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
- You should also log errors like this:
161
+ As you can see, Axe combines multiple errors into one – for an easy to read stack trace.
162
+
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.
87
164
 
88
165
  ```js
89
- const err = new Error('Oops!');
90
- axe.error(err);
166
+ logger.log('hello world');
167
+ // hello world
91
168
  ```
92
169
 
93
- **To recap:** The first argument `message` should be a String, and the second `meta` should be an optional Object.
170
+ ```js
171
+ logger.info('hello world');
172
+ // hello world
173
+ ```
94
174
 
95
- 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`).
175
+ ```js
176
+ logger.warn('uh oh!', { amount_spent: 50 });
177
+ // uh oh! { amount_spent: 50 }
178
+ ```
96
179
 
97
- 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.
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:**
98
181
 
99
- 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:
182
+ ```js
183
+ logger.warn({ hello: 'world' }, 'uh oh');
184
+ // uh oh { hello: 'world' }
185
+ ```
100
186
 
101
187
  ```js
102
- { message: 'hello world', meta: { level: 'info' } }
188
+ logger.warn('uh oh', 'foo bar', 'beep boop');
189
+ // uh oh foo bar beep boop
103
190
  ```
104
191
 
105
- 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:
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
+ ```
106
204
 
107
205
  ```js
108
- { 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
109
216
  ```
110
217
 
111
- 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][].
112
219
 
113
220
  ```js
114
- { message: 'uh oh!', meta: { amount_spent: 50, level: 'warn' } }
221
+ logger.info('favorite color is %s', 'blue');
222
+ // favorite color is blue
115
223
  ```
116
224
 
117
- 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.
118
226
 
119
- 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][].
227
+ Axe's goal is to allow you to log in any style, but make your log output more readable, organized, and clean.
120
228
 
121
- **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!**
229
+ The **most impactful feature of Axe** is that it **makes logger output human-friendly and readable** when there are multiple errors.
122
230
 
231
+ Normally `console` output (and most other loggers) by default will output the following unreadable stack trace:
123
232
 
124
- ## Application Information
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
+ ```
125
257
 
126
- By default a `meta.app` property is populated in all logs for you using [parse-app-info][].
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
+ ```
127
286
 
128
- At a glance, here are the properties that are automatically populated for you:
287
+ Lastly, Axe works in both server-side and client-side environments (with Node and the browser).
129
288
 
130
- | Property | Description |
131
- | ----------- | ----------------------------------- |
132
- | environment | The value of NODE_ENV |
133
- | hostname | Name of the computer |
134
- | name | Name of the app from `package.json` |
135
- | node | Version if node.js running the app |
136
- | pid | Process ID as in `process.pid` |
137
- | version | Version of the app `package.json` |
138
289
 
139
- Additional properties when the app is in a git repository
290
+ ## Application Metadata and Information
140
291
 
141
- | Property | Description |
142
- | -------- | ------------------------------------------------------------------ |
143
- | hash | git hash of latest commit if the app |
144
- | tag | the latest git tag. Property is not available when there is no tag |
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).
293
+
294
+ Axe will automatically add the following metadata and information to the `meta` Object argument passed to logger methods:
295
+
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. |
315
+
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).
317
+
318
+ Axe will omit from metadata all properties via the default Array from `meta.omittedFields` option (see [Options](#options) below for more insight).
319
+
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.
321
+
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:
323
+
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.
145
394
 
146
395
 
147
396
  ## Usage
148
397
 
149
- 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
+ ```
150
458
 
151
459
  ### Node
152
460
 
153
461
  ```js
154
462
  const Axe = require('axe');
155
463
 
156
- const axe = new Axe({ key: 'YOUR-CABIN-API-KEY' });
464
+ const logger = new Axe();
157
465
 
158
- axe.info('hello world');
466
+ logger.info('hello world');
159
467
  ```
160
468
 
161
469
  ### Browser
162
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
+
163
475
  #### VanillaJS
164
476
 
165
- **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**.
166
478
 
167
479
  ```html
168
- <script src="https://polyfill.io/v3/polyfill.min.js?features=es6,Map,Map.prototype,Math.sign,Promise,Reflect,Symbol,Symbol.iterator,Symbol.prototype,Symbol.toPrimitive,Symbol.toStringTag,Uint32Array,window.crypto,Object.assign,Object.getOwnPropertySymbols,Array.from,Set,BigInt,WeakMap,WeakRef,WeakSet"></script>
480
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=Promise"></script>
169
481
  <script src="https://unpkg.com/axe"></script>
170
482
  <script type="text/javascript">
171
- (function() {
172
- var Axe = new Axe({ key: 'YOUR-CABIN-API-KEY' });
173
- 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');
174
491
  });
175
492
  </script>
176
493
  ```
@@ -180,29 +497,10 @@ axe.info('hello world');
180
497
  We recommend using <https://polyfill.io> (specifically with the bundle mentioned in [VanillaJS](#vanillajs) above):
181
498
 
182
499
  ```html
183
- <script src="https://polyfill.io/v3/polyfill.min.js?features=es6,Map,Map.prototype,Math.sign,Promise,Reflect,Symbol,Symbol.iterator,Symbol.prototype,Symbol.toPrimitive,Symbol.toStringTag,Uint32Array,window.crypto,Object.assign,Object.getOwnPropertySymbols,Array.from,Set,BigInt,WeakMap,WeakRef,WeakSet"></script>
500
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=Promise"></script>
184
501
  ```
185
502
 
186
- * Map is not supported in IE 10
187
- * Map.prototype() is not supported in IE 10
188
- * Math.sign() is not supported in IE 10
189
- * Promise is not supported in Opera Mobile 12.1, Opera Mini all, IE Mobile 10, IE 10, Blackberry Browser 7
190
- * Reflect is not supported in IE 10
191
- * Symbol.iterator() is not supported in IE 10
192
- * Symbol.prototype() is not supported in IE 10
193
- * Symbol.toPrimitive() is not supported in IE 10
194
- * Symbol.toStringTag() is not supported in IE 10
195
- * Uint32Array is not supported in IE Mobile 10, IE 10, Blackberry Browser 7
196
- * window\.crypto() is not supported in IE 10
197
- * Object.assign() is not supported in IE 10
198
- * Object.getOwnPropertySymbols() is not supported in IE 10
199
- * Array.from() is not supported in IE 10
200
- * Set is not supported in IE 10
201
- * Symbol is not supported in IE 10
202
- * BigInt is not supported in IE 10
203
- * WeakMap is not supported in IE 10
204
- * WeakRef is not supported in Opera 81, IE 10
205
- * WeakSet is not supported in IE 10
503
+ * Promise is not supported in op\_mini all
206
504
 
207
505
  #### Bundler
208
506
 
@@ -229,9 +527,9 @@ Loggers supported include, but are not limited to:
229
527
  const signale = require('signale');
230
528
  const Axe = require('axe');
231
529
 
232
- const axe = new Axe({ logger: signale, key: 'YOUR-CABIN-API-KEY' });
530
+ const logger = new Axe({ logger: signale });
233
531
 
234
- axe.info('hello world');
532
+ logger.info('hello world');
235
533
  ```
236
534
 
237
535
  In [Lad][], we have an approach similar to the following, where non-production environments use [consola][], and production environments use [pino][].
@@ -264,79 +562,260 @@ const logger = new Axe({
264
562
  logger.info('hello world');
265
563
  ```
266
564
 
267
- ### 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.
588
+
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).
590
+
591
+ Pre hooks require an Array to be returned of `[ err, message, meta ]`.
592
+
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.
594
+
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.
596
+
597
+ You should properly handle any errors in your pre hooks, otherwise they will be thrown and logger methods will not be invoked.
268
598
 
269
- 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.
599
+ We will catch errors for post hooks by default and log them as errors with your logger methods' `logger.error` method).
270
600
 
271
- 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`.
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:
272
602
 
273
- See [Options](#options) below for more information.
603
+ ```js
604
+ const Axe = require('axe');
274
605
 
275
- ### Suppress logs
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
+ });
276
616
 
277
- This is useful when you want need logging turned off in certain environments.
617
+ logger.info('hello world');
278
618
 
279
- For example when you're running tests you can set `axe.config.silent = true`.
619
+ // hello planet earth
620
+ ```
280
621
 
281
622
  ```js
282
623
  const Axe = require('axe');
283
624
 
284
- const axe = new Axe({ silent: true, key: 'YOUR-CABIN-API-KEY' });
625
+ const logger = new Axe();
626
+
627
+ logger.pre('error', (err, message, meta) => {
628
+ if (err instanceof Error) err.is_beep_boop = true;
629
+ return [err, message, meta];
630
+ });
285
631
 
286
- axe.info('hello world');
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
+ // }
287
644
  ```
288
645
 
289
- ### Stack Traces and Error Handling
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.
290
647
 
291
- Please see Cabin's documentation for [stack traces and error handling](https://github.com/cabinjs/cabin#stack-traces-and-error-handling) for more information.
648
+ ### Remapping
292
649
 
293
- > If you're not using `cabin`, you can simply replace instances of the word `cabin` with `axe` in the documentation examples linked above.
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.
294
651
 
652
+ ```js
653
+ const logger = new Axe({
654
+ meta: {
655
+ remappedFields: {
656
+ 'response.headers': 'responseHeaders'
657
+ }
658
+ }
659
+ });
295
660
 
296
- ## Options
661
+ logger.info('foo bar', {
662
+ response: {
663
+ headers: {
664
+ 'X-Hello-World': true
665
+ }
666
+ }
667
+ });
297
668
 
298
- * `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)
299
- * `endpoint` (String) - defaults to `https://api.cabinjs.com`
300
- * `headers` (Object) - HTTP headers to send along with log to the `endpoint`
301
- * `timeout` (Number) - defaults to `5000`, number of milliseconds to wait for a response
302
- * `retry` (Number) - defaults to `3`, number of attempts to retry sending log over HTTP
303
- * `showStack` (Boolean) - defaults to `true` (attempts to parse a boolean value from `process.env.SHOW_STACK`) - whether or not to output a stack trace
304
- * `meta` (Object) - stores all meta config information
305
- * `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.
306
- * `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.
307
- * `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.
308
- * `silent` (Boolean) - defaults to `false`, whether or not to suppress log output to console
309
- * `logger` (Object) - defaults to `console` (with [console-polyfill][] added automatically), but you may wish to use a [custom logger](#custom-logger)
310
- * `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.
311
- * `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`)
312
- * `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)
313
- * `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.
314
- * `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][]).
669
+ // foo bar { responseHeaders: { 'X-Hello-World': true } }
670
+ ```
315
671
 
316
- ### DEPRECATED
672
+ ### Omitting
317
673
 
318
- * `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.
319
- * This will be automatically assigned to `meta.show` when passed as part of config.
674
+ If you would like to omit fields, such as `response.headers` from a response, so you are only left with the status code:
320
675
 
676
+ ```js
677
+ const logger = new Axe({
678
+ meta: {
679
+ omittedFields: ['level', 'err', 'app', 'args', 'response.headers']
680
+ }
681
+ });
321
682
 
322
- ## Aliases
683
+ logger.info('foo bar', {
684
+ response: {
685
+ status: 200,
686
+ headers: {
687
+ 'X-Hello-World': true
688
+ }
689
+ }
690
+ });
691
+
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:
698
+
699
+ ```js
700
+ const logger = new Axe({
701
+ meta: {
702
+ pickedFields: [ 'response.status' ]
703
+ }
704
+ });
705
+
706
+ logger.info('foo bar', {
707
+ response: {
708
+ status: 200,
709
+ headers: {
710
+ 'X-Hello-World': true
711
+ }
712
+ }
713
+ });
714
+
715
+ // foo bar { response: { status: 200 } }
716
+ ```
717
+
718
+ ### Aliases
323
719
 
324
720
  We have provided helper/safety aliases for `logger.warn` and `logger.error` of `logger.warning` and `logger.err` respectively.
325
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)
326
729
 
327
- ## Methods
328
730
 
329
- Two extra methods are available, which were inspired by [Slack's logger][slack-logger] and added for compatibility:
731
+ ## Examples
330
732
 
331
- * `axe.setLevel(level)` - sets the log `level` (String) severity to capture (must be valid enumerable level)
332
- * `axe.getNormalizedLevel(level)` - gets the normalized log `level` (String) severity (normalizes to known logger levels, e.g. "warning" => "warn", "err" => "error", "log" => "info")
333
- * `axe.setName(name)` - sets the `name` (String) property (some loggers like `pino` will prefix logs with the name set here)
334
- * `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
335
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:
336
736
 
337
- ## 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).
338
738
 
339
- 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:
340
819
 
341
820
  1. You will need to install the `@slack/web-api` package locally:
342
821
 
@@ -352,58 +831,37 @@ This is just an example of using the `callback` option to send a message to Slac
352
831
 
353
832
  ```js
354
833
  const os = require('os');
834
+
355
835
  const Axe = require('axe');
356
836
  const { WebClient } = require('@slack/web-api');
357
- const signale = require('signale');
358
- const pino = require('pino')({
359
- customLevels: {
360
- log: 30
361
- },
362
- hooks: {
363
- // <https://github.com/pinojs/pino/blob/master/docs/api.md#logmethod>
364
- logMethod(inputArgs, method) {
365
- return method.call(this, {
366
- // <https://github.com/pinojs/pino/issues/854>
367
- // message: inputArgs[0],
368
- msg: inputArgs[0],
369
- meta: inputArgs[1]
370
- });
371
- }
372
- }
373
- });
374
-
375
- const isProduction = process.env.NODE_ENV === 'production';
376
-
377
- const config = {
378
- logger: isProduction ? pino : signale,
379
- level: isProduction ? 'warn' : 'info',
380
- name: process.env.HOSTNAME || os.hostname()
381
- };
382
837
 
383
- // custom logger for Slack that inherits our Axe config
384
- // (with the exception of a `callback` function for logging to Slack)
385
- 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
+ });
386
844
 
387
845
  // create an instance of the Slack Web Client API for posting messages
388
846
  const web = new WebClient('INSERT-YOUR-TOKEN', {
389
847
  // <https://slack.dev/node-slack-sdk/web-api#logging>
390
- logger: slackLogger,
391
- logLevel: config.level
848
+ logger,
849
+ logLevel: logger.config.level
392
850
  });
393
851
 
394
- // create our application logger that uses a custom callback function
395
- 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();
396
858
 
397
- axe.setCallback(async (level, message, meta) => {
859
+ // otherwise post a message to the slack channel
398
860
  try {
399
- // if it was not an error then return early
400
- if (!['error','fatal'].includes(level)) return;
401
-
402
- // otherwise post a message to the slack channel
403
861
  const result = await web.chat.postMessage({
404
- channel: 'general',
405
- username: 'Cabin',
406
- icon_emoji: ':evergreen_tree:',
862
+ channel: 'monitoring',
863
+ username: 'Axe',
864
+ icon_emoji: ':axe:',
407
865
  attachments: [
408
866
  {
409
867
  title: meta.err && meta.err.message ? meta.err.message : message,
@@ -436,29 +894,86 @@ This is just an example of using the `callback` option to send a message to Slac
436
894
  });
437
895
 
438
896
  // finally log the result from slack
439
- axe.info('web.chat.postMessage', { result, callback: false });
897
+ logger.info('slack message sent', { result });
440
898
  } catch (err) {
441
- axe.error(err, { callback: false });
899
+ logger.fatal(err, { ignore_emit: true });
442
900
  }
443
- });
444
901
 
445
- 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!'));
446
912
  ```
447
913
 
914
+ ### Suppress Logger Data
448
915
 
449
- ## Contributors
916
+ This is an example of using a custom hook to manipulate logger arguments to suppress sensitive data.
450
917
 
451
- | Name | Website |
452
- | ---------------- | ------------------------- |
453
- | **Nick Baugh** | <http://niftylettuce.com> |
454
- | **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('');
934
+
935
+ return Reflect.apply(fn, this, [message, meta]);
936
+ };
937
+ }
938
+
939
+ logger.warn('hello world beep');
940
+
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
+ ```
455
967
 
456
968
 
457
- ## Trademark Notice
969
+ ## Contributors
458
970
 
459
- Axe, Lad, Lass, and their respective logos are trademarks of Niftylettuce LLC.
460
- These trademarks may not be reproduced, distributed, transmitted, or otherwise used, except with the prior written permission of Niftylettuce LLC.
461
- If you are seeking permission to use these trademarks, then please [contact us](mailto:niftylettuce@gmail.com).
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> |
462
977
 
463
978
 
464
979
  ## License
@@ -470,8 +985,6 @@ If you are seeking permission to use these trademarks, then please [contact us](
470
985
 
471
986
  [npm]: https://www.npmjs.com/
472
987
 
473
- [yarn]: https://yarnpkg.com/
474
-
475
988
  [lad]: https://lad.js.org/
476
989
 
477
990
  [cabin]: https://cabinjs.com/
@@ -484,8 +997,6 @@ If you are seeking permission to use these trademarks, then please [contact us](
484
997
 
485
998
  [signale]: https://github.com/klauscfhq/signale
486
999
 
487
- [high-console]: https://github.com/tusharf5/high-console
488
-
489
1000
  [pino]: https://github.com/pinojs/pino
490
1001
 
491
1002
  [winston]: https://github.com/winstonjs/winston
@@ -494,11 +1005,9 @@ If you are seeking permission to use these trademarks, then please [contact us](
494
1005
 
495
1006
  [console-polyfill]: https://github.com/paulmillr/console-polyfill
496
1007
 
497
- [cabin-api]: https://github.com/cabinjs/api.cabinjs.com
498
-
499
1008
  [consola]: https://github.com/nuxt/consola
500
1009
 
501
- [log4j]: https://en.wikipedia.org/wiki/Log4
1010
+ [log4j]: https://en.wikipedia.org/wiki/Log4j#Log4j_log_levels
502
1011
 
503
1012
  [parse-app-info]: https://github.com/cabinjs/parse-app-info
504
1013
 
@@ -508,4 +1017,12 @@ If you are seeking permission to use these trademarks, then please [contact us](
508
1017
 
509
1018
  [util.format]: https://nodejs.org/api/util.html#util_util_format_format_args
510
1019
 
511
- [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