'use strict'; const assert = require('assert'), path = require('path'), shell = require('./shell.js'), spawn = require('child_process').spawn, stream = require('stream'), { StringDecoder } = require('string_decoder'), TransformStream = stream.Transform; const LOGTAIL_CMD = path.join(__dirname, 'scripts/logtail.sh'); class LogStream extends TransformStream { constructor(options) { super(); this._options = Object.assign({ source: 'unknown', format: 'json' }, options); this._decoder = new StringDecoder(); this._soFar = ''; } _format(line) { if (this._options.format !== 'json') return line + '\n'; const data = line.split(' '); // logs are let timestamp = (new Date(data[0])).getTime(); if (isNaN(timestamp)) timestamp = 0; const message = line.slice(data[0].length+1); return JSON.stringify({ realtimeTimestamp: timestamp * 1000, // timestamp info can be missing (0) for app logs via logPaths message: message || line, // send the line if message parsing failed source: this._options.source }) + '\n'; } _transform(chunk, encoding, callback) { const data = this._soFar + this._decoder.write(chunk); let start = this._soFar.length, end = -1; while ((end = data.indexOf('\n', start)) !== -1) { const line = data.slice(start, end); // does not include end this.push(this._format(line)); start = end + 1; } this._soFar = data.slice(start); callback(null); } _flush(callback) { const line = this._soFar + this._decoder.end(); this.push(this._format(line)); callback(null); } } function tail(filePaths, options) { assert(Array.isArray(filePaths)); assert.strictEqual(typeof options, 'object'); const lines = options.lines === -1 ? '+1' : options.lines; const args = [ LOGTAIL_CMD, '--lines=' + lines ]; if (options.follow) args.push('--follow'); return shell.sudo('tail', args.concat(filePaths), { streamStdout: true }, () => {}); } function journalctl(unit, options) { assert.strictEqual(typeof unit, 'string'); assert.strictEqual(typeof options, 'object'); const args = [ '--lines=' + (options.lines === -1 ? 'all' : options.lines), `--unit=${unit}`, '--no-pager', '--output=short-iso' ]; if (options.follow) args.push('--follow'); return spawn('journalctl', args); } exports = module.exports = { tail, journalctl, LogStream };