log.ts 3.72 KB
Newer Older
liuzhe-lz's avatar
liuzhe-lz committed
1
2
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
Deshui Yu's avatar
Deshui Yu committed
3

4
5
6
7
8
9
10
11
12
/**
 *  Python-like logging interface.
 *
 *      const logger = getLogger('moduleName');
 *      logger.info('hello', { to: 'world' });
 *
 *  Outputs:
 *
 *      [1970-01-01 00:00:00] INFO (moduleName) hello { to: 'world' }
13
14
15
 *
 *  Loggers use `util.inspect()` to format values,
 *  so objects will be smartly stringified and exceptions will include stack trace.
16
 **/
17

18
import util from 'util';
19

20
import globals from 'common/globals';
21

22
const levelNameToValue = { trace: 0, debug: 10, info: 20, warning: 30, error: 40, critical: 50 } as const;
Deshui Yu's avatar
Deshui Yu committed
23

24
const loggers: Record<string, Logger> = {};
Deshui Yu's avatar
Deshui Yu committed
25

26
27
28
29
30
31
export function getLogger(name: string): Logger {
    if (loggers[name] === undefined) {
        loggers[name] = new Logger(name);
    }
    return loggers[name];
}
Deshui Yu's avatar
Deshui Yu committed
32

33
34
35
36
37
38
39
40
41
42
/**
 *  A special logger prints to stderr when the logging system has problems.
 *  For modules that are responsible for handling logger errors.
 **/
export function getRobustLogger(name: string): Logger {
    if (loggers[name] === undefined || !(loggers[name] as RobustLogger).robust) {
        loggers[name] = new RobustLogger(name);
    }
    return loggers[name];
}
Deshui Yu's avatar
Deshui Yu committed
43

44
export class Logger {
45
    protected name: string;
46

47
    constructor(name: string) {
48
        this.name = name;
Deshui Yu's avatar
Deshui Yu committed
49
50
    }

51
    public trace(...args: any[]): void {
52
        this.log(levelNameToValue.trace, 'TRACE', args);
53
    }
54

55
    public debug(...args: any[]): void {
56
        this.log(levelNameToValue.debug, 'DEBUG', args);
57
    }
SparkSnail's avatar
SparkSnail committed
58

59
    public info(...args: any[]): void {
60
        this.log(levelNameToValue.info, 'INFO', args);
61
62
    }

63
    public warning(...args: any[]): void {
64
        this.log(levelNameToValue.warning, 'WARNING', args);
Deshui Yu's avatar
Deshui Yu committed
65
66
    }

67
    public error(...args: any[]): void {
68
        this.log(levelNameToValue.error, 'ERROR', args);
69
70
    }

71
    public critical(...args: any[]): void {
72
        this.log(levelNameToValue.critical, 'CRITICAL', args);
Deshui Yu's avatar
Deshui Yu committed
73
74
    }

75
76
77
78
79
    protected log(levelValue: number, levelName: string, args: any[]): void {
        if (levelValue >= levelNameToValue[globals.args.logLevel]) {
            const msg = `[${timestamp()}] ${levelName} (${this.name}) ${formatArgs(args)}`;
            globals.logStream.writeLine(msg);
        }
Deshui Yu's avatar
Deshui Yu committed
80
    }
81
}
Deshui Yu's avatar
Deshui Yu committed
82

83
84
85
86
87
88
89
class RobustLogger extends Logger {
    public readonly robust: boolean = true;
    private errorOccurred: boolean = false;

    protected log(levelValue: number, levelName: string, args: any[]): void {
        if (this.errorOccurred) {
            this.logAfterError(levelName, args);
90
            return;
Deshui Yu's avatar
Deshui Yu committed
91
        }
92
93
94
95
        try {
            if (levelValue >= levelNameToValue[globals.args.logLevel]) {
                const msg = `[${timestamp()}] ${levelName} (${this.name}) ${formatArgs(args)}`;
                globals.logStream.writeLineSync(msg);
liuzhe-lz's avatar
liuzhe-lz committed
96
            }
97
98
99
100
        } catch (error) {
            this.errorOccurred = true;
            console.error('[ERROR] Logger has stopped working:', error);
            this.logAfterError(levelName, args);
J-shang's avatar
J-shang committed
101
        }
Deshui Yu's avatar
Deshui Yu committed
102
103
    }

104
105
106
107
108
    private logAfterError(levelName: string, args: any[]): void {
        try {
            args = args.map(arg => util.inspect(arg));
        } catch { /* fallback */ }
        console.error(`[${levelName}] (${this.name})`, ...args);
Deshui Yu's avatar
Deshui Yu committed
109
    }
110
111
}

112
113
114
115
116
function timestamp(): string {
    const now = new Date();
    const date = now.getFullYear() + '-' + zeroPad(now.getMonth() + 1) + '-' + zeroPad(now.getDate());
    const time = zeroPad(now.getHours()) + ':' + zeroPad(now.getMinutes()) + ':' + zeroPad(now.getSeconds());
    return date + ' ' + time;
Deshui Yu's avatar
Deshui Yu committed
117
118
}

119
120
function zeroPad(num: number): string {
    return num.toString().padStart(2, '0');
Deshui Yu's avatar
Deshui Yu committed
121
122
}

123
124
function formatArgs(args: any[]): string {
    return args.map(arg => (typeof arg === 'string' ? arg : util.inspect(arg))).join(' ');
liuzhe-lz's avatar
liuzhe-lz committed
125
}