import { ThrottledDelayer } from '../../../base/common/async.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { basename, dirname, joinPath } from '../../../base/common/resources.js';
import { URI } from '../../../base/common/uri.js';
import { ByteSize, FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from '../../files/common/files.js';
import { BufferLogger } from './bufferLog.js';
import { AbstractLoggerService, AbstractMessageLogger, ILogger, ILoggerOptions, ILoggerService, LogLevel } from './log.js';
const MAX_FILE_SIZE = 5 * ByteSize.MB;
class FileLogger extends AbstractMessageLogger implements ILogger {
private readonly initializePromise: Promise<void>;
private readonly flushDelayer: ThrottledDelayer<void>;
private backupIndex: number = 1;
private buffer: string = '';
constructor(
private readonly resource: URI,
level: LogLevel,
private readonly donotUseFormatters: boolean,
@IFileService private readonly fileService: IFileService
) {
super();
this.setLevel(level);
this.flushDelayer = new ThrottledDelayer<void>(100 );
this.initializePromise = this.initialize();
}
override async flush(): Promise<void> {
if (!this.buffer) {
return;
}
await this.initializePromise;
let content = await this.loadContent();
if (content.length > MAX_FILE_SIZE) {
await this.fileService.writeFile(this.getBackupResource(), VSBuffer.fromString(content));
content = '';
}
if (this.buffer) {
content += this.buffer;
this.buffer = '';
await this.fileService.writeFile(this.resource, VSBuffer.fromString(content));
}
}
private async initialize(): Promise<void> {
try {
await this.fileService.createFile(this.resource);
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_MODIFIED_SINCE) {
throw error;
}
}
}
protected log(level: LogLevel, message: string): void {
if (this.donotUseFormatters) {
this.buffer += message;
} else {
this.buffer += `${this.getCurrentTimestamp()} [${this.stringifyLogLevel(level)}] ${message}\n`;
}
this.flushDelayer.trigger(() => this.flush());
}
private getCurrentTimestamp(): string {
const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v;
const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v;
const currentTime = new Date();
return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`;
}
private getBackupResource(): URI {
this.backupIndex = this.backupIndex > 5 ? 1 : this.backupIndex;
return joinPath(dirname(this.resource), `${basename(this.resource)}_${this.backupIndex++}`);
}
private async loadContent(): Promise<string> {
try {
const content = await this.fileService.readFile(this.resource);
return content.value.toString();
} catch (e) {
return '';
}
}
private stringifyLogLevel(level: LogLevel): string {
switch (level) {
case LogLevel.Debug: return 'debug';
case LogLevel.Error: return 'error';
case LogLevel.Info: return 'info';
case LogLevel.Trace: return 'trace';
case LogLevel.Warning: return 'warning';
}
return '';
}
}
export class FileLoggerService extends AbstractLoggerService implements ILoggerService {
constructor(
logLevel: LogLevel,
logsHome: URI,
private readonly fileService: IFileService,
) {
super(logLevel, logsHome);
}
protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger {
const logger = new BufferLogger(logLevel);
whenProviderRegistered(resource, this.fileService).then(() => logger.logger = new FileLogger(resource, logger.getLevel(), !!options?.donotUseFormatters, this.fileService));
return logger;
}
}