Add an endpoint and module for reading journald logs

Change-type: minor
Closes: #1003
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2019-06-18 12:22:37 +01:00
parent fa26988ca6
commit 43cbf7dbba
No known key found for this signature in database
GPG Key ID: 49690ED87032539F
3 changed files with 107 additions and 0 deletions

View File

@ -1149,6 +1149,7 @@ Response:
]
}
```
### V2 Utilities
#### Cleanup volumes with no references
@ -1179,3 +1180,37 @@ Unsuccessful response:
}
```
#### Journald logs
Added in supervisor version v10.1.0
Retrieve a stream to the journald logs on device. This is
equivalent to running `journalctl -o export`. Options
supported are:
##### all: boolean
Show all fields in full, equivalent to `journalctl --all`.
##### follow: boolean
Continuously stream logs as they are generated, equivalent
to `journalctl --follow`.
##### count: integer
Show the most recent `count` events, equivalent to
`journalctl --line=<count>`.
##### unit
Show journal logs from `unit` only, equivalent to
`journalctl --unit=<unit>`.
Fields should be provided via POST body in JSON format.
From an application container (with systemd installed):
```
$ curl -X POST --data '{"follow":true,"all":true}' "$BALENA_SUPERVISOR_ADDRESS/v2/journal-logs?apikey=$BALENA_SUPERVISOR_API_KEY" | systemd-journal-remote - -o log.journal
```
The `log.journal` file can then be viewed with
```
journalctl --file log.journal -f
```

View File

@ -13,8 +13,11 @@ import {
import { doPurge, doRestart, serviceAction } from './common';
import Volume from '../compose/volume';
import { spawnJournalctl } from '../lib/journald';
import log from '../lib/supervisor-console';
import supervisorVersion = require('../lib/supervisor-version');
import { checkInt, checkTruthy } from '../lib/validation';
export function createV2Api(router: Router, applications: ApplicationManager) {
const { _lockingIfNecessary, deviceState } = applications;
@ -533,4 +536,32 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
});
}
});
router.post('/v2/journal-logs', (req, res) => {
try {
const all = checkTruthy(req.body.all) || false;
const follow = checkTruthy(req.body.follow) || false;
const count = checkInt(req.body.count, { positive: true }) || undefined;
const unit = req.body.unit;
const journald = spawnJournalctl({ all, follow, count, unit });
res.status(200);
journald.stdout.pipe(res);
res.on('close', () => {
journald.kill('SIGKILL');
});
journald.on('exit', () => {
res.end();
});
} catch (e) {
log.error('There was an error reading the journalctl process', e);
if (res.headersSent) {
return;
}
res.json({
status: 'failed',
message: messageFromError(e),
});
}
});
}

41
src/lib/journald.ts Normal file
View File

@ -0,0 +1,41 @@
import { ChildProcess, spawn } from 'child_process';
import constants = require('./constants');
import log from './supervisor-console';
export function spawnJournalctl(opts: {
all: boolean;
follow: boolean;
count?: number;
unit?: string;
}): ChildProcess {
const args = [
// The directory we want to run the chroot from
constants.rootMountPoint,
'journalctl',
'-o',
'export',
];
if (opts.all) {
args.push('-a');
}
if (opts.follow) {
args.push('--follow');
}
if (opts.unit != null) {
args.push('-u');
args.push(opts.unit);
}
if (opts.count != null) {
args.push('-n');
args.push(opts.count.toString());
}
log.debug('Spawning journald with: chroot ', args.join(' '));
const journald = spawn('chroot', args, {
stdio: 'pipe',
});
return journald;
}