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.
- package/README.md +723 -206
- package/dist/axe.js +1298 -7130
- package/dist/axe.min.js +1 -1
- package/lib/index.js +357 -276
- package/package.json +45 -88
package/README.md
CHANGED
|
@@ -1,176 +1,493 @@
|
|
|
1
1
|
# Axe
|
|
2
2
|
|
|
3
|
-
[](https://codecov.io/gh/cabinjs/axe)
|
|
3
|
+
[](https://github.com/cabinjs/axe/actions/workflows/ci.yml)
|
|
5
4
|
[](https://github.com/sindresorhus/xo)
|
|
6
5
|
[](https://github.com/prettier/prettier)
|
|
7
6
|
[](https://github.com/lassjs/lass)
|
|
8
7
|
[](LICENSE)
|
|
9
8
|
|
|
10
|
-
>
|
|
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
|
-
* [
|
|
25
|
-
* [Suppress logs](#suppress-logs)
|
|
25
|
+
* [Silent Logging](#silent-logging)
|
|
26
26
|
* [Stack Traces and Error Handling](#stack-traces-and-error-handling)
|
|
27
|
-
* [
|
|
28
|
-
* [
|
|
29
|
-
* [
|
|
30
|
-
* [
|
|
31
|
-
* [
|
|
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
|
-
##
|
|
41
|
+
## Foreword
|
|
38
42
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
67
|
+
Or if you're logging a user, or a variable in general:
|
|
54
68
|
|
|
55
|
-
|
|
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
|
-
|
|
79
|
+
You might write logs with three arguments `(level, message, meta)` using the `log` method of Axe's returned `logger` instance:
|
|
59
80
|
|
|
60
|
-
|
|
81
|
+
```js
|
|
82
|
+
logger.log('info', 'Hello world', { beep: 'boop', foo: true });
|
|
83
|
+
// Hello world { beep: 'boop', foo: true }
|
|
84
|
+
```
|
|
61
85
|
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
128
|
+
Or even multiple errors:
|
|
78
129
|
|
|
79
130
|
```js
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
axe.
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
166
|
+
logger.log('hello world');
|
|
167
|
+
// hello world
|
|
91
168
|
```
|
|
92
169
|
|
|
93
|
-
|
|
170
|
+
```js
|
|
171
|
+
logger.info('hello world');
|
|
172
|
+
// hello world
|
|
173
|
+
```
|
|
94
174
|
|
|
95
|
-
|
|
175
|
+
```js
|
|
176
|
+
logger.warn('uh oh!', { amount_spent: 50 });
|
|
177
|
+
// uh oh! { amount_spent: 50 }
|
|
178
|
+
```
|
|
96
179
|
|
|
97
|
-
|
|
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
|
-
|
|
182
|
+
```js
|
|
183
|
+
logger.warn({ hello: 'world' }, 'uh oh');
|
|
184
|
+
// uh oh { hello: 'world' }
|
|
185
|
+
```
|
|
100
186
|
|
|
101
187
|
```js
|
|
102
|
-
|
|
188
|
+
logger.warn('uh oh', 'foo bar', 'beep boop');
|
|
189
|
+
// uh oh foo bar beep boop
|
|
103
190
|
```
|
|
104
191
|
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
+
logger.info('favorite color is %s', 'blue');
|
|
222
|
+
// favorite color is blue
|
|
115
223
|
```
|
|
116
224
|
|
|
117
|
-
|
|
225
|
+
As you can see, Axe makes your logs consistent in both Node and browser environments.
|
|
118
226
|
|
|
119
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
+
## Application Metadata and Information
|
|
140
291
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
464
|
+
const logger = new Axe();
|
|
157
465
|
|
|
158
|
-
|
|
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
|
|
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=
|
|
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
|
-
|
|
173
|
-
|
|
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=
|
|
500
|
+
<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise"></script>
|
|
184
501
|
```
|
|
185
502
|
|
|
186
|
-
*
|
|
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
|
|
530
|
+
const logger = new Axe({ logger: signale });
|
|
233
531
|
|
|
234
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
+
```js
|
|
604
|
+
const Axe = require('axe');
|
|
274
605
|
|
|
275
|
-
|
|
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
|
-
|
|
617
|
+
logger.info('hello world');
|
|
278
618
|
|
|
279
|
-
|
|
619
|
+
// hello planet earth
|
|
620
|
+
```
|
|
280
621
|
|
|
281
622
|
```js
|
|
282
623
|
const Axe = require('axe');
|
|
283
624
|
|
|
284
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
648
|
+
### Remapping
|
|
292
649
|
|
|
293
|
-
|
|
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
|
-
|
|
661
|
+
logger.info('foo bar', {
|
|
662
|
+
response: {
|
|
663
|
+
headers: {
|
|
664
|
+
'X-Hello-World': true
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
});
|
|
297
668
|
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
###
|
|
672
|
+
### Omitting
|
|
317
673
|
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
731
|
+
## Examples
|
|
330
732
|
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
391
|
-
logLevel: config.level
|
|
848
|
+
logger,
|
|
849
|
+
logLevel: logger.config.level
|
|
392
850
|
});
|
|
393
851
|
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
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: '
|
|
405
|
-
username: '
|
|
406
|
-
icon_emoji: ':
|
|
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
|
-
|
|
897
|
+
logger.info('slack message sent', { result });
|
|
440
898
|
} catch (err) {
|
|
441
|
-
|
|
899
|
+
logger.fatal(err, { ignore_emit: true });
|
|
442
900
|
}
|
|
443
|
-
});
|
|
444
901
|
|
|
445
|
-
|
|
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
|
-
|
|
916
|
+
This is an example of using a custom hook to manipulate logger arguments to suppress sensitive data.
|
|
450
917
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
##
|
|
969
|
+
## Contributors
|
|
458
970
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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/
|
|
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/
|
|
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
|