This also changes timeout error format to "page.click: Timeout 5000ms exceeded", so that all errors can be similarly prefixed with api name. We can now have different api names in different clients, and our protocol is more reasonable.
177 lines
5.2 KiB
TypeScript
177 lines
5.2 KiB
TypeScript
/**
|
|
* Copyright Microsoft Corporation. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import * as debug from 'debug';
|
|
import { helper } from './helper';
|
|
import { LoggerSink, LoggerSeverity } from './loggerSink';
|
|
|
|
export function logError(logger: Logger): (error: Error) => void {
|
|
return error => logger.error(error);
|
|
}
|
|
|
|
export class Logger {
|
|
private _loggerSink: LoggerSink;
|
|
private _name: string;
|
|
private _hints: { color?: string; };
|
|
private _scopeName: string | undefined;
|
|
private _recording: string[] | undefined;
|
|
|
|
constructor(loggerSink: LoggerSink, name: string, hints: { color?: string }, scopeName?: string, record?: boolean) {
|
|
this._loggerSink = loggerSink;
|
|
this._name = name;
|
|
this._hints = hints;
|
|
this._scopeName = scopeName;
|
|
if (record)
|
|
this._recording = [];
|
|
}
|
|
|
|
isEnabled(severity?: LoggerSeverity): boolean {
|
|
return this._loggerSink.isEnabled(this._name, severity || 'info');
|
|
}
|
|
|
|
verbose(message: string, ...args: any[]) {
|
|
return this._innerLog('verbose', message, args);
|
|
}
|
|
|
|
info(message: string, ...args: any[]) {
|
|
return this._innerLog('info', message, args);
|
|
}
|
|
|
|
warn(message: string, ...args: any[]) {
|
|
return this._innerLog('warning', message, args);
|
|
}
|
|
|
|
error(message: string | Error, ...args: any[]) {
|
|
return this._innerLog('error', message, args);
|
|
}
|
|
|
|
createScope(scopeName: string | undefined, record?: boolean): Logger {
|
|
if (scopeName)
|
|
this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints);
|
|
return new Logger(this._loggerSink, this._name, this._hints, scopeName, record);
|
|
}
|
|
|
|
endScope(status: string) {
|
|
if (this._scopeName)
|
|
this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints);
|
|
}
|
|
|
|
private _innerLog(severity: LoggerSeverity, message: string | Error, ...args: any[]) {
|
|
if (this._recording)
|
|
this._recording.push(`[${this._name}] ${message}`);
|
|
this._loggerSink.log(this._name, severity, message, args, this._hints);
|
|
}
|
|
|
|
recording(): string[] {
|
|
return this._recording ? this._recording.slice() : [];
|
|
}
|
|
}
|
|
|
|
export class Loggers {
|
|
readonly api: Logger;
|
|
readonly browser: Logger;
|
|
readonly protocol: Logger;
|
|
|
|
constructor(userSink: LoggerSink | undefined) {
|
|
const loggerSink = new MultiplexingLoggerSink();
|
|
if (userSink)
|
|
loggerSink.add('user', userSink);
|
|
if (helper.isDebugMode())
|
|
loggerSink.add('pwdebug', new PwDebugLoggerSink());
|
|
loggerSink.add('debug', new DebugLoggerSink());
|
|
|
|
this.api = new Logger(loggerSink, 'api', { color: 'cyan' });
|
|
this.browser = new Logger(loggerSink, 'browser', {});
|
|
this.protocol = new Logger(loggerSink, 'protocol', { color: 'green' });
|
|
}
|
|
}
|
|
|
|
class MultiplexingLoggerSink implements LoggerSink {
|
|
private _loggers = new Map<string, LoggerSink>();
|
|
|
|
add(id: string, logger: LoggerSink) {
|
|
this._loggers.set(id, logger);
|
|
}
|
|
|
|
get(id: string): LoggerSink | undefined {
|
|
return this._loggers.get(id);
|
|
}
|
|
|
|
remove(id: string) {
|
|
this._loggers.delete(id);
|
|
}
|
|
|
|
isEnabled(name: string, severity: LoggerSeverity): boolean {
|
|
for (const logger of this._loggers.values()) {
|
|
if (logger.isEnabled(name, severity))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) {
|
|
for (const logger of this._loggers.values()) {
|
|
if (logger.isEnabled(name, severity))
|
|
logger.log(name, severity, message, args, hints);
|
|
}
|
|
}
|
|
}
|
|
|
|
const colorMap = new Map<string, number>([
|
|
['red', 160],
|
|
['green', 34],
|
|
['yellow', 172],
|
|
['blue', 33],
|
|
['magenta', 207],
|
|
['cyan', 45],
|
|
['reset', 0],
|
|
]);
|
|
|
|
class DebugLoggerSink {
|
|
private _debuggers = new Map<string, debug.IDebugger>();
|
|
|
|
isEnabled(name: string, severity: LoggerSeverity): boolean {
|
|
return debug.enabled(`pw:${name}`);
|
|
}
|
|
|
|
log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) {
|
|
let cachedDebugger = this._debuggers.get(name);
|
|
if (!cachedDebugger) {
|
|
cachedDebugger = debug(`pw:${name}`);
|
|
this._debuggers.set(name, cachedDebugger);
|
|
|
|
let color = hints.color || 'reset';
|
|
switch (severity) {
|
|
case 'error': color = 'red'; break;
|
|
case 'warning': color = 'yellow'; break;
|
|
}
|
|
const escaped = colorMap.get(color) || 0;
|
|
if (escaped)
|
|
(cachedDebugger as any).color = String(escaped);
|
|
}
|
|
cachedDebugger(message, ...args);
|
|
}
|
|
}
|
|
|
|
class PwDebugLoggerSink {
|
|
isEnabled(name: string, severity: LoggerSeverity): boolean {
|
|
return false;
|
|
}
|
|
|
|
log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }) {
|
|
}
|
|
}
|