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