UNPKG

@mieweb/wikigdrive

Version:

Google Drive to MarkDown synchronization

186 lines (162 loc) 5.23 kB
import winston from 'winston'; import {EventEmitter} from 'node:events'; import path from 'node:path'; import {DailyRotateFile} from './DailyRotateFile.ts'; import {ansi_colors} from './colors.ts'; import {JobLogFile} from './JobLogFile.ts'; const __dirname = import.meta.dirname; const PROJECT_ROOT = path.resolve(__dirname, '../../..'); function getStackInfo(stackIndex, stackOffset = 0) { // get call stack, and analyze it // get all file, method, and line numbers const stackList = (new Error()).stack.split('\n') .filter(line => line.trim() !== 'Error') .filter(line => line.indexOf('src/utils/logger/logger.ts') == -1) .filter(line => line.indexOf('src/telemetry.ts') === -1) .slice(3 + stackOffset); // stack trace format: // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi // do not remove the regex expresses to outside this method (due to a BUG in node.js) const stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi; const stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi; const s = stackList[stackIndex] || stackList[0]; const sp = stackReg.exec(s) || stackReg2.exec(s); if (sp && sp.length === 5) { sp[2] = sp[2].replace('file://', ''); return { method: sp[1], path: sp[2], line: sp[3], pos: sp[4], file: path.basename(sp[2]), stack: stackList.join('\n') }; } } const myFormat = (formatConfig = { console: false }) => winston.format.printf((params) => { params.message = params.message || ''; const { level, timestamp } = params; let { filename } = params; let errorStr = ''; if (level === 'error' || level === 'warn') { if (params.message) { errorStr += params.message; } if (params.stack && !params.message.match(/^[\s]+at /g)) { errorStr += ' ' + params.stack; } } else { errorStr += params.message; } if (filename) { filename = path.relative(PROJECT_ROOT, filename); } if ('/index.js' === filename) { filename = null; } if (formatConfig.console) { errorStr = errorStr.split('\n') .map(line => line.indexOf('node_modules') === -1 ? line : ansi_colors.gray(line)) .join('\n'); } if (filename) { return `${timestamp} [${level}] (${filename}): ${errorStr}`; } else { return `${timestamp} [${level}]: ${errorStr}`; } }); export function instrumentLogger(logger, childOpts = {}) { for (const funcName of ['info', 'error', 'warn']) { const originMethod = logger[funcName]; logger[funcName] = (msg, payload) => { const stackInfo = getStackInfo(0, +(payload?.stackOffset)); if (!payload?.filename && stackInfo) { let filename = path.relative(PROJECT_ROOT, stackInfo.path); if (stackInfo.line) { filename += ':' + stackInfo.line; if (stackInfo.pos) { filename += ':' + stackInfo.pos; } } payload = Object.assign({}, childOpts, payload, { filename }); } originMethod.apply(logger, [msg, Object.assign({}, childOpts, payload)]); return logger; }; } const originMethod = logger.child; logger.child = (opts) => { const childLogger = originMethod.apply(logger, [opts]); instrumentLogger(childLogger, opts); return childLogger; }; } export function createLogger(workdir: string, eventBus?: EventEmitter) { const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), myFormat() ), defaultMeta: {}, transports: [ // // - Write all logs with level `error` and below to `error.log` // - Write all logs with level `info` and below to `combined.log` // ], }); logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), myFormat({ console: true }) ) })); const dirname = path.join(workdir, '%driveId%', '.logs'); logger.add(new JobLogFile({ format: winston.format.json(), zippedArchive: true, dirname: dirname, filename: 'job-%JOB_ID%.log' })); logger.add(new DailyRotateFile({ format: winston.format.json(), zippedArchive: true, maxSize: '20m', maxFiles: '14d', dirname: dirname, createSymlink: true, symlinkName: 'error.log', filename: '%DATE%-error.log', level: 'error', json: true })); logger.add(new DailyRotateFile({ format: winston.format.json(), zippedArchive: true, maxSize: '20m', maxFiles: '14d', dirname: dirname, createSymlink: true, symlinkName: 'combined.log', filename: '%DATE%-combined.log', json: true })); instrumentLogger(logger); process .on('unhandledRejection', async (reason: any) => { console.error('unhandledRejection', reason); logger.error('unhandledRejection: ' + reason.stack ? reason.stack : reason.message, reason); if (eventBus && reason?.response?.data?.error === 'invalid_grant') { eventBus.emit('panic:invalid_grant'); return; } }) .on('uncaughtException', err => { console.error('uncaughtException', err); logger.error('Uncaught Exception thrown', err); }); return logger; }