log.ts 4.16 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

'use strict';

import * as fs from 'fs';
import { Writable } from 'stream';
import { WritableStreamBuffer } from 'stream-buffers';
import { format } from 'util';
import * as component from '../common/component';
SparkSnail's avatar
SparkSnail committed
11
import { getExperimentStartupInfo, isReadonly } from './experimentStartupInfo';
Deshui Yu's avatar
Deshui Yu committed
12

13
const FATAL: number = 1;
Deshui Yu's avatar
Deshui Yu committed
14
15
16
17
const ERROR: number = 2;
const WARNING: number = 3;
const INFO: number = 4;
const DEBUG: number = 5;
18
19
const TRACE: number = 6;

20
21
22
23
24
25
26
27
const logLevelNameMap: Map<string, number> = new Map([
    ['fatal', FATAL],
    ['error', ERROR],
    ['warning', WARNING],
    ['info', INFO],
    ['debug', DEBUG],
    ['trace', TRACE]
]);
Deshui Yu's avatar
Deshui Yu committed
28
29
30
31
32
33
34

class BufferSerialEmitter {
    private buffer: Buffer;
    private emitting: boolean;
    private writable: Writable;

    constructor(writable: Writable) {
Zejun Lin's avatar
Zejun Lin committed
35
        this.buffer = Buffer.alloc(0);
Deshui Yu's avatar
Deshui Yu committed
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
        this.emitting = false;
        this.writable = writable;
    }

    public feed(buffer: Buffer): void {
        this.buffer = Buffer.concat([this.buffer, buffer]);
        if (!this.emitting) {
            this.emit();
        }
    }

    private emit(): void {
        this.emitting = true;
        this.writable.write(this.buffer, () => {
            if (this.buffer.length === 0) {
                this.emitting = false;
            } else {
                this.emit();
            }
        });
Zejun Lin's avatar
Zejun Lin committed
56
        this.buffer = Buffer.alloc(0);
Deshui Yu's avatar
Deshui Yu committed
57
58
59
60
61
    }
}

@component.Singleton
class Logger {
chicm-ms's avatar
chicm-ms committed
62
    private level: number = INFO;
63
64
    private bufferSerialEmitter?: BufferSerialEmitter;
    private writable?: Writable;
SparkSnail's avatar
SparkSnail committed
65
    private readonly: boolean = false;
Deshui Yu's avatar
Deshui Yu committed
66
67

    constructor(fileName?: string) {
68
69
70
71
72
73
74
75
        const logFile: string | undefined = fileName;
        if (logFile) {
            this.writable = fs.createWriteStream(logFile, {
                flags: 'a+',
                encoding: 'utf8',
                autoClose: true
            });
            this.bufferSerialEmitter = new BufferSerialEmitter(this.writable);
Deshui Yu's avatar
Deshui Yu committed
76
        }
77
78
79
80
81
82
83

        const logLevelName: string = getExperimentStartupInfo()
                                    .getLogLevel();
        const logLevel: number | undefined = logLevelNameMap.get(logLevelName);
        if (logLevel !== undefined) {
            this.level = logLevel;
        }
SparkSnail's avatar
SparkSnail committed
84
85

        this.readonly = isReadonly();
86
87
    }

chicm-ms's avatar
chicm-ms committed
88
    public close(): void {
89
90
91
        if (this.writable) {
            this.writable.destroy();
        }
Deshui Yu's avatar
Deshui Yu committed
92
93
    }

94
95
96
97
98
99
    public trace(...param: any[]): void {
        if (this.level >= TRACE) {
            this.log('TRACE', param);
        }
    }

Deshui Yu's avatar
Deshui Yu committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    public debug(...param: any[]): void {
        if (this.level >= DEBUG) {
            this.log('DEBUG', param);
        }
    }

    public info(...param: any[]): void {
        if (this.level >= INFO) {
            this.log('INFO', param);
        }
    }

    public warning(...param: any[]): void {
        if (this.level >= WARNING) {
            this.log('WARNING', param);
        }
    }

    public error(...param: any[]): void {
        if (this.level >= ERROR) {
            this.log('ERROR', param);
        }
    }

124
125
    public fatal(...param: any[]): void {
        this.log('FATAL', param);
Deshui Yu's avatar
Deshui Yu committed
126
    }
SparkSnail's avatar
SparkSnail committed
127
128
129
130
131
132
    
    /**
     * if the experiment is not in readonly mode, write log content to stream
     * @param level log level
     * @param param the params to be written
     */
Deshui Yu's avatar
Deshui Yu committed
133
    private log(level: string, param: any[]): void {
SparkSnail's avatar
SparkSnail committed
134
        if (!this.readonly) {
135
136
137
138
            const time = new Date();
            const localTime = new Date(time.getTime() - time.getTimezoneOffset() * 60000);
            const timeStr = localTime.toISOString().slice(0, -5).replace('T', ' ');
            const logContent = `[${timeStr}] ${level} ${format(param)}\n`;
139
140
141
142
143
144
145
146
            if (this.writable && this.bufferSerialEmitter) {
                const buffer: WritableStreamBuffer = new WritableStreamBuffer();
                buffer.write(logContent);
                buffer.end();
                this.bufferSerialEmitter.feed(buffer.getContents());
            } else {
                console.log(logContent);
            }
SparkSnail's avatar
SparkSnail committed
147
        }
Deshui Yu's avatar
Deshui Yu committed
148
149
150
    }
}

SparkSnail's avatar
SparkSnail committed
151
function getLogger(): Logger {
Deshui Yu's avatar
Deshui Yu committed
152
153
154
    return component.get(Logger);
}

chicm-ms's avatar
chicm-ms committed
155
export { Logger, getLogger, logLevelNameMap };