/** * 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: process.env.GRIST_LOG_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;