import { create as createLog, format } from '@tdb/log/lib/server'; import { moment } from '@tdb/util'; import { app } from 'electron'; import * as is from 'electron-is'; import * as elog from 'electron-log'; import * as fs from 'fs-extra'; import { dirname, join } from 'path'; import { filter, map } from 'rxjs/operators'; import { IpcClient } from '../ipc/Client'; import { LogEvent, LoggerEvents, LogLevel, ProcessType } from './types'; export { moment, id } from '@tdb/util'; type ILogMetadata = { start: { dev: number; prod: number } }; type Env = 'prod' | 'dev'; let isInitialized = false; export const log = createLog(); /** * Configure local logging on the [main] process. */ export function init(args: { ipc: IpcClient; dir?: string }) { if (isInitialized) { return log; } isInitialized = true; const ipc = args.ipc as IpcClient; // Derive log location and store it. const paths = getPaths({ dir: args.dir || join(dirname(app.getPath('logs')), app.getName()), }); const { dir, env } = paths; fs.ensureDirSync(dir); // Configure the file transport. elog.transports.file.format = '{h}:{i}:{s} {text}'; elog.transports.file.file = env === 'prod' ? paths.prod.path : paths.dev.path; elog.transports.file.level = 'info'; // Turn off console logging - handled below. elog.transports.console.level = false; const logToConsole = (...items: any) => console.log.apply(null, items); // tslint:disable-line const logToConsoleGray = (msg: string) => logToConsole(log.gray(msg)); // Alert user of log location. const tailPath = (file: string, color?: boolean) => { file = color ? log.cyan(file) : file; return log.gray(`${dir}/${file}`); }; const tailCommand = (file: string, color?: boolean) => { let msg = 'tail -f'; msg = color ? log.white(msg) : msg; msg = `${msg} ${tailPath(file, color)}`; return log.gray(msg); }; if (is.dev()) { logToConsole(); logToConsoleGray(`logs | ${tailCommand(paths.dev.filename, true)}`); logToConsoleGray(` ${tailPath(paths.prod.filename)}`); logToConsole(); } // Write events to logs. const write = (process: ProcessType, level: LogLevel, output: string) => { const prefix = toPrefix(process); elog[level](prefix, output); if (is.dev() && process === 'MAIN') { // NB: Don't worry about writing VIEW renderer logs to the console // This is handled by the base [@tdb/log]. logToConsole(prefix, output); } }; // Pass log events to the electron-log. log.events$ .pipe( filter(() => !log.silent), filter(e => e.type === 'LOG'), map(e => e.payload as LogEvent), ) .subscribe(e => write('MAIN', e.level, e.output)); // Listen for log events from the [renderer] process // and write them to the log. ipc.events$ .pipe( filter(e => e.type === 'LOG/write'), filter(e => e.sender.process === 'RENDERER'), map(e => e.payload), ) .subscribe(e => write('RENDERER', e.level, format(e))); // Insert a "startup" divider into log to visually chunk into sessions. const count = increment({ dir, env }); const div = () => { let msg = `(${log.blue(count)})`; msg = `------------------------------------------------------ ${msg}`; msg = log.gray(msg); return msg; }; elog.info(''); elog.info(log.gray(`${moment().format('D MMM YYYY')} [${env}]`)); elog.info(div()); elog.info(''); // Finish up. return log; } /** * Derives paths for the logger. */ export function getPaths(args: { dir: string }) { const IS_DEV = is.dev(); const env: Env = IS_DEV ? 'dev' : 'prod'; const dir = args.dir; const file = { dev: 'dev.log', prod: 'prod.log', }; const dev = { filename: file.dev, path: join(dir, file.dev), }; const prod = { filename: file.prod, path: join(dir, file.prod), }; return { env, dir, dev, prod }; } /** * INTERNAL */ function increment(args: { dir: string; env: 'dev' | 'prod' }) { const read = () => { const DEFAULT: ILogMetadata = { start: { dev: 0, prod: 0 } }; try { return fs.pathExistsSync(path) ? (fs.readJsonSync(path) as ILogMetadata) : DEFAULT; } catch (error) { return DEFAULT; } }; const { dir, env } = args; const path = join(dir, 'meta.json'); const data = read(); data.start[env] += 1; fs.writeFileSync(path, JSON.stringify(data, null, ' ')); return data.start[env]; } const toPrefix = (process: ProcessType) => { const isMain = process === 'MAIN'; let prefix = isMain ? 'MAIN' : 'VIEW'; prefix = `${prefix} ‣`; prefix = `${prefix} `.substr(0, 6); prefix = isMain ? log.green(prefix) : log.magenta(prefix); return prefix; }; // const toPrefix = (process: ProcessType) => { // let prefix = process === 'MAIN' ? 'MAIN' : 'VIEW'; // prefix = `[${prefix}]`; // prefix = `${prefix} `.substr(0, 6); // prefix = log.gray(prefix); // return prefix; // };