journalctl-ts
Version:
A journalctl client for node, written in Typescript
263 lines • 11.4 kB
JavaScript
;
/**
* @author Thomas Novotny
* @packageDocumentation
*/
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = __importDefault(require("events"));
const child_process_1 = __importDefault(require("child_process"));
const readline_1 = __importDefault(require("readline"));
/**
* Validata a fieldname as proper journald fieldName. Allowed are only A-Z, 0-9 and underscores
* @param fieldName Field name to validate
* @throws Error if field name is invalid
* @returns The same field name
*/
function validateFieldName(fieldName) {
const RegExp_ValidFieldName = /^[A-Z0-9_]+$/;
if (!RegExp_ValidFieldName.test(fieldName))
throw new Error(`Not a valid journald Field Name: "${fieldName}"`);
return fieldName;
}
/**
* Validate a string as proper syslog identifier
* @param identifier String to validate
* @throws Error if field syslog identifier is invalid
* @returns The same identifier
*/
function validateSyslogIdentifier(identifier) {
const RegExp_SyslogIdentifier = /^[^\s]+$/;
if (!RegExp_SyslogIdentifier.test(identifier))
throw new Error(`Not a valid SYSLOG_IDENTIFIER: "${identifier}"`);
return identifier;
}
/**
* Validate date as proper date
* @param dateString date string
* @throws Error if date string is invalid
* @returns well-formed date string
*/
function validateDate(dateString) {
const RegExp_DateString = /^\d\d\d\d-\d\d-\d\d(?: \d\d:\d\d(?::\d\d)?)?$/;
if (!RegExp_DateString.test(dateString))
throw new Error(`Not a valid date: "${dateString}"`);
if (isNaN(new Date(dateString).getTime()))
throw new Error(`Not a valid date: "${dateString}"`);
return dateString;
}
/**
* Validate that a value is a positive integer
* @throws Error if value is either not an integer or not positive
* @param value the value that was tested
*/
function validatePositiveInteger(value) {
if (!Number.isInteger(value) || isNaN(value))
throw new Error(`Not an integer: ${value}`);
if (value < 0)
throw new Error(`Not a positive integer: ${value}`);
return value;
}
/**
* Validates a journald priority.
* If priority is an integer, the functions clamps the values to the next valid priority value
* @param priority priority value
* @throws Error if priority is not a number or not an integer
* @returns valid journald priority
*/
function validatePriority(priority) {
validatePositiveInteger(priority);
if (priority < 0 || priority > 7)
throw new Error(`Not a valid priority: ${priority}`);
return priority;
}
/**
* Validates a systemd unit name
* @param unit Unit name to validate
* @throws Error if unit name is not valid
* @returns The unit name
*/
function validateUnit(unit) {
const RegExp_Systemd_Unit_Name = /^[A-Za-z0-9:\-_\.\\]+$/;
if (!RegExp_Systemd_Unit_Name.test(unit))
throw new Error(`Not a valid systemd unit name: "${unit}"`);
return unit;
}
/**
* This class is an EventEmitter that watches the local systemd journal for events and emits them.
* @typeParam FieldNames List of field names that should be included in the JournalD messages. If left undefined, the messages will include all available fields.
*/
class JournalCtl extends events_1.default {
/**
* Create a new JournalCtl interface with the specified options
*
* @param options Optional options object
* @throws Error if options are malformed or if the journalctl process can not be spawned
*/
constructor(options = {}, outputFields) {
super();
/** Initial args:
* -q (no error output)
* -o json (output as newline delimited json)
*/
//const args: string[] = ['-q', '-o', 'json'];
const args = ['-o', 'json'];
if (options.until) {
let dateString = typeof (options.until) === 'string' ? validateDate(options.until) : options.until.toISOString().replace(/T|\.\d\d\dZ/g, ' ').trim();
args.push('-U', `${dateString}`);
}
else {
// If "until" is not set, set -f (tail)
args.push("-f");
}
if (options.all)
args.push('-a');
// Unless otherwise defined, default lines are 10
if (!options.until && !options.since && options.lines === undefined) {
args.push('-n', `${10}`);
}
else if (options.lines !== undefined) {
args.push('-n', `${validatePositiveInteger(options.lines)}`);
}
if (options.since) {
let dateString = typeof (options.since) === 'string' ? validateDate(options.since) : options.since.toISOString().replace(/T|\.\d\d\dZ/g, ' ').trim();
args.push('-S', `${dateString}`);
}
if (options.since && options.until) {
const dSince = (typeof (options.since) == 'string' ? new Date(options.since) : options.since);
const dUntil = (typeof (options.until) == 'string' ? new Date(options.until) : options.until);
if (dSince.getTime() > dUntil.getTime())
throw new Error(`'Since' date can not be more recent than 'Until' date.`);
}
if (options.priority !== undefined) {
args.push('-p');
if (typeof (options.priority) === 'number') {
args.push(`${validatePriority(options.priority)}`);
}
else {
args.push(`${validatePriority(options.priority.from)}..${validatePriority(options.priority.to)}`);
}
}
if (options.identifier !== undefined)
args.push('-t', validateSyslogIdentifier(options.identifier));
if (options.unit)
args.push('-u', validateUnit(options.unit));
if (options.filter) {
for (let key in options.filter) {
args.push(`${validateFieldName(key)}=${options.filter[key]}`);
}
}
if (outputFields) {
outputFields.forEach((fieldName) => {
args.push('--output-field', `${validateFieldName(fieldName)}`);
});
if (!outputFields.includes("MESSAGE"))
args.push('--output-field', `MESSAGE`);
if (!outputFields.includes("SYSLOG_IDENTIFIER"))
args.push('--output-field', `SYSLOG_IDENTIFIER`);
if (!outputFields.includes("_HOSTNAME"))
args.push('--output-field', `_HOSTNAME`);
}
// Start journalctl
//console.log(['journalctl', ...args].join(' '));
this.journalCtl = child_process_1.default.spawn('journalctl', args);
this.stdErrorText = "";
this.journalCtl.stderr.on('data', (chunk) => {
this.stdErrorText += chunk.toString();
});
this.journalCtl.on("exit", (code) => {
var _a;
if (code) {
this.emit('error', new Error((_a = this.stdErrorText) !== null && _a !== void 0 ? _a : `Process exited with code ${code}`));
}
this.emit('exit');
});
this.journalCtl.on("error", (err) => {
if (err.message.includes("ENOENT")) {
this.emit('error', new Error(`journalctl executable not found. Is this a systemd distribution?`));
}
else if (err.message.includes("EACCES")) {
this.emit('error', new Error(`journalctl executable not accessible.`));
}
else {
this.emit('error', err);
}
});
const lineReader = readline_1.default.createInterface(this.journalCtl.stdout);
lineReader.on('line', (line) => {
try {
// Note: apparently journald can in some cases end messages like ", }" which adds an invalid comma. The replace function removes it
let message = JSON.parse(line.replace(/,\s*\}$/, ' }'));
if (message.MESSAGE === undefined)
message.MESSAGE = "";
if (message._HOSTNAME === undefined)
message._HOSTNAME = "";
this.emit('message', message);
}
catch (err) {
this.emit('error', new Error(`Could not parse JSON: ${line}`));
}
});
this.generatorInstanceExtists = false;
}
on(event, callBack) {
return super.on(event, callBack);
}
/**
* Creates an AsyncGenerator for this instance
* @returns AsyncGenerator which yields one journald message at a time as they come in
*/
createGenerator() {
return __asyncGenerator(this, arguments, function* createGenerator_1() {
if (this.generatorInstanceExtists) {
throw new Error(`Only one instance of the async message generator can be created per JournalCtl instance.`);
}
this.generatorInstanceExtists = true;
let totalEntries = 0;
let resolve;
//let reject: (err: Error) => void;
const messagePromises = [];
const createPromise = () => new Promise((res /*, rej */) => { resolve = res; /*reject = rej;*/ });
messagePromises.push(createPromise());
this.on('error', () => {
//reject(err);
resolve(undefined);
}).on('message', message => {
const oldResolve = resolve;
messagePromises.push(createPromise());
totalEntries++;
oldResolve(message);
}).on('exit', () => {
resolve(undefined);
});
let result;
while (result = yield __await(messagePromises.shift())) {
yield yield __await(result);
}
return yield __await({ totalEntries: totalEntries });
});
}
/**
* Stops the journalctl process and causes this instance to eventually emit the 'exit' event
* @returns the return value of ChildProcess.kill()
*/
stop() {
return this.journalCtl.kill();
}
}
exports.default = JournalCtl;
//# sourceMappingURL=JournalCtl.js.map