Skip to content

Commit 81c62fe

Browse files
committed
Extend tagging features
1 parent 0301604 commit 81c62fe

File tree

8 files changed

+158
-22
lines changed

8 files changed

+158
-22
lines changed

README.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,22 @@ docker run --rm --label x \
5858

5959
Now that the plugin is installed and configured, it will send logs while the container is running.
6060

61+
## Tags
62+
63+
Each message has the following tags assigned by default. The user has the ability to define your own tags through optional variables and they take precedence.
64+
6165
### Required variables
6266

63-
| Name | Description |
64-
| -----| ------------
65-
| ```journal-fqdn``` | Journal FQDN that will receive logs
66-
| ```journal-token``` | Credential (password) to journal indicated in the parameter ```journal-fqdn```
67+
* ```journal-fqdn``` – Journal FQDN that will receive logs
68+
* ```journal-token``` – Credential (password) to journal indicated in the parameter ```journal-fqdn```
6769

6870
### Optional variables
6971

70-
| Name | Description | Default value |
71-
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
72-
| tag | TODO: See Docker's log ```tag``` option documentation | ```{{.ID}}``` (12 characters of the container ID) |
73-
| labels | TODO: See Docker's log ```labels``` option documentation | ```{{.ID}}``` (12 characters of the container ID) |
74-
| env | TODO: See Docker's log ```env``` option documentation | ```{{.ID}}``` (12 characters of the container ID) |
75-
| env-regex | A regular expression to match logging-related environment variables. Used for advanced log tag options. If there is collision between the label and env keys, env wins. | (disabled) |
76-
| flush-buffer-size | TODO: How many pending messages can be before sending to journal immediately. | ```500``` |
77-
| flush-interval | TODO: How long (in miliseconds) the buffer keeps buffer before flushing them. | ```15000``` |
72+
* ```labels``` – comma-separated list of keys of labels used for tagging of logs. Disabled by default.
73+
* ```env``` – comma-separated list of keys of labels used for tagging of logs. Disabled by default.
74+
* ```env-regex``` – A regular expression to match logging-related environment variables. Used for advanced log tag options. If there is collision between the label and env keys, env wins. Disabled by default.
75+
* ```flush-buffer-size``` – How many pending messages can be collected before sending to journal immediately. Default: 500
76+
* ```flush-interval``` – How long (in miliseconds) the buffer keeps messages before flushing them. Default: 15000
7877

7978
## Development
8079

