1 | #!/usr/bin/env node
|
2 |
|
3 | const fs = require('fs')
|
4 | const args = require('args')
|
5 | const path = require('path')
|
6 | const pump = require('pump')
|
7 | const split = require('split2')
|
8 | const { Transform } = require('readable-stream')
|
9 | const prettyFactory = require('./')
|
10 | const CONSTANTS = require('./lib/constants')
|
11 | const { isObject } = require('./lib/utils')
|
12 |
|
13 | const bourne = require('@hapi/bourne')
|
14 | const stripJsonComments = require('strip-json-comments')
|
15 | const parseJSON = input => {
|
16 | return bourne.parse(stripJsonComments(input), { protoAction: 'remove' })
|
17 | }
|
18 |
|
19 | const JoyCon = require('joycon')
|
20 | const joycon = new JoyCon({
|
21 | parseJSON,
|
22 | files: [
|
23 | 'pino-pretty.config.js',
|
24 | '.pino-prettyrc',
|
25 | '.pino-prettyrc.json'
|
26 | ],
|
27 | stopDir: path.dirname(process.cwd())
|
28 | })
|
29 | joycon.addLoader({
|
30 | test: /\.[^.]*rc$/,
|
31 | loadSync: (path) => parseJSON(fs.readFileSync(path, 'utf-8'))
|
32 | })
|
33 |
|
34 | args
|
35 | .option(['c', 'colorize'], 'Force adding color sequences to the output')
|
36 | .option(['f', 'crlf'], 'Append CRLF instead of LF to formatted lines')
|
37 | .option(['e', 'errorProps'], 'Comma separated list of properties on error objects to show (`*` for all properties) (defaults to ``)')
|
38 | .option(['l', 'levelFirst'], 'Display the log level as the first output field')
|
39 | .option(['k', 'errorLikeObjectKeys'], 'Define which keys contain error objects (`-k err,error`) (defaults to `err,error`)')
|
40 | .option(['m', 'messageKey'], 'Highlight the message under the specified key', CONSTANTS.MESSAGE_KEY)
|
41 | .option('levelKey', 'Detect the log level under the specified key', CONSTANTS.LEVEL_KEY)
|
42 | .option(['b', 'levelLabel'], 'Output the log level using the specified label', CONSTANTS.LEVEL_LABEL)
|
43 | .option(['o', 'messageFormat'], 'Format output of message')
|
44 | .option(['a', 'timestampKey'], 'Display the timestamp from the specified key', CONSTANTS.TIMESTAMP_KEY)
|
45 | .option(['t', 'translateTime'], 'Display epoch timestamps as UTC ISO format or according to an optional format string (default ISO 8601)')
|
46 | .option(['s', 'search'], 'Specify a search pattern according to jmespath')
|
47 | .option(['i', 'ignore'], 'Ignore one or several keys: (`-i time,hostname`)')
|
48 | .option(['H', 'hideObject'], 'Hide objects from output (but not error object)')
|
49 | .option('config', 'specify a path to a json file containing the pino-pretty options')
|
50 |
|
51 | args
|
52 | .example('cat log | pino-pretty', 'To prettify logs, simply pipe a log file through')
|
53 | .example('cat log | pino-pretty -m fooMessage', 'To highlight a string at a key other than \'msg\'')
|
54 | .example('cat log | pino-pretty --levelKey fooLevel', 'To detect the log level at a key other than \'level\'')
|
55 | .example('cat log | pino-pretty --levelLabel LVL -o "{LVL}"', 'To output the log level label using a key other than \'levelLabel\'')
|
56 | .example('cat log | pino-pretty -a fooTimestamp', 'To display timestamp from a key other than \'time\'')
|
57 | .example('cat log | pino-pretty -t', 'To convert Epoch timestamps to ISO timestamps use the -t option')
|
58 | .example('cat log | pino-pretty -t "SYS:yyyy-mm-dd HH:MM:ss"', 'To convert Epoch timestamps to local timezone format use the -t option with "SYS:" prefixed format string')
|
59 | .example('cat log | pino-pretty -l', 'To flip level and time/date in standard output use the -l option')
|
60 | .example('cat log | pino-pretty -s "msg == \'hello world\'"', 'Only prints messages with msg equals to \'hello world\'')
|
61 | .example('cat log | pino-pretty -i pid,hostname', 'Prettify logs but don\'t print pid and hostname')
|
62 | .example('cat log | pino-pretty --config=/path/to/config.json', 'Loads options from a config file')
|
63 |
|
64 | const DEFAULT_VALUE = '\0default'
|
65 |
|
66 | let opts = args.parse(process.argv, {
|
67 | mri: {
|
68 | default: {
|
69 | messageKey: DEFAULT_VALUE,
|
70 | levelKey: DEFAULT_VALUE,
|
71 | timestampKey: DEFAULT_VALUE
|
72 | }
|
73 | }
|
74 | })
|
75 |
|
76 |
|
77 | opts = filter(opts, value => value !== DEFAULT_VALUE)
|
78 | const config = loadConfig(opts.config)
|
79 |
|
80 | opts = Object.assign({}, config, opts)
|
81 |
|
82 | opts.errorLikeObjectKeys = opts.errorLikeObjectKeys || 'err,error'
|
83 | opts.errorProps = opts.errorProps || ''
|
84 | const pretty = prettyFactory(opts)
|
85 | const prettyTransport = new Transform({
|
86 | objectMode: true,
|
87 | transform (chunk, enc, cb) {
|
88 | const line = pretty(chunk.toString())
|
89 | if (line === undefined) return cb()
|
90 | cb(null, line)
|
91 | }
|
92 | })
|
93 |
|
94 | pump(process.stdin, split(), prettyTransport, process.stdout)
|
95 |
|
96 |
|
97 |
|
98 | if (!process.stdin.isTTY && !fs.fstatSync(process.stdin.fd).isFile()) {
|
99 | process.once('SIGINT', function noOp () {})
|
100 | }
|
101 |
|
102 | function loadConfig (configPath) {
|
103 | const files = configPath ? [path.resolve(configPath)] : undefined
|
104 | const result = joycon.loadSync(files)
|
105 | if (result.path && !isObject(result.data)) {
|
106 | configPath = configPath || path.basename(result.path)
|
107 | throw new Error(`Invalid runtime configuration file: ${configPath}`)
|
108 | }
|
109 | if (configPath && !result.data) {
|
110 | throw new Error(`Failed to load runtime configuration file: ${configPath}`)
|
111 | }
|
112 | return result.data
|
113 | }
|
114 |
|
115 | function filter (obj, cb) {
|
116 | return Object.keys(obj).reduce((acc, key) => {
|
117 | const value = obj[key]
|
118 | if (cb(value, key)) {
|
119 | acc[key] = value
|
120 | }
|
121 | return acc
|
122 | }, {})
|
123 | }
|