@pkerschbaum/code-oss-file-service
Version:
VS Code ([microsoft/vscode](https://github.com/microsoft/vscode)) includes a rich "`FileService`" and "`DiskFileSystemProvider`" abstraction built on top of Node.js core modules (`fs`, `path`) and Electron's `shell` module. This package allows to use that
592 lines (483 loc) • 15.4 kB
text/typescript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
function now(): string {
return new Date().toISOString();
}
export enum LogLevel {
Trace,
Debug,
Info,
Warning,
Error,
Critical,
Off
}
export const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info;
export interface ILogger extends IDisposable {
onDidChangeLogLevel: Event<LogLevel>;
getLevel(): LogLevel;
setLevel(level: LogLevel): void;
trace(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string | Error, ...args: any[]): void;
critical(message: string | Error, ...args: any[]): void;
/**
* An operation to flush the contents. Can be synchronous.
*/
flush(): void;
}
export function log(logger: ILogger, level: LogLevel, message: string): void {
switch (level) {
case LogLevel.Trace: logger.trace(message); break;
case LogLevel.Debug: logger.debug(message); break;
case LogLevel.Info: logger.info(message); break;
case LogLevel.Warning: logger.warn(message); break;
case LogLevel.Error: logger.error(message); break;
case LogLevel.Critical: logger.critical(message); break;
default: throw new Error('Invalid log level');
}
}
export function format(args: any): string {
let result = '';
for (let i = 0; i < args.length; i++) {
let a = args[i];
if (typeof a === 'object') {
try {
a = JSON.stringify(a);
} catch (e) { }
}
result += (i > 0 ? ' ' : '') + a;
}
return result;
}
export interface ILogService extends ILogger {
readonly _serviceBrand: undefined;
}
export interface ILoggerOptions {
/**
* Name of the logger.
*/
name?: string;
/**
* Do not create rotating files if max size exceeds.
*/
donotRotate?: boolean;
/**
* Do not use formatters.
*/
donotUseFormatters?: boolean;
/**
* If set, logger logs the message always.
*/
always?: boolean;
}
export interface ILoggerService {
readonly _serviceBrand: undefined;
/**
* Creates a logger, or gets one if it already exists.
*/
createLogger(file: URI, options?: ILoggerOptions): ILogger;
/**
* Gets an existing logger, if any.
*/
getLogger(file: URI): ILogger | undefined;
}
export abstract class AbstractLogger extends Disposable {
private level: LogLevel = DEFAULT_LOG_LEVEL;
private readonly _onDidChangeLogLevel: Emitter<LogLevel> = this._register(new Emitter<LogLevel>());
readonly onDidChangeLogLevel: Event<LogLevel> = this._onDidChangeLogLevel.event;
setLevel(level: LogLevel): void {
if (this.level !== level) {
this.level = level;
this._onDidChangeLogLevel.fire(this.level);
}
}
getLevel(): LogLevel {
return this.level;
}
}
export abstract class AbstractMessageLogger extends AbstractLogger implements ILogger {
protected abstract log(level: LogLevel, message: string): void;
constructor(private readonly logAlways?: boolean) {
super();
}
private checkLogLevel(level: LogLevel): boolean {
return this.logAlways || this.getLevel() <= level;
}
trace(message: string, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Trace)) {
this.log(LogLevel.Trace, format([message, ...args]));
}
}
debug(message: string, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Debug)) {
this.log(LogLevel.Debug, format([message, ...args]));
}
}
info(message: string, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Info)) {
this.log(LogLevel.Info, format([message, ...args]));
}
}
warn(message: string, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Warning)) {
this.log(LogLevel.Warning, format([message, ...args]));
}
}
error(message: string | Error, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Error)) {
if (message instanceof Error) {
const array = Array.prototype.slice.call(arguments) as any[];
array[0] = message.stack;
this.log(LogLevel.Error, format(array));
} else {
this.log(LogLevel.Error, format([message, ...args]));
}
}
}
critical(message: string | Error, ...args: any[]): void {
if (this.checkLogLevel(LogLevel.Critical)) {
this.log(LogLevel.Critical, format([message, ...args]));
}
}
flush(): void { }
}
export class ConsoleMainLogger extends AbstractLogger implements ILogger {
private useColors: boolean;
constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
super();
this.setLevel(logLevel);
this.useColors = !isWindows;
}
trace(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Trace) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${now()}]`, message, ...args);
}
}
}
debug(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Debug) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${now()}]`, message, ...args);
}
}
}
info(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Info) {
if (this.useColors) {
console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.log(`[main ${now()}]`, message, ...args);
}
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Warning) {
if (this.useColors) {
console.warn(`\x1b[93m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.warn(`[main ${now()}]`, message, ...args);
}
}
}
error(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Error) {
if (this.useColors) {
console.error(`\x1b[91m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${now()}]`, message, ...args);
}
}
}
critical(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Critical) {
if (this.useColors) {
console.error(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args);
} else {
console.error(`[main ${now()}]`, message, ...args);
}
}
}
override dispose(): void {
// noop
}
flush(): void {
// noop
}
}
export class ConsoleLogger extends AbstractLogger implements ILogger {
constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
super();
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Trace) {
console.log('%cTRACE', 'color: #888', message, ...args);
}
}
debug(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Debug) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
}
}
info(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Info) {
console.log('%c INFO', 'color: #33f', message, ...args);
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Warning) {
console.log('%c WARN', 'color: #993', message, ...args);
}
}
error(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Error) {
console.log('%c ERR', 'color: #f33', message, ...args);
}
}
critical(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Critical) {
console.log('%cCRITI', 'background: #f33; color: white', message, ...args);
}
}
override dispose(): void {
// noop
}
flush(): void {
// noop
}
}
export class AdapterLogger extends AbstractLogger implements ILogger {
constructor(private readonly adapter: { log: (logLevel: LogLevel, args: any[]) => void }, logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
super();
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Trace) {
this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]);
}
}
debug(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Debug) {
this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]);
}
}
info(message: string, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Info) {
this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]);
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Warning) {
this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]);
}
}
error(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Error) {
this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]);
}
}
critical(message: string | Error, ...args: any[]): void {
if (this.getLevel() <= LogLevel.Critical) {
this.adapter.log(LogLevel.Critical, [this.extractMessage(message), ...args]);
}
}
private extractMessage(msg: string | Error): string {
if (typeof msg === 'string') {
return msg;
}
return toErrorMessage(msg, this.getLevel() <= LogLevel.Trace);
}
override dispose(): void {
// noop
}
flush(): void {
// noop
}
}
export class MultiplexLogService extends AbstractLogger implements ILogService {
declare readonly _serviceBrand: undefined;
constructor(private readonly logServices: ReadonlyArray<ILogger>) {
super();
if (logServices.length) {
this.setLevel(logServices[0].getLevel());
}
}
override setLevel(level: LogLevel): void {
for (const logService of this.logServices) {
logService.setLevel(level);
}
super.setLevel(level);
}
trace(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.trace(message, ...args);
}
}
debug(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.debug(message, ...args);
}
}
info(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.info(message, ...args);
}
}
warn(message: string, ...args: any[]): void {
for (const logService of this.logServices) {
logService.warn(message, ...args);
}
}
error(message: string | Error, ...args: any[]): void {
for (const logService of this.logServices) {
logService.error(message, ...args);
}
}
critical(message: string | Error, ...args: any[]): void {
for (const logService of this.logServices) {
logService.critical(message, ...args);
}
}
flush(): void {
for (const logService of this.logServices) {
logService.flush();
}
}
override dispose(): void {
for (const logService of this.logServices) {
logService.dispose();
}
}
}
export class LogService extends Disposable implements ILogService {
declare readonly _serviceBrand: undefined;
constructor(private logger: ILogger) {
super();
this._register(logger);
}
get onDidChangeLogLevel(): Event<LogLevel> {
return this.logger.onDidChangeLogLevel;
}
setLevel(level: LogLevel): void {
this.logger.setLevel(level);
}
getLevel(): LogLevel {
return this.logger.getLevel();
}
trace(message: string, ...args: any[]): void {
this.logger.trace(message, ...args);
}
debug(message: string, ...args: any[]): void {
this.logger.debug(message, ...args);
}
info(message: string, ...args: any[]): void {
this.logger.info(message, ...args);
}
warn(message: string, ...args: any[]): void {
this.logger.warn(message, ...args);
}
error(message: string | Error, ...args: any[]): void {
this.logger.error(message, ...args);
}
critical(message: string | Error, ...args: any[]): void {
this.logger.critical(message, ...args);
}
flush(): void {
this.logger.flush();
}
}
export abstract class AbstractLoggerService extends Disposable implements ILoggerService {
declare readonly _serviceBrand: undefined;
private readonly loggers = new Map<string, ILogger>();
private readonly logLevelChangeableLoggers: ILogger[] = [];
constructor(
private logLevel: LogLevel,
onDidChangeLogLevel: Event<LogLevel>,
) {
super();
this._register(onDidChangeLogLevel(logLevel => {
this.logLevel = logLevel;
this.logLevelChangeableLoggers.forEach(logger => logger.setLevel(logLevel));
}));
}
getLogger(resource: URI) {
return this.loggers.get(resource.toString());
}
createLogger(resource: URI, options?: ILoggerOptions): ILogger {
let logger = this.loggers.get(resource.toString());
if (!logger) {
logger = this.doCreateLogger(resource, options?.always ? LogLevel.Trace : this.logLevel, options);
this.loggers.set(resource.toString(), logger);
if (!options?.always) {
this.logLevelChangeableLoggers.push(logger);
}
}
return logger;
}
override dispose(): void {
this.logLevelChangeableLoggers.splice(0, this.logLevelChangeableLoggers.length);
this.loggers.forEach(logger => logger.dispose());
this.loggers.clear();
super.dispose();
}
protected abstract doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger;
}
export class NullLogService implements ILogService {
declare readonly _serviceBrand: undefined;
readonly onDidChangeLogLevel: Event<LogLevel> = new Emitter<LogLevel>().event;
setLevel(level: LogLevel): void { }
getLevel(): LogLevel { return LogLevel.Info; }
trace(message: string, ...args: any[]): void { }
debug(message: string, ...args: any[]): void { }
info(message: string, ...args: any[]): void { }
warn(message: string, ...args: any[]): void { }
error(message: string | Error, ...args: any[]): void { }
critical(message: string | Error, ...args: any[]): void { }
dispose(): void { }
flush(): void { }
}
export function parseLogLevel(logLevel: string): LogLevel | undefined {
switch (logLevel) {
case 'trace':
return LogLevel.Trace;
case 'debug':
return LogLevel.Debug;
case 'info':
return LogLevel.Info;
case 'warn':
return LogLevel.Warning;
case 'error':
return LogLevel.Error;
case 'critical':
return LogLevel.Critical;
case 'off':
return LogLevel.Off;
}
return undefined;
}
export function LogLevelToString(logLevel: LogLevel): string {
switch (logLevel) {
case LogLevel.Trace: return 'trace';
case LogLevel.Debug: return 'debug';
case LogLevel.Info: return 'info';
case LogLevel.Warning: return 'warn';
case LogLevel.Error: return 'error';
case LogLevel.Critical: return 'critical';
case LogLevel.Off: return 'off';
}
}