gristlabs_grist-core/app/server/lib/log.ts

90 lines
3.5 KiB
TypeScript
Raw Normal View History

/**
* Configures grist logging. This is merely a customization of the 'winston' logging module,
* and all winston methods are available. Additionally provides log.timestamp() function.
* Usage:
* var log = require('./lib/log');
* log.info(...);
*/
import {timeFormat} from 'app/common/timeFormat';
import * as winston from 'winston';
interface LogWithTimestamp extends winston.LoggerInstance {
timestamp(): string;
// We'd like to log raw json, for convenience of parsing downstream.
// We have a customization that interferes with meta arguments, and
// existing log messages that depend on that customization. For
// clarity then, we just add "raw" flavors of the primary level
// methods that pass their object argument through to winston.
rawError(msg: string, meta: ILogMeta): void;
rawInfo(msg: string, meta: ILogMeta): void;
rawWarn(msg: string, meta: ILogMeta): void;
rawDebug(msg: string, meta: ILogMeta): void;
origLog(level: string, msg: string, ...args: any[]): void;
}
/**
* Hack winston to provide a saner behavior with regard to its optional arguments. Winston allows
* two optional arguments at the end: "meta" (if object) and "callback" (if function). We don't
* use them, but we do use variable number of arguments as in log.info("foo %s", foo). If foo is
* an object, winston dumps it in an ugly way, not at all as intended. We fix by always appending
* {} to the end of the arguments, so that winston sees an empty meta object.
* We can add support for callback if ever needed.
*/
const origLog = winston.Logger.prototype.log;
winston.Logger.prototype.log = function(level: string, msg: string, ...args: any[]) {
return origLog.call(this, level, msg, ...args, {});
};
const rawLog = new (winston.Logger)();
const log: LogWithTimestamp = Object.assign(rawLog, {
timestamp,
/**
* Versions of log.info etc that take a meta parameter. For
* winston, logs are streams of info objects. Info objects
* have two mandatory fields, level and message. They can
* have other fields, called "meta" fields. When logging
* in json, those fields are added directly to the json,
* rather than stringified into the message field, which
* is what we want and why we are adding these variants.
*/
rawError: (msg: string, meta: ILogMeta) => origLog.call(log, 'error', msg, meta),
rawInfo: (msg: string, meta: ILogMeta) => origLog.call(log, 'info', msg, meta),
rawWarn: (msg: string, meta: ILogMeta) => origLog.call(log, 'warn', msg, meta),
rawDebug: (msg: string, meta: ILogMeta) => origLog.call(log, 'debug', msg, meta),
origLog,
});
/**
* Returns the current timestamp as a string in the same format as used in logging.
*/
function timestamp() {
return timeFormat("A", new Date());
}
const fileTransportOptions = {
stream: process.stderr,
level: 'debug',
timestamp: log.timestamp,
colorize: true,
json: process.env.GRIST_HOSTED_VERSION ? true : false
};
// Configure logging to use console and simple timestamps.
log.add(winston.transports.File, fileTransportOptions);
// Also update the default logger to use the same format.
winston.remove(winston.transports.Console);
winston.add(winston.transports.File, fileTransportOptions);
// It's a little tricky to export a type when the top-level export is an object.
// tslint:disable-next-line:no-namespace
declare namespace log { // eslint-disable-line @typescript-eslint/no-namespace
interface ILogMeta {
[key: string]: any;
}
}
type ILogMeta = log.ILogMeta;
export = log;