logtunnel 0.4.0 → 1.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 +61 -0
- package/package.json +2 -2
- package/src/definition.js +3 -1
- package/src/log-source.js +1 -1
- package/src/main.js +3 -1
- package/src/pipeline.js +34 -17
- package/src/transformers/filters/field.js +18 -0
- package/src/transformers/filters/find.js +11 -0
- package/src/transformers/filters/ignore.js +11 -0
- package/src/transformers/outputs/bigodon.js +17 -0
- package/src/transformers/outputs/factory.js +21 -0
- package/src/transformers/outputs/inspect.js +22 -0
- package/src/transformers/outputs/json.js +11 -0
- package/src/transformers/outputs/logfmt.js +13 -0
- package/src/transformers/outputs/original.js +7 -0
- package/src/transformers/outputs/table.js +67 -0
- package/src/transformers/parsers/factory.js +16 -0
- package/src/transformers/parsers/json.js +16 -0
- package/src/transformers/parsers/logfmt.js +13 -0
- package/src/transformers/parsers/regex.js +11 -0
- package/src/transformers/parsers/table.js +30 -0
- package/test/filters.spec.js +40 -40
- package/test/output.spec.js +61 -22
- package/test/parse.spec.js +20 -20
- package/test/pipeline.spec.js +69 -20
- package/test/utils.js +6 -1
- package/src/transformers/field.js +0 -11
- package/src/transformers/filter.js +0 -4
- package/src/transformers/ignore.js +0 -4
- package/src/transformers/output-json.js +0 -7
- package/src/transformers/output-logfmt.js +0 -9
- package/src/transformers/output-mustache.js +0 -13
- package/src/transformers/output-original.js +0 -3
- package/src/transformers/output-unset.js +0 -12
- package/src/transformers/output.js +0 -15
- package/src/transformers/parse-json.js +0 -12
- package/src/transformers/parse-logfmt.js +0 -9
- package/src/transformers/parse-regex.js +0 -4
- package/src/transformers/parse-table.js +0 -26
- package/src/transformers/parse.js +0 -14
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Logtunnel
|
|
2
|
+
|
|
3
|
+
CLI tool that allows you to format, filter and search your log output
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- [NodeJS >= 12](https://nodejs.org/en/download/)
|
|
8
|
+
- [NPM](https://docs.npmjs.com/cli/v7/configuring-npm/install)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
``sudo npm i -g logtunnel``
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Find logs that contain "alice":
|
|
17
|
+
``curl -s https://cdn.codetunnel.net/lt/text.log | lt alice``
|
|
18
|
+
|
|
19
|
+
Find logs that contain "alice" and "purchase":
|
|
20
|
+
``curl -s https://cdn.codetunnel.net/lt/text.log | lt -f alice -f purchase``
|
|
21
|
+
|
|
22
|
+
Find logs that contain "alice" and ignore the ones that contain "info":
|
|
23
|
+
``curl -s https://cdn.codetunnel.net/lt/text.log | lt -f alice -i info``
|
|
24
|
+
|
|
25
|
+
Parse logs as JSON and output them with that template:
|
|
26
|
+
``curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o '[{{lvl}}] {{log}}'``
|
|
27
|
+
|
|
28
|
+
Parse logs as JSON, apply template and find the ones containing "alice":
|
|
29
|
+
``curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o '[{{lvl}}] {{log}}' -f alice``
|
|
30
|
+
|
|
31
|
+
Parse logs as JSON, apply template and show the ones with "delay > 200":
|
|
32
|
+
``curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o '[{{lvl}} in {{delay}}ms] {{log}}' -F 'delay > 200'``
|
|
33
|
+
|
|
34
|
+
Parse logs as JSON, apply template and show the ones with "log" containing "Alice":
|
|
35
|
+
``curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o '[{{lvl}}] {{log}}' -F 'log.toLowerCase().includes("alice")'``
|
|
36
|
+
|
|
37
|
+
Parse logs as logfmt, show the ones with "delay > 200" and show their original line (as if no parsing happened):
|
|
38
|
+
``curl -s https://cdn.codetunnel.net/lt/logfmt.log | lt -p logfmt -o original -F 'delay > 200'``
|
|
39
|
+
|
|
40
|
+
Parse logs as JSON and output them as a table:
|
|
41
|
+
``curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o table``
|
|
42
|
+
|
|
43
|
+
Parse logs with regex, and output in logfmt:
|
|
44
|
+
``curl -s https://cdn.codetunnel.net/lt/text.log | lt -p '\[(?<lvl>\S*) in\s*(?<delay>\d*)ms\] (?<log>.*)' -o logfmt``
|
|
45
|
+
|
|
46
|
+
Parse logs with regex, and show the ones with "delay > 200":
|
|
47
|
+
``curl -s https://cdn.codetunnel.net/lt/text.log | lt -p '(?<delay>\d+)ms' -o original -F 'delay > 200'``
|
|
48
|
+
|
|
49
|
+
Parse table and show rows containing "cilium":
|
|
50
|
+
``curl -s https://cdn.codetunnel.net/lt/table.log | lt -p table -o original -f cilium``
|
|
51
|
+
|
|
52
|
+
Parse table, show rows containing "cilium" and the first headers row:
|
|
53
|
+
``curl -s https://cdn.codetunnel.net/lt/table.log | lt -p table -o original -f cilium -H``
|
|
54
|
+
|
|
55
|
+
Parse table, show rows with RESTARTS > 0:
|
|
56
|
+
``curl -s https://cdn.codetunnel.net/lt/table.log | lt -p table -o original -F 'RESTARTS > 0' -H``
|
|
57
|
+
|
|
58
|
+
Show rows that are not ready:
|
|
59
|
+
``curl -s https://cdn.codetunnel.net/lt/table.log | lt -p '(?<up>\d)/(?<total>\d)' -o original -F 'up < total' -H``
|
|
60
|
+
|
|
61
|
+
For more information ``lt --help``
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logtunnel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "CLI tool that allows you to format, filter and search your log output",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"@hapi/bossy": "^5.1.0",
|
|
35
35
|
"@hapi/hoek": "^9.2.0",
|
|
36
36
|
"@hapi/podium": "^4.1.3",
|
|
37
|
+
"bigodon": "^2.3.0",
|
|
37
38
|
"colors": "^1.4.0",
|
|
38
39
|
"debug": "^4.3.2",
|
|
39
|
-
"jstache": "^0.1.0",
|
|
40
40
|
"logfmt": "^1.3.2",
|
|
41
41
|
"semver": "^7.3.5"
|
|
42
42
|
}
|
package/src/definition.js
CHANGED
|
@@ -29,7 +29,7 @@ const definition = {
|
|
|
29
29
|
type: 'string',
|
|
30
30
|
},
|
|
31
31
|
o: {
|
|
32
|
-
description: 'Formats the output using this template. Allowed: json, logfmt, original or a
|
|
32
|
+
description: 'Formats the output using this template. Allowed: json, logfmt, inspect, original, table or a bigodon template.',
|
|
33
33
|
alias: 'output',
|
|
34
34
|
type: 'string',
|
|
35
35
|
},
|
|
@@ -65,6 +65,8 @@ const examples = [
|
|
|
65
65
|
$ + 'curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o \'[{{lvl}}] {{log}}\' -F \'log.toLowerCase().includes("alice")\'',
|
|
66
66
|
'Parse logs as logfmt, show the ones with "delay > 200" and show their original line (as if no parsing happened)'.dim,
|
|
67
67
|
$ + 'curl -s https://cdn.codetunnel.net/lt/logfmt.log | lt -p logfmt -o original -F \'delay > 200\'',
|
|
68
|
+
'Parse logs as JSON and output them as a table'.dim,
|
|
69
|
+
$ + 'curl -s https://cdn.codetunnel.net/lt/json.log | lt -p json -o table',
|
|
68
70
|
'Parse logs with regex, and output in logfmt'.dim,
|
|
69
71
|
$ + 'curl -s https://cdn.codetunnel.net/lt/text.log | lt -p \'\\[(?<lvl>\\S*) in\\s*(?<delay>\\d*)ms\\] (?<log>.*)\' -o logfmt',
|
|
70
72
|
'Parse logs with regex, and show the ones with "delay > 200"'.dim,
|
package/src/log-source.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports.logSource = emitter => {
|
|
|
7
7
|
emitter.on('data', data => {
|
|
8
8
|
const lines = data.toString()
|
|
9
9
|
.split(/[\r\n|\n]/);
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
lines[0] = incompleteLine + lines[0];
|
|
12
12
|
incompleteLine = lines.pop(); // Either an incomplete line or an empty string due to the last \n
|
|
13
13
|
|
package/src/main.js
CHANGED
|
@@ -32,7 +32,9 @@ function run() {
|
|
|
32
32
|
debug('building pipeline');
|
|
33
33
|
const pipeline = new LogPipeline(args, process.stdout);
|
|
34
34
|
debug('registering stdin');
|
|
35
|
-
logSource(process.stdin)
|
|
35
|
+
logSource(process.stdin)
|
|
36
|
+
.on('log-line', l => pipeline.onLogLine(l))
|
|
37
|
+
.on('end', () => pipeline.onEnd());
|
|
36
38
|
} catch(e) {
|
|
37
39
|
console.error('Error:', e.message);
|
|
38
40
|
process.exit(1);
|
package/src/pipeline.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const debug = require('debug')('logtunnel:pipeline');
|
|
3
2
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
3
|
+
const { FieldFilter } = require('./transformers/filters/field');
|
|
4
|
+
const { FindFilter } = require('./transformers/filters/find');
|
|
5
|
+
const { IgnoreFilter } = require('./transformers/filters/ignore');
|
|
6
|
+
const { outputFactory } = require('./transformers/outputs/factory');
|
|
7
|
+
const { parseFactory } = require('./transformers/parsers/factory');
|
|
8
|
+
|
|
9
|
+
const debug = require('debug')('logtunnel:pipeline');
|
|
9
10
|
|
|
10
11
|
class LogPipeline {
|
|
11
12
|
constructor(args, stdout) {
|
|
12
13
|
this.firstLine = null;
|
|
13
14
|
this.args = args;
|
|
14
15
|
this.stdout = stdout;
|
|
15
|
-
this.
|
|
16
|
+
this.outputTransformer = outputFactory(this.args.output);
|
|
17
|
+
this.isOutputBuffered = Boolean(this.outputTransformer.flush);
|
|
18
|
+
this.transformers = this._buildTransformers()
|
|
19
|
+
.filter(t => t !== null);
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
onLogLine(line) {
|
|
@@ -29,11 +33,11 @@ class LogPipeline {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
_logLine(line) {
|
|
36
|
+
async _logLine(line) {
|
|
33
37
|
let output = line;
|
|
34
38
|
|
|
35
39
|
for (let transformer of this.transformers) {
|
|
36
|
-
const result = transformer(output, line, this);
|
|
40
|
+
const result = await transformer.run(output, line, this);
|
|
37
41
|
|
|
38
42
|
// Transformer accepted the line
|
|
39
43
|
if(result === true) {
|
|
@@ -49,7 +53,11 @@ class LogPipeline {
|
|
|
49
53
|
|
|
50
54
|
// Transformer modified the line
|
|
51
55
|
output = result;
|
|
52
|
-
debug('line transformed: ' + JSON.stringify(output));
|
|
56
|
+
debug('line transformed: ' + JSON.stringify(output));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.isOutputBuffered) {
|
|
60
|
+
return;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
this.stdout.write(output + '\n');
|
|
@@ -61,23 +69,32 @@ class LogPipeline {
|
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
this.firstLine = line;
|
|
64
|
-
if(this.args.headers) {
|
|
72
|
+
if(this.args.headers && !this.isOutputBuffered) {
|
|
65
73
|
this.stdout.write(this.firstLine + '\n');
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
onEnd() {
|
|
78
|
+
if (!this.isOutputBuffered) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const lines = this.outputTransformer.flush();
|
|
83
|
+
lines.forEach(line => this.stdout.write(line + '\n'));
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
_buildTransformers() {
|
|
70
87
|
return [
|
|
71
88
|
// First of all, filter which lines to accept
|
|
72
|
-
this.args._ ?
|
|
73
|
-
...this.args.filter.map(
|
|
74
|
-
...this.args.ignore.map(
|
|
89
|
+
this.args._ ? new FindFilter(this.args._) : null,
|
|
90
|
+
...this.args.filter.map(f => new FindFilter(f)),
|
|
91
|
+
...this.args.ignore.map(i => new IgnoreFilter(i)),
|
|
75
92
|
// Parse them...
|
|
76
|
-
|
|
93
|
+
parseFactory(this.args.parser),
|
|
77
94
|
// ...and apply field filters
|
|
78
|
-
...this.args.field.map(
|
|
95
|
+
...this.args.field.map(f => new FieldFilter(f)),
|
|
79
96
|
// And finally format the output
|
|
80
|
-
|
|
97
|
+
this.outputTransformer,
|
|
81
98
|
];
|
|
82
99
|
}
|
|
83
100
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { compileExpression } = require('bigodon');
|
|
2
|
+
|
|
3
|
+
class FieldFilter {
|
|
4
|
+
constructor(expression) {
|
|
5
|
+
this.expression = compileExpression(expression);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async run(line) {
|
|
9
|
+
if(typeof line !== 'object') {
|
|
10
|
+
throw new Error("To use a field filter, you need to specify a parser like '-p json' ");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result = await this.expression(line);
|
|
14
|
+
return Boolean(result);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { FieldFilter };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { compile } = require('bigodon');
|
|
2
|
+
|
|
3
|
+
class BigodonOutput {
|
|
4
|
+
constructor(template) {
|
|
5
|
+
this.template = compile(template);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async run(line) {
|
|
9
|
+
if (typeof line !== 'object') {
|
|
10
|
+
throw new Error("To use an output transformer, you need to specify a parser like '-p json' ");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return this.template(line);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { BigodonOutput };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { JsonOutput } = require('./json');
|
|
2
|
+
const { LogfmtOutput } = require('./logfmt');
|
|
3
|
+
const { BigodonOutput } = require('./bigodon');
|
|
4
|
+
const { OriginalOutput } = require('./original');
|
|
5
|
+
const { InspectOutput } = require('./inspect');
|
|
6
|
+
const { TableOutput } = require('./table');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function outputFactory(format) {
|
|
10
|
+
switch(format?.toLowerCase()) {
|
|
11
|
+
case undefined: return new InspectOutput(false);
|
|
12
|
+
case 'json': return new JsonOutput();
|
|
13
|
+
case 'table': return new TableOutput();
|
|
14
|
+
case 'logfmt': return new LogfmtOutput();
|
|
15
|
+
case 'inspect': return new InspectOutput(true);
|
|
16
|
+
case 'original': return new OriginalOutput();
|
|
17
|
+
default: return new BigodonOutput(format);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { outputFactory };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const util = require('util');
|
|
2
|
+
|
|
3
|
+
class InspectOutput {
|
|
4
|
+
constructor(breakLines = false) {
|
|
5
|
+
this.options = {
|
|
6
|
+
colors: true,
|
|
7
|
+
depth: null,
|
|
8
|
+
breakLength: breakLines ? 80 : Infinity,
|
|
9
|
+
compact: !breakLines,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
run(line) {
|
|
14
|
+
if (typeof line === 'string') {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return util.inspect({ ...line }, this.options);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { InspectOutput };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const logfmt = require('logfmt');
|
|
2
|
+
|
|
3
|
+
class LogfmtOutput {
|
|
4
|
+
run(line) {
|
|
5
|
+
if (typeof line !== 'object') {
|
|
6
|
+
throw new Error("To use an output transformer, you need to specify a parser like '-p json'");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return logfmt.stringify(line);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { LogfmtOutput };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
class TableOutput {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.rows = [];
|
|
4
|
+
this.columns = [];
|
|
5
|
+
this.columnWidths = new Map();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
run(line) {
|
|
9
|
+
if (typeof line !== 'object') {
|
|
10
|
+
throw new Error("To use an output transformer, you need to specify a parser like '-p json'");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.rows.push(line);
|
|
14
|
+
|
|
15
|
+
Object.keys(line).forEach(column => {
|
|
16
|
+
if (!this.columnWidths.has(column)) {
|
|
17
|
+
this.columns.push(column);
|
|
18
|
+
this.columnWidths.set(column, column.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cell = this._toCell(line[column]);
|
|
22
|
+
const width = this.columnWidths.get(column);
|
|
23
|
+
if (cell.length > width) {
|
|
24
|
+
this.columnWidths.set(column, cell.length);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
flush() {
|
|
31
|
+
if (this.rows.length === 0) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const columns = this.columns;
|
|
36
|
+
const widths = columns.map(column => this.columnWidths.get(column));
|
|
37
|
+
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push(this._formatRow(columns, widths));
|
|
40
|
+
this.rows.forEach(row => {
|
|
41
|
+
const cells = columns.map(column => this._toCell(row[column]));
|
|
42
|
+
lines.push(this._formatRow(cells, widths));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_toCell(value) {
|
|
49
|
+
if (value === null || value === undefined) {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'object') {
|
|
53
|
+
return JSON.stringify(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return String(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_formatRow(cells, widths) {
|
|
60
|
+
return cells
|
|
61
|
+
.map((cell, index) => cell.padEnd(widths[index]))
|
|
62
|
+
.join(' ')
|
|
63
|
+
.trimEnd();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { TableOutput };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { JsonParse } = require("./json");
|
|
2
|
+
const { LogfmtParse } = require("./logfmt");
|
|
3
|
+
const { TableParse } = require("./table");
|
|
4
|
+
const { RegexParse } = require("./regex");
|
|
5
|
+
|
|
6
|
+
function parseFactory(format) {
|
|
7
|
+
switch(format?.toLowerCase()) {
|
|
8
|
+
case void 0: return null;
|
|
9
|
+
case 'json': return new JsonParse();
|
|
10
|
+
case 'logfmt': return new LogfmtParse();
|
|
11
|
+
case 'table': return new TableParse();
|
|
12
|
+
default: return new RegexParse(format);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { parseFactory };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class TableParse {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.headers = null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
run(line, _original, pipeline) {
|
|
7
|
+
if (!pipeline.firstLine) {
|
|
8
|
+
// Ignore first line, it's the headers
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (!this.headers) {
|
|
12
|
+
this.headers = this._splitColumns(pipeline.firstLine);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const columns = this._splitColumns(line);
|
|
16
|
+
const obj = {};
|
|
17
|
+
|
|
18
|
+
this.headers.forEach((header, i) => {
|
|
19
|
+
obj[header] = columns[i];
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_splitColumns(line) {
|
|
26
|
+
return line.replace(/\s+/g, ' ').split(' ');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { TableParse };
|
package/test/filters.spec.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const Lab = require('@hapi/lab');
|
|
2
2
|
const Code = require('@hapi/code');
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const { FindFilter } = require('../src/transformers/filters/find');
|
|
5
|
+
const { IgnoreFilter } = require('../src/transformers/filters/ignore');
|
|
6
|
+
const { FieldFilter } = require('../src/transformers/filters/field');
|
|
7
7
|
|
|
8
8
|
const { describe, it } = exports.lab = Lab.script();
|
|
9
9
|
const { expect } = Code;
|
|
@@ -11,76 +11,76 @@ const { expect } = Code;
|
|
|
11
11
|
describe('filters', () => {
|
|
12
12
|
describe('--filter', () => {
|
|
13
13
|
it('should accept matching strings', () => {
|
|
14
|
-
const transformer =
|
|
15
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
14
|
+
const transformer = new FindFilter('psu');
|
|
15
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
16
16
|
expect(result).to.be.true();
|
|
17
17
|
});
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
it('should reject non-matching strings', () => {
|
|
20
|
-
const transformer =
|
|
21
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
20
|
+
const transformer = new FindFilter('aaa');
|
|
21
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
22
22
|
expect(result).to.be.false();
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
it('should be case insensitive', () => {
|
|
26
|
-
const transformer =
|
|
27
|
-
const result = transformer('lorem ipSUm dolor sit amet');
|
|
26
|
+
const transformer = new FindFilter('PsU');
|
|
27
|
+
const result = transformer.run('lorem ipSUm dolor sit amet');
|
|
28
28
|
expect(result).to.be.true();
|
|
29
29
|
});
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
it('should support regex', () => {
|
|
32
|
-
const transformer =
|
|
33
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
32
|
+
const transformer = new FindFilter('i.*m');
|
|
33
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
34
34
|
expect(result).to.be.true();
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
describe('--ignore', () => {
|
|
38
38
|
it('should reject matching strings', () => {
|
|
39
|
-
const transformer =
|
|
40
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
39
|
+
const transformer = new IgnoreFilter('psu');
|
|
40
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
41
41
|
expect(result).to.be.false();
|
|
42
42
|
});
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
it('should accept non-matching strings', () => {
|
|
45
|
-
const transformer =
|
|
46
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
45
|
+
const transformer = new IgnoreFilter('aaa');
|
|
46
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
47
47
|
expect(result).to.be.true();
|
|
48
48
|
});
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
it('should be case insensitive', () => {
|
|
51
|
-
const transformer =
|
|
52
|
-
const result = transformer('lorem ipSUm dolor sit amet');
|
|
51
|
+
const transformer = new IgnoreFilter('PsU');
|
|
52
|
+
const result = transformer.run('lorem ipSUm dolor sit amet');
|
|
53
53
|
expect(result).to.be.false();
|
|
54
54
|
});
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
it('should support regex', () => {
|
|
57
|
-
const transformer =
|
|
58
|
-
const result = transformer('lorem ipsum dolor sit amet');
|
|
57
|
+
const transformer = new IgnoreFilter('i.*m');
|
|
58
|
+
const result = transformer.run('lorem ipsum dolor sit amet');
|
|
59
59
|
expect(result).to.be.false();
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
describe('--field', () => {
|
|
63
|
-
it('should error when strings wasn\'t parsed into object', () => {
|
|
64
|
-
const transformer =
|
|
65
|
-
expect(
|
|
63
|
+
it('should error when strings wasn\'t parsed into object', async () => {
|
|
64
|
+
const transformer = new FieldFilter('a');
|
|
65
|
+
expect(transformer.run('a')).to.reject();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('should compare equality', () => {
|
|
69
|
-
const transformer =
|
|
70
|
-
expect(transformer({ foo: 'bar' })).to.be.true();
|
|
71
|
-
expect(transformer({ foo: 'baz' })).to.be.false();
|
|
68
|
+
it('should compare equality', async () => {
|
|
69
|
+
const transformer = new FieldFilter('eq foo "bar"');
|
|
70
|
+
expect(await transformer.run({ foo: 'bar' })).to.be.true();
|
|
71
|
+
expect(await transformer.run({ foo: 'baz' })).to.be.false();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('should compare inequality with strings', () => {
|
|
75
|
-
const transformer =
|
|
76
|
-
expect(transformer({ foo: '3' })).to.be.true();
|
|
77
|
-
expect(transformer({ foo: '2' })).to.be.false();
|
|
74
|
+
it('should compare inequality with strings', async () => {
|
|
75
|
+
const transformer = new FieldFilter('gte foo 3');
|
|
76
|
+
expect(await transformer.run({ foo: '3' })).to.be.true();
|
|
77
|
+
expect(await transformer.run({ foo: '2' })).to.be.false();
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
it('should accept javascript', () => {
|
|
81
|
-
const transformer =
|
|
82
|
-
expect(transformer({ foo: 'ABARA' })).to.be.true();
|
|
83
|
-
expect(transformer({ foo: 'ABADA' })).to.be.false();
|
|
80
|
+
it('should accept javascript', async () => {
|
|
81
|
+
const transformer = new FieldFilter('includes (lower foo) "bar"');
|
|
82
|
+
expect(await transformer.run({ foo: 'ABARA' })).to.be.true();
|
|
83
|
+
expect(await transformer.run({ foo: 'ABADA' })).to.be.false();
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
});
|
package/test/output.spec.js
CHANGED
|
@@ -1,57 +1,96 @@
|
|
|
1
1
|
const Lab = require('@hapi/lab');
|
|
2
2
|
const Code = require('@hapi/code');
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const { outputFactory } = require('../src/transformers/outputs/factory');
|
|
5
5
|
|
|
6
6
|
const { describe, it } = exports.lab = Lab.script();
|
|
7
7
|
const { expect } = Code;
|
|
8
8
|
|
|
9
9
|
describe('outputs', () => {
|
|
10
10
|
it('should should not modify unparsed when unset', () => {
|
|
11
|
-
const format =
|
|
12
|
-
expect(format('foo')).to.be.true();
|
|
11
|
+
const format = outputFactory();
|
|
12
|
+
expect(format.run('foo')).to.be.true();
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('should
|
|
16
|
-
const format =
|
|
17
|
-
const line = format({ foo: 'bar' });
|
|
15
|
+
it('should inspect parsed lines single-line when unset', () => {
|
|
16
|
+
const format = outputFactory();
|
|
17
|
+
const line = format.run({ foo: 'bar' });
|
|
18
18
|
expect(line).to.be.a.string();
|
|
19
19
|
expect(line).to.match(/\{.*foo.*:.*bar.*\}/);
|
|
20
|
+
expect(line.split('\n').length).to.equal(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should inspect parsed lines multi-line', () => {
|
|
24
|
+
const format = outputFactory('inspect');
|
|
25
|
+
const line = format.run({ foo: 'bar' });
|
|
26
|
+
expect(line).to.be.a.string();
|
|
27
|
+
expect(line.split('\n').join(' ')).to.match(/\{.*foo.*:.*bar.*\}/);
|
|
28
|
+
expect(line.split('\n').length).to.equal(3);
|
|
20
29
|
});
|
|
21
30
|
|
|
22
31
|
it('should format as json', () => {
|
|
23
|
-
const format =
|
|
24
|
-
const line = format({ foo: 'bar' });
|
|
32
|
+
const format = outputFactory('json');
|
|
33
|
+
const line = format.run({ foo: 'bar' });
|
|
25
34
|
expect(line).to.equal('{"foo":"bar"}');
|
|
26
35
|
});
|
|
27
36
|
|
|
28
37
|
it('should format as logfmt', () => {
|
|
29
|
-
const format =
|
|
30
|
-
const line = format({ foo: 'bar' });
|
|
38
|
+
const format = outputFactory('logfmt');
|
|
39
|
+
const line = format.run({ foo: 'bar' });
|
|
31
40
|
expect(line).to.equal('foo=bar');
|
|
32
41
|
});
|
|
33
42
|
|
|
34
43
|
it('should format as original', () => {
|
|
35
|
-
const format =
|
|
36
|
-
const line = format({ foo: 'bar' }, 'fubá');
|
|
44
|
+
const format = outputFactory('original');
|
|
45
|
+
const line = format.run({ foo: 'bar' }, 'fubá');
|
|
37
46
|
expect(line).to.equal('fubá');
|
|
38
47
|
});
|
|
39
48
|
|
|
40
|
-
it('should format
|
|
41
|
-
const format =
|
|
42
|
-
|
|
49
|
+
it('should format as table', () => {
|
|
50
|
+
const format = outputFactory('table');
|
|
51
|
+
format.run({ foo: 'bar', baz: 'qux' });
|
|
52
|
+
format.run({ foo: 'longer' });
|
|
53
|
+
const lines = format.flush();
|
|
54
|
+
|
|
55
|
+
expect(lines).to.equal([
|
|
56
|
+
'foo baz',
|
|
57
|
+
'bar qux',
|
|
58
|
+
'longer',
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return empty table output when no rows were provided', () => {
|
|
63
|
+
const format = outputFactory('table');
|
|
64
|
+
expect(format.flush()).to.equal([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should stringify object and null table cells', () => {
|
|
68
|
+
const format = outputFactory('table');
|
|
69
|
+
format.run({ foo: null, bar: { baz: 1 } });
|
|
70
|
+
const lines = format.flush();
|
|
71
|
+
|
|
72
|
+
expect(lines).to.equal([
|
|
73
|
+
'foo bar',
|
|
74
|
+
' {"baz":1}',
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should format with bigodon', async () => {
|
|
79
|
+
const format = outputFactory('[{{foo}}]');
|
|
80
|
+
const line = await format.run({ foo: 'bar' });
|
|
43
81
|
expect(line).to.equal('[bar]');
|
|
44
82
|
});
|
|
45
83
|
|
|
46
|
-
it('should allow
|
|
47
|
-
const format =
|
|
48
|
-
const line = format({ foo: 'bar' });
|
|
84
|
+
it('should allow bigodon helpers', async () => {
|
|
85
|
+
const format = outputFactory('[{{upper foo }}]');
|
|
86
|
+
const line = await format.run({ foo: 'bar' });
|
|
49
87
|
expect(line).to.equal('[BAR]');
|
|
50
88
|
});
|
|
51
89
|
|
|
52
|
-
it('should refuse to format unparsed lines', () => {
|
|
53
|
-
expect(() =>
|
|
54
|
-
expect(() =>
|
|
55
|
-
expect(() =>
|
|
90
|
+
it('should refuse to format unparsed lines', async () => {
|
|
91
|
+
expect(() => outputFactory('json').run('foo')).to.throw();
|
|
92
|
+
expect(() => outputFactory('logfmt').run('foo')).to.throw();
|
|
93
|
+
expect(() => outputFactory('table').run('foo')).to.throw();
|
|
94
|
+
await expect(outputFactory('{{foo}}').run('foo')).to.reject();
|
|
56
95
|
});
|
|
57
96
|
});
|
package/test/parse.spec.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
const Lab = require('@hapi/lab');
|
|
2
2
|
const Code = require('@hapi/code');
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const { parseFactory } = require('../src/transformers/parsers/factory');
|
|
5
5
|
|
|
6
6
|
const { describe, it } = exports.lab = Lab.script();
|
|
7
7
|
const { expect } = Code;
|
|
8
8
|
|
|
9
9
|
describe('parsers', () => {
|
|
10
10
|
it('should return null when no format is specified', () => {
|
|
11
|
-
expect(
|
|
11
|
+
expect(parseFactory(void 0)).to.be.null();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('should parse json', () => {
|
|
15
|
-
const parser =
|
|
16
|
-
expect(parser('{"foo": "bar"}')).to.equal({ foo: 'bar' });
|
|
15
|
+
const parser = parseFactory('json');
|
|
16
|
+
expect(parser.run('{"foo": "bar"}')).to.equal({ foo: 'bar' });
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it('should ignore invalid json lines when parsing json', () => {
|
|
20
|
-
const parser =
|
|
21
|
-
expect(parser('Oy')).to.be.false();
|
|
22
|
-
expect(parser('"Oy"')).to.be.false();
|
|
20
|
+
const parser = parseFactory('json');
|
|
21
|
+
expect(parser.run('Oy')).to.be.false();
|
|
22
|
+
expect(parser.run('"Oy"')).to.be.false();
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should parse logfmt', () => {
|
|
26
|
-
const parser =
|
|
27
|
-
expect(parser('foo=bar')).to.equal({ foo: 'bar' });
|
|
26
|
+
const parser = parseFactory('logfmt');
|
|
27
|
+
expect(parser.run('foo=bar')).to.equal({ foo: 'bar' });
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('should ignore invalid logfmt lines when parsing logfmt', () => {
|
|
31
|
-
const parser =
|
|
32
|
-
expect(parser(void 0)).to.be.false();
|
|
31
|
+
const parser = parseFactory('logfmt');
|
|
32
|
+
expect(parser.run(void 0)).to.be.false();
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
it('should parse regex', () => {
|
|
36
|
-
const parser =
|
|
37
|
-
expect(parser('[info 20ms] lorem ipsum dolor')).to.equal({
|
|
36
|
+
const parser = parseFactory('\\[(?<severity>\\S+)\\s*(?<delay>\\d+)ms\\] (?<message>.*)');
|
|
37
|
+
expect(parser.run('[info 20ms] lorem ipsum dolor')).to.equal({
|
|
38
38
|
severity: 'info',
|
|
39
39
|
delay: '20',
|
|
40
40
|
message: 'lorem ipsum dolor',
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
expect(parser('foo')).to.be.false();
|
|
43
|
+
expect(parser.run('foo')).to.be.false();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
describe('tables', () => {
|
|
@@ -52,21 +52,21 @@ describe('parsers', () => {
|
|
|
52
52
|
|
|
53
53
|
it('should ignore headers', () => {
|
|
54
54
|
const pipeline = { firstLine: null };
|
|
55
|
-
const parser =
|
|
55
|
+
const parser = parseFactory('table');
|
|
56
56
|
const line = tableRows[0];
|
|
57
|
-
|
|
58
|
-
expect(parser(line, line, pipeline)).to.be.false();
|
|
57
|
+
|
|
58
|
+
expect(parser.run(line, line, pipeline)).to.be.false();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('should parse subsequent rows', () => {
|
|
62
62
|
const pipeline = { firstLine: tableRows[0] };
|
|
63
|
-
const parser =
|
|
63
|
+
const parser = parseFactory('table');
|
|
64
64
|
let line;
|
|
65
65
|
|
|
66
66
|
line = tableRows[1];
|
|
67
|
-
expect(parser(line, line, pipeline)).to.equal({ NAME: 'foo', TYPE: 'bar' });
|
|
67
|
+
expect(parser.run(line, line, pipeline)).to.equal({ NAME: 'foo', TYPE: 'bar' });
|
|
68
68
|
line = tableRows[2];
|
|
69
|
-
expect(parser(line, line, pipeline)).to.equal({ NAME: 'baz', TYPE: 'qux' });
|
|
69
|
+
expect(parser.run(line, line, pipeline)).to.equal({ NAME: 'baz', TYPE: 'qux' });
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
});
|
package/test/pipeline.spec.js
CHANGED
|
@@ -6,11 +6,11 @@ const { describe, it } = exports.lab = Lab.script();
|
|
|
6
6
|
const { expect } = Code;
|
|
7
7
|
|
|
8
8
|
describe('pipeline', () => {
|
|
9
|
-
it('should remove rejected lines and keep accepted ones', () => {
|
|
9
|
+
it('should remove rejected lines and keep accepted ones', async () => {
|
|
10
10
|
const args = [
|
|
11
11
|
f('potato')
|
|
12
12
|
];
|
|
13
|
-
const actual = runPipeline([
|
|
13
|
+
const actual = await runPipeline([
|
|
14
14
|
'yada yada yada',
|
|
15
15
|
'lorem ipsum POTATO dolor',
|
|
16
16
|
'ablueblue POTATO',
|
|
@@ -24,13 +24,13 @@ describe('pipeline', () => {
|
|
|
24
24
|
expect(actual).to.equal(expected);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
it('should apply multiple filters', () => {
|
|
27
|
+
it('should apply multiple filters', async () => {
|
|
28
28
|
const args = [
|
|
29
29
|
f('foo'),
|
|
30
30
|
f('bar'),
|
|
31
31
|
i('nope'),
|
|
32
32
|
];
|
|
33
|
-
const actual = runPipeline([
|
|
33
|
+
const actual = await runPipeline([
|
|
34
34
|
'1. foo',
|
|
35
35
|
'2. foo bar nope',
|
|
36
36
|
'3. foo bar',
|
|
@@ -43,12 +43,12 @@ describe('pipeline', () => {
|
|
|
43
43
|
expect(actual).to.equal(expected);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it('should apply default argument as filter', () => {
|
|
46
|
+
it('should apply default argument as filter', async () => {
|
|
47
47
|
const args = [
|
|
48
48
|
_('foo'),
|
|
49
49
|
i('bar'),
|
|
50
50
|
];
|
|
51
|
-
const actual = runPipeline([
|
|
51
|
+
const actual = await runPipeline([
|
|
52
52
|
'1. foo',
|
|
53
53
|
'2. foo bar',
|
|
54
54
|
'3. baz',
|
|
@@ -60,12 +60,12 @@ describe('pipeline', () => {
|
|
|
60
60
|
expect(actual).to.equal(expected);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it('should parse and format correctly', () => {
|
|
63
|
+
it('should parse and format correctly', async () => {
|
|
64
64
|
const args = [
|
|
65
65
|
p('logfmt'),
|
|
66
66
|
o('json'),
|
|
67
67
|
];
|
|
68
|
-
const actual = runPipeline([
|
|
68
|
+
const actual = await runPipeline([
|
|
69
69
|
'foo=bar baz=qux',
|
|
70
70
|
], args);
|
|
71
71
|
const expected = [
|
|
@@ -75,13 +75,31 @@ describe('pipeline', () => {
|
|
|
75
75
|
expect(actual).to.equal(expected);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it('should
|
|
78
|
+
it('should buffer and format tables when requested', async () => {
|
|
79
|
+
const args = [
|
|
80
|
+
p('logfmt'),
|
|
81
|
+
o('table'),
|
|
82
|
+
];
|
|
83
|
+
const actual = await runPipeline([
|
|
84
|
+
'foo=bar baz=qux',
|
|
85
|
+
'foo=longer',
|
|
86
|
+
], args);
|
|
87
|
+
const expected = [
|
|
88
|
+
'foo baz',
|
|
89
|
+
'bar qux',
|
|
90
|
+
'longer',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
expect(actual).to.equal(expected);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should filter by the original line even when parsers and outputs are provided', async () => {
|
|
79
97
|
const args = [
|
|
80
98
|
p('logfmt'),
|
|
81
99
|
o('json'),
|
|
82
100
|
i('o=n'),
|
|
83
101
|
];
|
|
84
|
-
const actual = runPipeline([
|
|
102
|
+
const actual = await runPipeline([
|
|
85
103
|
'foo=bar baz=qux',
|
|
86
104
|
'foo=nope baz=qux',
|
|
87
105
|
], args);
|
|
@@ -92,13 +110,13 @@ describe('pipeline', () => {
|
|
|
92
110
|
expect(actual).to.equal(expected);
|
|
93
111
|
});
|
|
94
112
|
|
|
95
|
-
it('should apply field filters after parse', () => {
|
|
113
|
+
it('should apply field filters after parse', async () => {
|
|
96
114
|
const args = [
|
|
97
115
|
p('logfmt'),
|
|
98
116
|
o('json'),
|
|
99
|
-
F('foo
|
|
117
|
+
F('startsWith foo "b"'),
|
|
100
118
|
];
|
|
101
|
-
const actual = runPipeline([
|
|
119
|
+
const actual = await runPipeline([
|
|
102
120
|
'foo=bar baz=qux',
|
|
103
121
|
'foo=nope baz=qux',
|
|
104
122
|
], args);
|
|
@@ -109,14 +127,14 @@ describe('pipeline', () => {
|
|
|
109
127
|
expect(actual).to.equal(expected);
|
|
110
128
|
});
|
|
111
129
|
|
|
112
|
-
it('should apply multiple field filters', () => {
|
|
130
|
+
it('should apply multiple field filters', async () => {
|
|
113
131
|
const args = [
|
|
114
132
|
p('logfmt'),
|
|
115
133
|
o('json'),
|
|
116
|
-
F('foo
|
|
117
|
-
F('foo
|
|
134
|
+
F('startsWith foo "b"'),
|
|
135
|
+
F('endsWith foo "r"'),
|
|
118
136
|
];
|
|
119
|
-
const actual = runPipeline([
|
|
137
|
+
const actual = await runPipeline([
|
|
120
138
|
'foo=baz baz=qux',
|
|
121
139
|
'foo=bar baz=qux',
|
|
122
140
|
'foo=tar baz=qux',
|
|
@@ -128,14 +146,14 @@ describe('pipeline', () => {
|
|
|
128
146
|
expect(actual).to.equal(expected);
|
|
129
147
|
});
|
|
130
148
|
|
|
131
|
-
it('should log headers when -H is provided', () => {
|
|
149
|
+
it('should log headers when -H is provided', async () => {
|
|
132
150
|
const args = [
|
|
133
151
|
p('table'),
|
|
134
152
|
o('original'),
|
|
135
|
-
F('NAME
|
|
153
|
+
F('eq NAME "foo"'),
|
|
136
154
|
H(),
|
|
137
155
|
];
|
|
138
|
-
const actual = runPipeline([
|
|
156
|
+
const actual = await runPipeline([
|
|
139
157
|
'NAME',
|
|
140
158
|
'foo',
|
|
141
159
|
'bar',
|
|
@@ -147,4 +165,35 @@ describe('pipeline', () => {
|
|
|
147
165
|
|
|
148
166
|
expect(actual).to.equal(expected);
|
|
149
167
|
});
|
|
168
|
+
|
|
169
|
+
it('should log headers only once when both -H and -o table is provided', async () => {
|
|
170
|
+
const args = [
|
|
171
|
+
p('table'),
|
|
172
|
+
o('table'),
|
|
173
|
+
F('eq NAME "foo"'),
|
|
174
|
+
H(),
|
|
175
|
+
];
|
|
176
|
+
const actual = await runPipeline([
|
|
177
|
+
'NAME',
|
|
178
|
+
'foo',
|
|
179
|
+
'bar',
|
|
180
|
+
], args);
|
|
181
|
+
const expected = [
|
|
182
|
+
'NAME',
|
|
183
|
+
'foo',
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
expect(actual).to.equal(expected);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should not specify null prototype of regex parser', async () => {
|
|
190
|
+
const args = [
|
|
191
|
+
p('(?<num>\\d*)'),
|
|
192
|
+
];
|
|
193
|
+
const [actual] = await runPipeline([
|
|
194
|
+
'12',
|
|
195
|
+
], args);
|
|
196
|
+
|
|
197
|
+
expect(actual).not.to.include('null prototype');
|
|
198
|
+
});
|
|
150
199
|
});
|
package/test/utils.js
CHANGED
|
@@ -31,7 +31,7 @@ function slowPod(...strs) {
|
|
|
31
31
|
return emitter;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function runPipeline(inputLines, args) {
|
|
34
|
+
async function runPipeline(inputLines, args) {
|
|
35
35
|
const lines = [];
|
|
36
36
|
|
|
37
37
|
const stdout = { write: (line) => lines.push(line) };
|
|
@@ -39,6 +39,11 @@ function runPipeline(inputLines, args) {
|
|
|
39
39
|
|
|
40
40
|
inputLines.forEach(line => pipeline.onLogLine(line));
|
|
41
41
|
|
|
42
|
+
// Waiting for nextTick promises to resolve
|
|
43
|
+
await Hoek.wait(0);
|
|
44
|
+
|
|
45
|
+
pipeline.onEnd();
|
|
46
|
+
|
|
42
47
|
// Removing \n from the end of each line
|
|
43
48
|
return lines.map(line => line.slice(0, -1));
|
|
44
49
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const vm = require('vm');
|
|
2
|
-
|
|
3
|
-
module.exports = filter => line => {
|
|
4
|
-
if(typeof line !== 'object') {
|
|
5
|
-
throw new Error("To use a field filter, you need to specify a parser like '-p json' ");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
vm.createContext(line);
|
|
9
|
-
const result = vm.runInContext(filter, line);
|
|
10
|
-
return Boolean(result);
|
|
11
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const { compile } = require('jstache');
|
|
2
|
-
|
|
3
|
-
module.exports = format => {
|
|
4
|
-
const template = compile(format);
|
|
5
|
-
|
|
6
|
-
return line => {
|
|
7
|
-
if(typeof line !== 'object') {
|
|
8
|
-
throw new Error("To use an output transformer, you need to specify a parser like '-p json' ");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return template(line);
|
|
12
|
-
};
|
|
13
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const json = require('./output-json');
|
|
2
|
-
const logfmt = require('./output-logfmt');
|
|
3
|
-
const mustache = require('./output-mustache');
|
|
4
|
-
const original = require('./output-original');
|
|
5
|
-
const unset = require('./output-unset');
|
|
6
|
-
|
|
7
|
-
module.exports = format => {
|
|
8
|
-
switch(format?.toLowerCase()) {
|
|
9
|
-
case void 0: return unset();
|
|
10
|
-
case 'json': return json();
|
|
11
|
-
case 'logfmt': return logfmt();
|
|
12
|
-
case 'original': return original();
|
|
13
|
-
default: return mustache(format);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
function splitColumns(line) {
|
|
2
|
-
return line.replace(/\s+/g, ' ').split(' ');
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
module.exports = () => {
|
|
6
|
-
let headers;
|
|
7
|
-
|
|
8
|
-
return (line, _original, pipeline) => {
|
|
9
|
-
if(!pipeline.firstLine) {
|
|
10
|
-
// Ignore first line, it's the headers
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
if(!headers) {
|
|
14
|
-
headers = splitColumns(pipeline.firstLine);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const columns = splitColumns(line);
|
|
18
|
-
const obj = {};
|
|
19
|
-
|
|
20
|
-
headers.forEach((header, i) => {
|
|
21
|
-
obj[header] = columns[i];
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
return obj;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
const json = require('./parse-json');
|
|
2
|
-
const logfmt = require('./parse-logfmt');
|
|
3
|
-
const regex = require('./parse-regex');
|
|
4
|
-
const table = require('./parse-table');
|
|
5
|
-
|
|
6
|
-
module.exports = format => {
|
|
7
|
-
switch(format?.toLowerCase()) {
|
|
8
|
-
case void 0: return null;
|
|
9
|
-
case 'json': return json();
|
|
10
|
-
case 'logfmt': return logfmt();
|
|
11
|
-
case 'table': return table();
|
|
12
|
-
default: return regex(format);
|
|
13
|
-
}
|
|
14
|
-
};
|