logtunnel 1.1.0 → 1.2.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 +14 -0
- package/package.json +1 -1
- package/src/definition.js +7 -0
- package/src/main.js +2 -2
- package/src/pipeline.js +13 -11
- package/src/transformers/filters/pick.js +22 -0
- package/test/filters.spec.js +32 -0
- package/test/pipeline.spec.js +17 -1
- package/test/utils.js +2 -1
package/README.md
CHANGED
|
@@ -218,6 +218,20 @@ Convert `kubectl` table output to logfmt for easier downstream filtering:
|
|
|
218
218
|
curl -s https://cdn.codetunnel.net/lt/table.log | lt -p table -o logfmt
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
+
### Pick columns (`-P`)
|
|
222
|
+
|
|
223
|
+
Pick specific columns to be displayed after parsing (works with any parser):
|
|
224
|
+
|
|
225
|
+
List kubernetes pods and show only the specified columns:
|
|
226
|
+
```bash
|
|
227
|
+
cat ./examples/table.log | lt -p table -P NAME,STATUS,AGE -o table
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Find logs containing "error", parse as JSON and keep just the specified columns:
|
|
231
|
+
```bash
|
|
232
|
+
curl -s https://cdn.codetunnel.net/lt/text.log | lt -f error -p json -P ts,level,message -o json
|
|
233
|
+
```
|
|
234
|
+
|
|
221
235
|
### Custom regex parsing (`-p '(?<name>...)'`)
|
|
222
236
|
|
|
223
237
|
Use a regex with named groups to “extract fields” from unstructured text:
|
package/package.json
CHANGED
package/src/definition.js
CHANGED
|
@@ -49,6 +49,11 @@ const definition = {
|
|
|
49
49
|
alias: 'kubectl',
|
|
50
50
|
type: 'boolean',
|
|
51
51
|
},
|
|
52
|
+
P: {
|
|
53
|
+
description: 'Pick only these columns from parsed objects (comma-separated). Requires a parser like -p json.',
|
|
54
|
+
alias: 'pick',
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
52
57
|
};
|
|
53
58
|
|
|
54
59
|
const $ = '$ '.gray;
|
|
@@ -76,6 +81,8 @@ const examples = [
|
|
|
76
81
|
$ + 'curl -s https://cdn.codetunnel.net/lt/table.log | lt -H gateway',
|
|
77
82
|
'Kubernetes tables: -k is shorthand for -p table -o table:'.dim,
|
|
78
83
|
$ + 'curl -s https://cdn.codetunnel.net/lt/table.log | lt -k payment',
|
|
84
|
+
'Kubernetes tables: pick columns (NAME, STATUS, AGE):'.dim,
|
|
85
|
+
$ + 'cat ./examples/table.log | lt -p table -P NAME,STATUS,AGE -o table',
|
|
79
86
|
'Kubernetes tables: show pods that are not fully ready:'.dim,
|
|
80
87
|
$ + 'curl -s https://cdn.codetunnel.net/lt/table.log | lt -k -F \'lt (itemAt (split READY "/") 0) (itemAt (split READY "/") 1)\'',
|
|
81
88
|
'Custom regex parsing (extract fields) and output as logfmt:'.dim,
|
package/src/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const semver = require('semver');
|
|
3
|
-
if(!semver.satisfies(process.version, '>=
|
|
4
|
-
console.error(`Your NodeJS version (${process.version}) is too old for logtunnel :(\nUse at least NodeJS
|
|
3
|
+
if(!semver.satisfies(process.version, '>=16')) {
|
|
4
|
+
console.error(`Your NodeJS version (${process.version}) is too old for logtunnel :(\nUse at least NodeJS 16`);
|
|
5
5
|
process.exit(1);
|
|
6
6
|
}
|
|
7
7
|
|
package/src/pipeline.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { FieldFilter } = require('./transformers/filters/field');
|
|
4
4
|
const { FindFilter } = require('./transformers/filters/find');
|
|
5
5
|
const { IgnoreFilter } = require('./transformers/filters/ignore');
|
|
6
|
+
const { PickFilter } = require('./transformers/filters/pick');
|
|
6
7
|
const { outputFactory } = require('./transformers/outputs/factory');
|
|
7
8
|
const { parseFactory } = require('./transformers/parsers/factory');
|
|
8
9
|
|
|
@@ -20,17 +21,16 @@ class LogPipeline {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
onLogLine(line) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
24
|
+
debug('got line: ' + line)
|
|
25
|
+
const isFirstLine = this._updateFirstLine(line);
|
|
26
|
+
this._logLine(line, isFirstLine)
|
|
27
|
+
.catch(error => {
|
|
28
|
+
// Covering this would kill the process
|
|
29
|
+
/* $lab:coverage:off$ */
|
|
30
|
+
console.error('Error:', error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
/* $lab:coverage:on$ */
|
|
33
|
+
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async _logLine(line, isFirstLine) {
|
|
@@ -99,6 +99,8 @@ class LogPipeline {
|
|
|
99
99
|
parseFactory(this.args.parser),
|
|
100
100
|
// ...and apply field filters
|
|
101
101
|
...this.args.field.map(f => new FieldFilter(f)),
|
|
102
|
+
// ...optionally pick specific columns
|
|
103
|
+
this.args.pick ? new PickFilter(this.args.pick) : null,
|
|
102
104
|
// And finally format the output
|
|
103
105
|
this.outputTransformer,
|
|
104
106
|
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class PickFilter {
|
|
2
|
+
constructor(columns) {
|
|
3
|
+
this.columns = columns
|
|
4
|
+
.split(',')
|
|
5
|
+
.map(column => column.trim())
|
|
6
|
+
.filter(Boolean);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run(line) {
|
|
10
|
+
if (typeof line !== 'object') {
|
|
11
|
+
throw new Error("To use -P/--pick, you need to specify a parser like '-p json'");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Object.fromEntries(
|
|
15
|
+
this.columns
|
|
16
|
+
.filter(column => line[column] !== undefined)
|
|
17
|
+
.map(column => [column, line[column]])
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { PickFilter };
|
package/test/filters.spec.js
CHANGED
|
@@ -4,6 +4,7 @@ const Code = require('@hapi/code');
|
|
|
4
4
|
const { FindFilter } = require('../src/transformers/filters/find');
|
|
5
5
|
const { IgnoreFilter } = require('../src/transformers/filters/ignore');
|
|
6
6
|
const { FieldFilter } = require('../src/transformers/filters/field');
|
|
7
|
+
const { PickFilter } = require('../src/transformers/filters/pick');
|
|
7
8
|
|
|
8
9
|
const { describe, it } = exports.lab = Lab.script();
|
|
9
10
|
const { expect } = Code;
|
|
@@ -83,4 +84,35 @@ describe('filters', () => {
|
|
|
83
84
|
expect(await transformer.run({ foo: 'ABADA' })).to.be.false();
|
|
84
85
|
});
|
|
85
86
|
});
|
|
87
|
+
|
|
88
|
+
describe('--pick', () => {
|
|
89
|
+
it('should error when strings weren\'t parsed into object', async () => {
|
|
90
|
+
const transformer = new PickFilter('foo');
|
|
91
|
+
expect(transformer.run('a')).to.reject();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should pick only selected field (1 field)', async () => {
|
|
95
|
+
const transformer = new PickFilter('foo');
|
|
96
|
+
const result = await transformer.run({ foo: 'bar', baz: 'qux', extra: 'nope' });
|
|
97
|
+
expect(result).to.equal({ foo: 'bar' });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should pick only selected fields (2 fields)', async () => {
|
|
101
|
+
const transformer = new PickFilter('foo,baz');
|
|
102
|
+
const result = await transformer.run({ foo: 'bar', baz: 'qux', extra: 'nope' });
|
|
103
|
+
expect(result).to.equal({ foo: 'bar', baz: 'qux' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should not break with undefined fields', async () => {
|
|
107
|
+
const transformer = new PickFilter('foo,bar');
|
|
108
|
+
const result = await transformer.run({ foo: 'bar', baz: 'qux', extra: 'nope' });
|
|
109
|
+
expect(result).to.equal({ foo: 'bar' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should include falsy values', async () => {
|
|
113
|
+
const transformer = new PickFilter('foo,baz');
|
|
114
|
+
const result = await transformer.run({ foo: 0, baz: null, extra: 'nope' });
|
|
115
|
+
expect(result).to.equal({ foo: 0, baz: null });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
86
118
|
});
|
package/test/pipeline.spec.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const Lab = require('@hapi/lab');
|
|
2
2
|
const Code = require('@hapi/code');
|
|
3
|
-
const { runPipeline, _, f, i, F, p, o, H, k } = require('./utils');
|
|
3
|
+
const { runPipeline, _, f, i, F, P, p, o, H, k } = require('./utils');
|
|
4
4
|
|
|
5
5
|
const { describe, it } = exports.lab = Lab.script();
|
|
6
6
|
const { expect } = Code;
|
|
@@ -146,6 +146,22 @@ describe('pipeline', () => {
|
|
|
146
146
|
expect(actual).to.equal(expected);
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
it('should pick columns before formatting', async () => {
|
|
150
|
+
const args = [
|
|
151
|
+
p('logfmt'),
|
|
152
|
+
o('json'),
|
|
153
|
+
P('foo,baz'),
|
|
154
|
+
];
|
|
155
|
+
const actual = await runPipeline([
|
|
156
|
+
'foo=bar baz=qux extra=nope',
|
|
157
|
+
], args);
|
|
158
|
+
const expected = [
|
|
159
|
+
'{"foo":"bar","baz":"qux"}',
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
expect(actual).to.equal(expected);
|
|
163
|
+
});
|
|
164
|
+
|
|
149
165
|
it('should log headers when -H is provided', async () => {
|
|
150
166
|
const args = [
|
|
151
167
|
p('table'),
|
package/test/utils.js
CHANGED
|
@@ -64,9 +64,10 @@ const _ = str => ({ _: str });
|
|
|
64
64
|
const f = str => ({ filter: [str] });
|
|
65
65
|
const i = str => ({ ignore: [str] });
|
|
66
66
|
const F = str => ({ field: [str] });
|
|
67
|
+
const P = str => ({ pick: str });
|
|
67
68
|
const p = str => ({ parser: str });
|
|
68
69
|
const o = str => ({ output: str });
|
|
69
70
|
const H = () => ({ headers: true });
|
|
70
71
|
const k = () => ({ kubectl: true });
|
|
71
72
|
|
|
72
|
-
module.exports = { pod, slowPod, runPipeline, _, f, i, F, p, o, H, k };
|
|
73
|
+
module.exports = { pod, slowPod, runPipeline, _, f, i, F, P, p, o, H, k };
|