@mieweb/wikigdrive
Version:
Google Drive to MarkDown synchronization
186 lines (162 loc) • 5.23 kB
text/typescript
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;
}