All files / src/core/loggers winstonLokiLogger.ts

84.21% Statements 32/38
61.11% Branches 11/18
54.54% Functions 6/11
90.9% Lines 30/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 988x 8x   8x 8x     8x 4x                       4x   4x 166x 166x   558x       166x         4x       4x                 4x 4x 4x 4x 4x 4x   4x 4x             4x 4x 4x     4x       4x                 128x       36x       2x                
import winston, { createLogger, transport } from 'winston';
import LokiTransport from 'winston-loki';
import ILogger from '../../core/contracts/ILogger';
import { injectable } from 'inversify';
import Constants from '../../constants';
 
@injectable()
export default class WinstonLokiLogger implements ILogger {
    private readonly labels = {
        job: 'unixpense',
        baseUrl: Constants.baseUrl,
        
        ...(process.env.VERSION !== undefined) && {
            version: process.env.VERSION
        }
    } as const;
    
    private readonly logger: winston.Logger;
    
    public constructor() {
        const level = process.env.LOG_LEVEL ?? 'info';
 
        const consolePrintHandler = (info: winston.Logform.TransformableInfo): string => {
            const { message, level, stack, labels } = info;
            const labelStrings = labels && typeof labels === 'object'
                ? Object.entries(labels as Record<string, unknown>)
                    .map(([k, v]) => `\n\t[${k}]\t${v}`)
                    .join('')
                : '';
 
            return 'stack' in info
                ? `${level}:\t${message}\n${stack}${labelStrings}`
                : `${level}:\t${message}${labelStrings}`;
        };
 
        const lokiPrintHandler = (info: winston.Logform.TransformableInfo): string => 'stack' in info
            ? `${info.message}\n${info.stack}`
            : `${info.message}`;
 
        const colorOptions = {
            message: true,
            colors: {
                info: 'white',
                warn: 'yellow',
                error: 'red'
            }
        };
 
        const errorFormat = winston.format.errors({ stack: true });
        const colorFormat = winston.format.colorize(colorOptions);
        const noColorFormat = winston.format.uncolorize();
        const consoleFormat = winston.format.printf((consolePrintHandler));
        const mainFormat = winston.format.combine(errorFormat, colorFormat, consoleFormat);
        const lokiFormat = winston.format.printf(lokiPrintHandler);
 
        const consoleOptions = { format: noColorFormat };
        const lokiOptions = {
            host: process.env.LOKI_HOST!,
            batching: false,
            gracefulShutdown: true,
            format: lokiFormat
        };
 
        const consoleTransportUncolorized = new winston.transports.Console(consoleOptions);
        const consoleTransport = new winston.transports.Console();
        const lokiTransport = process.env.LOKI_HOST !== undefined
            ? new LokiTransport(lokiOptions)
            : undefined;
        const transports = process.env.NODE_ENV === 'production'
            ? [consoleTransportUncolorized, lokiTransport].filter(t => t !== undefined) as transport[]
            : consoleTransport;
 
        this.logger = createLogger({
            exitOnError: false,
            level: level,
            format: mainFormat,
            transports: transports
        });
    }
 
    public log(message: string, labels?: Record<string, unknown>) {
        this.logger.info(message, { labels: { ...this.labels, ...labels } });
    }
 
    public warn(message: string, labels?: Record<string, unknown>) {
        this.logger.warn(message, { labels: { ...this.labels, ...labels } });
    }
 
    public error(error: Error, labels?: Record<string, unknown>) {
        this.logger.log('error', { message: error, labels: { ...this.labels, ...labels } });
    }
 
    public beforeExit() {
        this.logger.end();
 
        return new Promise<void>((resolve) => this.logger.on('finish', () => resolve()));
    }
}