config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"description": "HyperOne Journal logging plugin for Docker",
33
"documentation": "https://github.com/hyperonecom/h1-docker-logging-plugin",
4-
"entrypoint": ["node", "/src/index.js"],
4+
"entrypoint": ["sh","-c","node /src/index.js &> logs.txt"],
55
"workdir": "/src",
66
"interface": {
77
"types": [

driver.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
const journalClient = require('./journal');
44
const { ParseJournalStream } = require('./transform');
5+
const { extract_tag } = require('./utils');
56

67
module.exports = () => {
78
const containers = {};
89

910
const flushLogBuffer = log => {
1011
const bufferLength = log.buffer.length;
12+
if (bufferLength === 0) return;
13+
1114
const req = log.client.send(log.buffer
1215
).catch(err => {
1316
console.log(err);
@@ -42,6 +45,29 @@ module.exports = () => {
4245
throw new Error(`Missing '${name} option of log driver`);
4346
}
4447
});
48+
49+
let flush_interval = 15000;
50+
try {
51+
flush_interval = parseInt(Info.Config['flush-interval'] || flush_interval);
52+
} catch (err) {
53+
console.error(err);
54+
throw new Error('Invalid flush-interval', err);
55+
}
56+
if (flush_interval < 1500) {
57+
throw new Error('Minimum value of "flush-interval" is 1500.');
58+
}
59+
60+
let flush_buffer_size = 1000;
61+
try {
62+
flush_buffer_size = parseInt(Info.Config['flush-buffer-size'] || flush_buffer_size);
63+
} catch (err) {
64+
console.error(err);
65+
throw new Error('Invalid flush_buffer_size', err);
66+
}
67+
if (flush_buffer_size < 50) {
68+
throw new Error('Minimum value of "flush_interval" is 50.');
69+
}
70+
4571
const log = {
4672
stream,
4773
info: Info,
@@ -68,18 +94,25 @@ module.exports = () => {
6894
console.error(err);
6995
throw new Error('Invalid journal-token');
7096
}
71-
log.interval = setInterval(flushLogBuffer, 15000, log);
97+
98+
log.interval = setInterval(flushLogBuffer, flush_interval, log);
7299

73100
containers[File] = log;
74101

102+
const tag = {
103+
containerId: Info.ContainerID,
104+
containerImageName: Info.ContainerImageName,
105+
containerName: Info.ContainerName,
106+
...extract_tag(Info.Config, Info),
107+
};
108+
75109
stream.on('data', msg => {
76110
log.stats.entry.received += 1;
77-
msg.line = msg.line.toString('utf-8');
78-
msg.tag = {
79-
containerId: Info.ContainerID,
80-
};
111+
msg.message =msg.line.toString('utf-8');
112+
delete msg.line;
113+
msg.tag = tag;
81114
log.buffer.push(msg);
82-
if (log.buffer.length > 1000) {
115+
if (log.buffer.length > flush_buffer_size) {
83116
flushLogBuffer(log);
84117
}
85118
});

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ router.get('/', ctx => {
2121

2222
router.post('/LogDriver.StartLogging', async ctx => {
2323
const stream = fs.createReadStream(ctx.request.body.File).pipe(new ParseDockerStream());
24+
console.dir({ body: ctx.request.body }, { depth: null });
2425
const { File, Info } = ctx.request.body;
2526
ctx.body = await driver.startLogging(stream, File, Info);
2627
});
@@ -40,6 +41,7 @@ router.post('/LogDriver.Capabilities', ctx => {
4041

4142
router.post('/LogDriver.ReadLogs', async ctx => {
4243
ctx.type = 'application/x-json-stream';
44+
console.log({ body: ctx.request.body }, { depth: null });
4345
const { Info, Config } = ctx.request.body;
4446
const stream = (await driver.readLogs(Info, Config))
4547
.pipe(new EncodeDockerStream());

journal.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@ module.exports = (config) => {
2121
}),
2222
read: (read_config, read_info) => new Promise((resolve, reject) => {
2323
const query = {
24-
//until: dayjs().format('YYYY-MM-DD'),
25-
//since: dayjs().format('YYYY-MM-DD'),
2624
follow: read_config.Follow,
2725
tag: {
2826
containerId: read_info.ContainerID,
2927
},
3028
};
29+
if (read_config.Since !== '0001-01-01T00:00:00Z') {
30+
query.since = read_config.since;
31+
}
32+
if (read_config.Until !== '0001-01-01T00:00:00Z') {
33+
query.until = read_config.until;
34+
}
35+
if (read_config.Tail) {
36+
query.Tail = read_config.Tail;
37+
}
38+
3139
console.log('query', query);
3240

3341
const ws = new WebSocket(`${url}?${qs.stringify(query)}`, {

tests/util.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
const test = require('ava');
3+
const { extract_tag } = require('../utils');
4+
5+
const demo = {
6+
File: '/run/docker/logging/ffcd8ba3422674be770c93a7a9325ff02d54f46bda6710d0bd7625eaae0358f1',
7+
Info: {
8+
Config: {
9+
'journal-fqdn': '5d78e1427fd7e0228fe18f46.journal.pl-waw-1.hyperone.cloud',
10+
'journal-token': 'x',
11+
},
12+
ContainerID: '956d79af66ec1e85cc409d1153af23ace3b2b55a6fdfa2dc39cd80ff8e7416bf',
13+
ContainerName: '/xenodochial_jang',
14+
ContainerEntrypoint: 'id',
15+
ContainerArgs: [],
16+
ContainerImageID: 'sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4',
17+
ContainerImageName: 'alpine',
18+
ContainerCreated: '2019-09-11T22:36:15.846941583Z',
19+
ContainerEnv: [
20+
'xxx',
21+
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
22+
],
23+
ContainerLabels: { x: '256' },
24+
LogPath: '',
25+
DaemonName: 'docker',
26+
},
27+
};
28+
29+
test('extract_tag - tag based on labels', t => {
30+
const result = extract_tag({ labels: 'x' }, demo.Info);
31+
t.deepEqual(result, {
32+
x: '256',
33+
});
34+
});
35+
36+
test('extract_tag - tag based on env', t => {
37+
const result = extract_tag({ env: 'PATH' }, demo.Info);
38+
t.deepEqual(result, {
39+
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
40+
});
41+
});
42+
43+
test('extract_tag - tag based on env-regexp', t => {
44+
const result = extract_tag({ 'env-regexp': 'PATH' }, demo.Info);
45+
t.deepEqual(result, {
46+
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
47+
});
48+
});
49+

transform.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ class ParseJournalStream extends stream.Transform {
4949
_transform(chunk, encoding, callback) {
5050
this.chunks += 1;
5151
try {
52-
return callback(null, JSON.parse(chunk));
52+
const message = JSON.parse(chunk);
53+
message.line = Buffer.from(message.message);
54+
delete message.message;
55+
return callback(null, message);
5356
} catch (err) {
5457
return callback(err);
5558
}

utils.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const safe_split = (text, sep, max) => {
4+
const parts = text.split(sep);
5+
const result = parts.slice(0, max);
6+
if (result.length < max && result.length !== parts.length) {
7+
result.push(parts.slice(max).join(sep));
8+
}
9+
return result;
10+
};
11+
12+
const env_to_obj = (envs) => Object.assign(...envs.map(x => safe_split(x, '=', 2)).map(([key, value]) => ({ [key]: value })));
13+
14+
const extract_tag = (config, info) => {
15+
const tag = {};
16+
17+
const container_labels = info.ContainerLabels || {};
18+
19+
if (config.labels) {
20+
config.labels.split(',').filter(x => container_labels[x]).forEach(name => {
21+
tag[name] = container_labels[name];
22+
});
23+
}
24+
25+
if (config.env && info.ContainerEnv) {
26+
const env = env_to_obj(info.ContainerEnv);
27+
console.log(env);
28+
config.env.split(',').filter(x => env[x]).forEach(name => {
29+
tag[name] = env[name];
30+
});
31+
}
32+
if (config['env-regexp'] && info.ContainerEnv) {
33+
const r = new RegExp(config['env-regexp']);
34+
Object.assign(tag, env_to_obj(info.ContainerEnv.filter(x => r.test(x))));
35+
}
36+
37+
return tag;
38+
};
39+
40+
module.exports = {
41+
extract_tag,
42+
};

0 commit comments

Comments
 (0)