Skip to content

Commit 59b9a86

Browse files
yasmanetsYaser El Dabete Arribasjsumners
authored
Support custom levels (#27)
* buildOptions with custom levels * tests added * Readme updated * version updated * update changelog * fix typo * fix levels relationship * fix comments * docs * Apply suggestions from code review --------- Co-authored-by: Yaser El Dabete Arribas <[email protected]> Co-authored-by: James Sumners <[email protected]>
1 parent e96416c commit 59b9a86

File tree

10 files changed

+133
-36
lines changed

10 files changed

+133
-36
lines changed

Readme.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,32 @@ This also shows the full structure of a configuration file, which can be loaded
178178
[tzstring]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
179179
[luxon]: https://moment.github.io/luxon/#/zones?id=specifying-a-zone
180180

181+
### Custom levels
182+
183+
Custom [Pino levels](https://github.com/pinojs/pino/blob/HEAD/docs/api.md#opt-customlevels) are supported.
184+
They must be established through a mapping defined under the `customLevels`
185+
configuration key. `customLevels` is a hash of hashes. Each key under
186+
`customLevels` is a custom level name with a value that is a hash with
187+
keys `level` and `syslogSeverity`. The `level` key maps to the log level number,
188+
and the `syslogSeverity` key to the name of a spec compliant syslog level:
189+
"emergency", "alert", "critical", "error", "warning", "notice", "info", or
190+
"debug".
191+
192+
The following example shows how to add customized levels:
193+
194+
```json
195+
{
196+
"modern": true,
197+
"appname": "none",
198+
"customLevels": {
199+
"customLevel_name": {
200+
"level": 70,
201+
"syslogSeverity": "alert"
202+
}
203+
}
204+
}
205+
```
206+
181207
## License
182208

183209
[MIT License](http://jsumners.mit-license.org/)

lib/rfc3164.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
const { DateTime } = require('luxon')
44
const stringify = require('fast-safe-stringify')
55
const through2 = require('through2')
6-
const utils = require('./utils')
76

87
function messageBuilderFactory (options) {
8+
const levelToSeverity = options.pinoLevelToSyslogSeverity
99
return function (data) {
10-
const severity = utils.levelToSeverity(data.level)
10+
const severity = levelToSeverity.get(data.level)
1111
const pri = (8 * options.facility) + severity
1212
let timestamp = DateTime.fromMillis(data.time, { zone: options.tz }).toFormat('LLL dd HH:mm:ss')
1313
if (timestamp[4] === '0') {

lib/rfc5424.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
const { DateTime } = require('luxon')
44
const stringify = require('fast-safe-stringify')
55
const through2 = require('through2')
6-
const utils = require('./utils')
76

87
function messageBuilderFactory (options) {
98
const version = 1
9+
const levelToSeverity = options.pinoLevelToSyslogSeverity
1010
return function (data) {
11-
const severity = utils.levelToSeverity(data.level)
11+
const severity = levelToSeverity.get(data.level)
1212
const pri = (8 * options.facility) + severity
1313
const tstamp = DateTime.fromMillis(data.time, { zone: options.tz })
1414
.set({ millisecond: 0 })

lib/transport.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
const build = require('pino-abstract-transport')
44
const SonicBoom = require('sonic-boom')
55
const { Transform, pipeline } = require('stream')
6-
const { defaults } = require('./utils')
6+
const { buildOptions } = require('./utils')
77
const { messageBuilderFactory: rfc5424 } = require('./rfc5424.js')
88
const { messageBuilderFactory: rfc3164 } = require('./rfc3164.js')
99

1010
module.exports = pinoTransport
1111

1212
function pinoTransport (options) {
13-
const opts = Object.assign({}, defaults, options)
14-
13+
const opts = buildOptions(options)
1514
const processMessage = opts.modern ? rfc5424(opts) : rfc3164(opts)
1615

1716
let destination

lib/utils.js

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
'use strict'
22

3+
const pinoLevels = {
4+
fatal: 60,
5+
error: 50,
6+
warn: 40,
7+
info: 30,
8+
debug: 20,
9+
trace: 10
10+
}
11+
312
const severity = {
413
emergency: 0,
514
alert: 1,
@@ -11,31 +20,19 @@ const severity = {
1120
debug: 7
1221
}
1322

14-
function levelToSeverity (level) {
15-
let result
16-
switch (level) {
17-
case 10: // pino: trace
18-
result = severity.debug
19-
break
20-
case 20: // pino: debug
21-
result = severity.debug
22-
break
23-
case 30: // pino: info
24-
result = severity.info
25-
break
26-
case 40: // pino: warn
27-
result = severity.warning
28-
break
29-
case 50: // pino: error
30-
result = severity.error
31-
break
32-
case 60: // pino: fatal
33-
default:
34-
result = severity.critical
35-
break
36-
}
37-
return result
38-
}
23+
/**
24+
* pinoLevelToSyslogSeverity is used to maintain a relationship between
25+
* known or custom pino levels and pino-syslog severity levels.
26+
*
27+
*/
28+
const pinoLevelToSyslogSeverity = new Map([
29+
[pinoLevels.trace, severity.debug],
30+
[pinoLevels.debug, severity.debug],
31+
[pinoLevels.info, severity.info],
32+
[pinoLevels.warn, severity.warning],
33+
[pinoLevels.error, severity.error],
34+
[pinoLevels.fatal, severity.critical]
35+
])
3936

4037
const defaults = {
4138
enablePipelining: true,
@@ -47,11 +44,43 @@ const defaults = {
4744
messageOnly: false,
4845
tz: 'UTC',
4946
newline: false,
50-
sync: false
47+
sync: false,
48+
pinoLevelToSyslogSeverity
49+
}
50+
51+
/**
52+
* Applies user supplied options to override internal defaults.
53+
* When the options object contains the `customLevels` property, the custom levels are added to the `pinoLevelToSyslogSeverity`
54+
* map along with their transformation into one of the syslog levels.
55+
*
56+
* When the given severity level does not exist, the default `severity.error` is used.
57+
*
58+
* @param {*} options
59+
* @param {*} args
60+
*
61+
* @returns {object} Updated options object with any defined custom levels
62+
* added to the `pinoLevelToSyslogSeverity` field.
63+
*/
64+
function buildOptions (options = {}, args = {}) {
65+
const opts = Object.assign({}, defaults, options, args)
66+
67+
if (options.customLevels == null) {
68+
return opts
69+
}
70+
71+
for (const [key, { level, syslogSeverity }] of Object.entries(options.customLevels)) {
72+
const sev = severity[syslogSeverity] || severity.error
73+
74+
pinoLevels[key] = level
75+
pinoLevelToSyslogSeverity.set(pinoLevels[key], sev)
76+
}
77+
78+
opts.pinoLevelToSyslogSeverity = pinoLevelToSyslogSeverity
79+
return opts
5180
}
5281

5382
module.exports = {
5483
severity,
55-
levelToSeverity,
84+
buildOptions,
5685
defaults
5786
}

psyslog.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const path = require('path')
55
const pump = require('pump')
66
const split2 = require('split2')
77
const parseJson = require('fast-json-parse')
8-
const { defaults } = require('./lib/utils')
8+
const { buildOptions } = require('./lib/utils')
99

1010
const longOpts = {
1111
modern: Boolean,
@@ -44,7 +44,7 @@ if (args.config) {
4444
}
4545
}
4646

47-
const options = Object.assign({}, defaults, jsonOptions, args)
47+
const options = buildOptions(jsonOptions, args)
4848

4949
let myTransport
5050
if (options.modern) {

test/3164.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ test('write synchronously', (t) => {
202202
psyslog.stdin.write(messages.helloWorld + '\n')
203203
})
204204

205+
test('sets customLevels', (t) => {
206+
t.plan(1)
207+
const expected = '<134>Apr 1 16:44:58 MacBook-Pro-3 test[94473]: hello world'
208+
const psyslog = spawn('node', [psyslogPath, '-c', configPath('3164', 'appname.json')])
209+
210+
psyslog.stdout.on('data', (data) => {
211+
const msg = data.toString()
212+
t.equal(msg, expected)
213+
psyslog.kill()
214+
})
215+
216+
psyslog.stdin.write(messages.helloWorld + '\n')
217+
})
218+
205219
function getConfigPath () {
206220
const cpath = join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments)))
207221
return require(cpath)

test/5424.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,20 @@ test('uses structured data', (t) => {
174174
psyslog.stdin.write(messages.helloWorld + '\n')
175175
})
176176

177+
test('sets customLevels', (t) => {
178+
t.plan(1)
179+
const expected = '<134>1 2016-04-01T16:44:58Z MacBook-Pro-3 test 94473 - - hello world'
180+
const psyslog = spawn('node', [psyslogPath, '-c', configPath('5424', 'custom-level.json')])
181+
182+
psyslog.stdout.on('data', (data) => {
183+
const msg = data.toString()
184+
t.equal(msg, expected)
185+
psyslog.kill()
186+
})
187+
188+
psyslog.stdin.write(messages.helloWorld + '\n')
189+
})
190+
177191
function getConfigPath () {
178192
const cpath = join.apply(null, [__dirname, 'fixtures', 'configs'].concat(Array.from(arguments)))
179193
return require(cpath)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"modern": false,
3+
"appname": "test",
4+
"messageOnly": true,
5+
"customLevels": {
6+
"unexpectedCodeBranch": { "level": 70, "syslogSeverity": "alert"}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"appname": "test",
3+
"messageOnly": true,
4+
"customLevels": {
5+
"unexpectedCodeBranch": { "level": 70, "syslogSeverity": "alert"}
6+
}
7+
}

0 commit comments

Comments
 (0)