@webos-tools/cli
Version:
Command Line Interface for development webOS application and service
585 lines (496 loc) • 22.1 kB
JavaScript
/*
* Copyright (c) 2021-2024 LG Electronics Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
const util = require('util'),
async = require('async'),
npmlog = require('npmlog'),
fs = require('fs'),
path = require('path'),
novacom = require('./base/novacom'),
errHndl = require('./base/error-handler'),
sessionLib = require('./session'),
createDateFileName = require('./util/createFileName').createDateFileName;
(function() {
const log = npmlog;
log.heading = 'log';
log.level = 'warn';
const reservedOption = ["--level", "--device", "--save"];
let idx, savedFilePath;
const logLib = {
/**
* @property {Object} log an npm log instance
*/
log: log,
show: function(options, next) {
log.info("log#show()");
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options = options || {};
async.series([
_makeSession,
_checkUser,
this.checkLogDaemon.bind(this, options),
_getSession,
_createLogFile,
_generateCmd,
function(next) {
// For id-filter with follow options, the pty option is required for ssh connection.
if (options.argv["id-filter"]) {
options.session.runWithOption(options.cmd, {pty:true}, process.stdin, _onData, process.stderr, next);
} else {
options.session.run(options.cmd, process.stdin, _onData, process.stderr, next);
}
},
], function(err) {
if (options.argv.save) {
next(err, {msg: "\nCreated " + savedFilePath + "\nSuccess"});
} else if (options.argv["id-filter"] && err && err[0].heading === "[ssh exec failure]:") {
next(errHndl.getErrMsg("NOT_MATCHED_LOG"));
} else {
next(err, null);
}
});
function _onData(data) {
log.info("log#show()#_onData()");
const strLogs = (Buffer.isBuffer(data)) ? data.toString() : data;
process.stdout.write(strLogs);
if (options.argv.save) {
fs.writeFileSync(savedFilePath, data, {encoding: 'utf8', flag:'a'});
}
}
function _getSession(next) {
log.info("log#show()#_getSession()");
if (options.display) {
options.returnWithError = true;
sessionLib.getSessionList(options, next);
} else {
next();
}
}
function _createLogFile(next) {
log.info("log#show()#_createLogFile()");
if (options.argv.save) {
createLogFile(options, next);
} else {
next();
}
}
function _makeSession(next) {
options.printTarget = false;
makeSession(options, next);
}
function _checkUser(next) {
checkUser(options, next);
}
function _generateCmd(next) {
generateCmd(options, next);
}
},
readMode: function(options, next) {
log.info("log#readMode()");
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options = options || {};
async.series([
_makeSession,
_checkUser,
this.checkLogDaemon.bind(this, options),
_getLogDir,
_generateCmd,
_createLogFile,
function(next) {
options.session.run(options.cmd, process.stdin, _onData, process.stderr, next);
},
], function(err) {
if (options.argv.save) {
next(err, {msg: "\nCreated " + savedFilePath + "\nSuccess"});
} else {
next(err, null);
}
});
function _onData(data) {
log.info("log#readMode()#_onData()");
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
console.log(str); // Do not remove
if (options.argv.save) {
fs.writeFileSync(savedFilePath, data, {encoding: 'utf8', flag:'a'});
}
}
function _getLogDir(next) {
log.info("log#readMode()#_getLogDir()");
const getLogDirCmd = "ls /run/log/journal/";
options.session.run(getLogDirCmd, process.stdin, _onDirData, process.stderr, next);
}
function _onDirData(data) {
log.info("log#readMode()#_onDirData()");
options.logDir = (Buffer.isBuffer(data)) ? data.toString() : data;
options.logDir = options.logDir.trim();
}
function _generateCmd(next) {
log.info("log#readMode()#_generateCmd()");
if (options.argv.file === "true") {
return next(errHndl.getErrMsg("EMPTY_FILENAME"));
}
if (options.argv.file) {
options.cmd = "journalctl --file /run/log/journal/" + options.logDir + "/" + options.argv.file;
if (options.argv.output) {
options.cmd += " --output " + options.argv.argv.remain;
}
}
if (options.argv['file-list']) {
options.cmd = "ls /run/log/journal/" + options.logDir;
}
log.verbose("log#readMode()#_generateCmd()", "options.cmd:" + options.cmd);
next();
}
function _createLogFile(next) {
log.info("log#readMode()#_createLogFile()");
if (options.argv.save) {
createLogFile(options, next);
} else {
next();
}
}
function _makeSession(next) {
options.printTarget = false;
makeSession(options, next);
}
function _checkUser(next) {
checkUser(options, next);
}
},
printUnitList: function(options, next) {
log.info("log#printUnitList()");
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options = options || {};
async.series([
_makeSession,
_checkUser,
this.checkLogDaemon.bind(this, options),
_getSession,
_generateCmd,
function(next) {
options.session.run(options.cmd, process.stdin, _onData, process.stderr, next);
},
], function(err) {
next(err, null);
});
function _onData(data) {
log.info("log#printUnitList()#_onData()");
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
const exp = /(\s\w[\S]*\.service)/g;
if (Array.isArray(str.match(exp))) {
str.match(exp).forEach(function(item) {
console.log(item.trim()); // Do not remove
});
}
}
function _getSession(next) {
log.info("log#printUnitList()#_getSession()");
if (options.display) {
options.returnWithError = true;
sessionLib.getSessionList(options, next);
} else {
next();
}
}
function _generateCmd(next) {
log.info("log#printUnitList()#_generateCmd()");
options.cmd = "systemctl list-units --type service";
if (options.sessionId) {
options.cmd = "systemctl --user list-units --type service";
options.cmd = `su ${options.sessionId} -l -c "${options.cmd}"`;
}
log.verbose("log#printUnitList()#_generateCmd()", "options.cmd:" + options.cmd);
next();
}
function _makeSession(next) {
options.printTarget = false;
makeSession(options, next);
}
function _checkUser(next) {
checkUser(options, next);
}
},
contextMode: function(options, next) {
log.info("log#contextMode()");
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options = options || {};
async.series([
_makeSession,
_checkUser,
this.checkLogDaemon.bind(this, options),
_getSession,
_generateCmd,
function(next) {
if (options.argv["context-list"]) {
options.session.run(options.cmd, process.stdin, _onListData, process.stderr, next);
} else if (options.argv["set-level"]) {
options.session.run(options.cmd, process.stdin, _onSetData, process.stderr, next);
}
},
], function(err) {
next(err, null);
});
function _onListData(data) {
log.info("log#contextMode()#_onListData()");
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
console.log(str.replace(/PmLogCtl: Context '(.*)'/g, '$1').trim());
}
function _onSetData(data) {
log.info("log#contextMode()#_onSetData()");
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
console.log(str.replace(/PmLogCtl: /g, '').trim());
}
function _getSession(next) {
log.info("log#contextMode()#_getSession()");
if (options.display) {
options.returnWithError = true;
sessionLib.getSessionList(options, next);
} else {
next();
}
}
function _generateCmd(next) {
log.info("log#contextMode()#_generateCmd()");
if (options.argv["context-list"]) {
options.cmd = "PmLogCtl show";
} else if (options.argv["set-level"]) {
reservedOption.forEach(function(item) {
idx = options.argv.argv.cooked.indexOf(item);
if (idx > -1) {
options.argv.argv.cooked.splice(idx, 1);
options.argv.argv.cooked.splice(idx, 1);
}
});
options.argv.argv.cooked.splice(0, 1);
options.cmd = `PmLogCtl set ${options.argv.argv.cooked.join(" ")}`;
}
log.verbose("log#contextMode()#_generateCmd()", "options.cmd:" + options.cmd);
next();
}
function _makeSession(next) {
options.printTarget = true;
makeSession(options, next);
}
function _checkUser(next) {
checkUser(options, next);
}
},
checkLogDaemon: function(options, next) {
log.info("log#checkLogDaemon()");
if (typeof next !== 'function') {
throw errHndl.getErrMsg("MISSING_CALLBACK", "next", util.inspect(next));
}
options = options || {};
let serviceParam = "",
cmd = "",
logFilePath = "",
returnData = "",
logDaemonStatus = false,
existLogFile = false;
async.series([
_makeSession,
_setparam,
function(next) {
options.session.run(cmd, process.stdin, _checkService, process.stderr, function(err) {
if (err) {
return next(err);
}
if (!logDaemonStatus) {
return next(errHndl.getErrMsg("NOT_MATCHED_LOGDAEMON", options.currentDaemon === "journald" ? "pmlogd" : "journald"));
}
cmd = `ls -al ${logFilePath}`;
options.session.run(cmd, process.stdin, _checkFile, process.stderr, function(error) {
if (error) {
return next(err);
}
if (!existLogFile) {
return next(errHndl.getErrMsg("NOT_EXIST_LOGFILE"));
}
next();
});
});
},
], function(err) {
if (err) {
console.log("CLI's current logging daemon : " + options.currentDaemon);
next(err, null);
} else {
next(null, {msg: "CLI's current logging daemon : " + options.currentDaemon
+ "\nThe target's current logging daemon : " + options.currentDaemon});
}
});
function _setparam(next) {
log.info("log#checkLogDaemon()#_setparam()");
if (options.currentDaemon === "journald") {
serviceParam = "systemd-journald";
logFilePath = "/run/log/journal";
} else if (options.currentDaemon === "pmlogd") {
serviceParam = "pm-log-daemon";
logFilePath = "/var/log/messages";
}
cmd = `systemctl status ${serviceParam}.service | tee`;
next();
}
function _checkService(data) {
log.info("log#checkLogDaemon()#_checkService()");
returnData += (Buffer.isBuffer(data)) ? data.toString() : data;
if (returnData.indexOf("active (running)") !== -1) {
logDaemonStatus = true;
}
}
function _checkFile(data) {
log.info("log#checkLogDaemon()#_checkFile()");
const str = (Buffer.isBuffer(data)) ? data.toString() : data;
if (str.length) {
existLogFile = true;
}
}
function _makeSession(next) {
options.printTarget = true;
makeSession(options, next);
}
}
};
function makeSession(options, next) {
if (!options.session) {
log.info("log#makeSession()", "need to make new session");
const printTarget = options.printTarget;
options.session = new novacom.Session(options.device, printTarget, next);
} else {
log.info("log#makeSession()", "already exist session");
next();
}
}
function checkUser(options, next) {
log.info("log#checkUser()", "username:", options.session.getDevice().username);
if (options.session.getDevice().username !== 'root') {
return next(errHndl.getErrMsg("NEED_ROOT_PERMISSION"));
} else {
next();
}
}
function generateCmd(options, next) {
log.info("log#generateCmd()", "options.currentDaemon:", options.currentDaemon);
if (options.currentDaemon === "pmlogd") {
const pmLogFilePath = " /var/log/messages";
if (options.argv.follow) {
options.cmd = "tail -f";
} else if (options.argv.lines) {
options.cmd = "tail";
} else {
options.cmd = "cat";
}
if (options.argv.lines) {
options.cmd += ` -n ${options.argv.lines}`;
}
options.cmd += pmLogFilePath;
if (options.argv["id-filter"]) {
// Handle when another option appears without id-filter value
const idReg = /^-/;
if (options.argv["id-filter"] === "true" || options.argv["id-filter"].match(idReg)) {
return next(errHndl.getErrMsg("EMPTY_VALUE", "id-filter"));
} else {
const convertReg = /\./g;
let filter = options.argv["id-filter"];
filter = filter.replace(convertReg, '\\.');
options.cmd += ` | grep -E "\\[(\\d*|\\d*\\:\\d*)\\] ${filter}"`;
}
}
} else if (options.currentDaemon === "journald") {
if (options.display && !options.argv.unit) {
return next(errHndl.getErrMsg("INVALID_COMBINATION"));
}
reservedOption.forEach(function(item) {
idx = options.argv.argv.cooked.indexOf(item);
if (idx > -1) {
options.argv.argv.cooked.splice(idx, 1);
options.argv.argv.cooked.splice(idx, 1);
}
});
if (options.argv.pid) {
idx = options.argv.argv.cooked.indexOf("-pid");
if (idx === -1) {
idx = options.argv.argv.cooked.indexOf("--pid");
}
if (idx > -1) {
options.argv.argv.cooked.splice(idx, 1, "_PID=");
}
}
if (options.argv.since) {
if (options.argv.argv.remain.length > 0) {
idx = options.argv.argv.cooked.indexOf("--since");
let sinceTmp = options.argv.argv.cooked[idx+1];
if (options.argv.argv.cooked[idx+2]) {
sinceTmp += " " + options.argv.argv.cooked[idx+2];
}
options.argv.argv.cooked.splice(idx+1, 2, "\"" + sinceTmp + "\"");
}
}
if (options.argv.until) {
if (options.argv.argv.remain.length > 0) {
idx = options.argv.argv.cooked.indexOf("--until");
let untilTmp = options.argv.argv.cooked[idx+1];
if (options.argv.argv.cooked[idx+2]) {
untilTmp += " " + options.argv.argv.cooked[idx+2];
}
options.argv.argv.cooked.splice(idx+1, 2, "\"" + untilTmp + "\"");
}
}
options.cmd = `journalctl ${options.argv.argv.cooked.join(" ")}`;
options.cmd = options.cmd.replace('_PID= ', '_PID=');
if (options.display && options.argv.unit) {
idx = options.argv.argv.cooked.indexOf("--unit");
options.argv.argv.cooked.splice(idx, 1, "--user-unit");
idx = options.argv.argv.cooked.indexOf("--display");
options.argv.argv.cooked.splice(idx, 2);
options.cmd = `journalctl ${options.argv.argv.cooked.join(" ")}`;
options.cmd = `su ${options.sessionId} -l -c "${options.cmd}"`;
}
}
log.verbose("log#generateCmd()", "options.cmd:", options.cmd);
next();
}
function createLogFile(options, next) {
log.info("log#createLogFile()");
const fileNameSeperator = "_",
fileExt = "log";
if (options.argv.argv.remain.length === 0) {
savedFilePath = path.resolve(createDateFileName(fileNameSeperator, fileExt));
} else {
if (path.extname(options.argv.argv.remain[0]) === "") {
options.argv.argv.remain[0] = options.argv.argv.remain[0] + "." + fileExt;
}
if (path.extname(options.argv.argv.remain[0]) === ".") {
options.argv.argv.remain[0] = options.argv.argv.remain[0] + fileExt;
}
savedFilePath = path.resolve(options.argv.argv.remain[0]);
}
log.verbose("log#createLogFile()", "savedFilePath:" + savedFilePath);
// For error handling of non exist path
fs.open(savedFilePath, 'w', function(err, fd) {
if (err) {
return next(errHndl.getErrMsg(err));
}
fs.closeSync(fd);
next();
});
// Defense code
if (fs.existsSync(savedFilePath)) {
fs.unlinkSync(savedFilePath);
}
log.verbose("log#createLogFile()", savedFilePath + " is exist: " + fs.existsSync(savedFilePath));
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = logLib;
}
}());