iobroker.js-controller
Version: 
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
145 lines (133 loc) • 4.93 kB
JavaScript
;
// const CLI = require('./messages.js');
const CLICommand = require('./cliCommand.js');
const { getConfigFileName } = require('../tools');
const chokidar = require('chokidar');
const fs = require('fs-extra');
const os = require('os');
const es = require('event-stream');
const tools = require('../tools');
/** Command ioBroker state ... */
module.exports = class CLILogs extends CLICommand {
    /** @param {import('./cliCommand').CLICommandOptions} options */
    constructor(options) {
        super(options);
        /** @type {Map<string, number>} */
        this.fileSizes = new Map();
        this.isReady = false;
    }
    /**
     * Executes a command
     * @param {any[]} args
     */
    execute(args, params) {
        /** @type {string | undefined} */
        const adapterName = args[0];
        const watch = params.watch || params.w;
        const count = params.lines || 1000;
        /** @type {CLILogsOptions} */
        const options = {
            complete: this.options['all'],
            adapterName
        };
        const config = fs.readJSONSync(require.resolve(getConfigFileName()));
        const logger = require('../logger')(config.log);
        let fileName = logger.getFileName();
        if (fileName) {
            let lines = fs.readFileSync(fileName).toString('utf-8').split('\n');
            lines = lines.filter(line => line);
            if (lines.length > count) {
                lines.splice(0, lines.length - count);
            }
            let regex;
            if (adapterName) {
                //2019-03-02 13:26:54.698  - debug: iot.0 [ALEXA] Created ALEXA device: Bad.Hauptlicht.Aktor.STATE ["turnOn","turnOff"]
                regex = new RegExp(': ' + adapterName + '\\.');
            }
            lines.forEach(line => {
                if (regex && !regex.test(line)) {
                    return;
                }
                console.log(line);
            });
            if (watch) {
                fileName = fileName.replace(/\\/g, '/');
                const parts = fileName.split('/');
                parts.pop();
                chokidar.watch(`${parts.join('/')}/iobroker*`, {awaitWriteFinish: {stabilityThreshold: 500}})
                    .on('all', this.watchHandler.bind(this, options))
                    .on('ready', () => this.isReady = true)
                ;
            }
        } else {
            console.log('No log file found');
        }
    }
    /**
     * @typedef CLILogsOptions
     * @property {boolean} [complete] Whether to show today's full log
     * @property {string} [adapterName] An optional adapter name to filter by
     */
    /**
     * Called by chokidar when watched files change
     * @param {CLILogsOptions} options some options
     * @param {string} event The type of change
     * @param {*} path Which path has changed
     * @param {*} stats Information about the file
     */
    watchHandler(options, event, path, stats) {
        if (event === 'add' || !this.fileSizes.has(path)) {
            this.fileSizes.set(path, stats.size);
            if (
                stats.size > 0 && (
                    this.isReady
                    || (options.complete && this.isTodaysLogfile(path))
                )
            ) {
                this.streamChange(path, 0, options);
            }
        } else if (event === 'change') {
            const oldFileSize = this.fileSizes.get(path);
            this.fileSizes.set(path, stats.size);
            if (this.isReady && stats.size > oldFileSize) {
                this.streamChange(path, oldFileSize, options);
            }
        } else if (event === 'unlink') {
            this.fileSizes.delete(path);
        }
    }
    /**
     * If the log file belongs to today
     * @param {string} path The log file path
     */
    isTodaysLogfile(path) {
        const YYYYMMDDDate = new Date().toJSON().slice(0, 10);
        return path.indexOf(YYYYMMDDDate) > -1;
    }
    /**
     * Streams a portion of a file to the console
     * @param {string} path The file to stream
     * @param {number} start The offset in bytes where to start
     * @param {CLILogsOptions} options some options
     */
    streamChange(path, start, options) {
        const input = fs.createReadStream(path, {
            encoding: 'utf8',
            start: start,
            autoClose: true
        });
        if (options.adapterName) {
            // Read the input line by line and only include the lines matching the filter
            input
                .pipe(es.split())
                // @ts-ignore
                .pipe(es.filterSync(line => line.indexOf(options.adapterName) > -1))
                .pipe(es.mapSync(line => line + os.EOL))
                .pipe(process.stdout)
            ;
        } else {
            // just pipe the input through
            tools.pipeLinewise(input, process.stdout);
        }
    }
};