import { ILogger, LogLevel } from '@theia/core/lib/common/logger'; export interface DaemonLog { readonly time: string; readonly level: DaemonLog.Level; readonly msg: string; } export namespace DaemonLog { export interface Url { readonly Scheme: string; readonly Host: string; readonly Path: string; } export namespace Url { export function is(arg: any | undefined): arg is Url { return !!arg && typeof arg.Scheme === 'string' && typeof arg.Host === 'string' && typeof arg.Path === 'string'; } export function toString(url: Url): string { const { Scheme, Host, Path } = url; return `${Scheme}://${Host}${Path}`; } } export interface System { readonly os: string; // readonly Resource: Resource; } export namespace System { export function toString(system: System): string { return `OS: ${system.os}` } } export interface Tool { readonly version: string; readonly systems: System[]; } export namespace Tool { export function is(arg: any | undefined): arg is Tool { return !!arg && typeof arg.version === 'string' && 'systems' in arg; } export function toString(tool: Tool): string { const { version, systems } = tool; return `Version: ${version}${!!systems ? ` Systems: [${tool.systems.map(System.toString).join(', ')}]` : ''}`; } } export type Level = 'trace' | 'debug' | 'info' | 'warning' | 'error'; export function is(arg: any | undefined): arg is DaemonLog { return !!arg && typeof arg.time === 'string' && typeof arg.level === 'string' && typeof arg.msg === 'string' } export function toLogLevel(log: DaemonLog): LogLevel { const { level } = log; switch (level) { case 'trace': return LogLevel.TRACE; case 'debug': return LogLevel.DEBUG; case 'info': return LogLevel.INFO; case 'warning': return LogLevel.WARN; case 'error': return LogLevel.ERROR; default: return LogLevel.INFO; } } export function log(logger: ILogger, logMessages: string): void { const parsed = parse(logMessages); for (const log of parsed) { const logLevel = toLogLevel(log); const message = toMessage(log, { omitLogLevel: true }); logger.log(logLevel, message); } } function parse(toLog: string): DaemonLog[] { const messages = toLog.trim().split('\n'); const result: DaemonLog[] = []; for (let i = 0; i < messages.length; i++) { try { const maybeDaemonLog = JSON.parse(messages[i]); if (DaemonLog.is(maybeDaemonLog)) { result.push(maybeDaemonLog); continue; } } catch { /* NOOP */ } result.push({ time: new Date().toString(), level: 'info', msg: messages[i] }); } return result; } export function toPrettyString(logMessages: string): string { const parsed = parse(logMessages); return parsed.map(log => toMessage(log)).join('\n') + '\n'; } function toMessage(log: DaemonLog, options: { omitLogLevel: boolean } = { omitLogLevel: false }): string { const details = Object.keys(log).filter(key => key !== 'msg' && key !== 'level' && key !== 'time').map(key => toDetails(log, key)).join(', '); const logLevel = options.omitLogLevel ? '' : `[${log.level.toUpperCase()}] `; return `${logLevel}${log.msg}${!!details ? ` [${details}]` : ''}` } function toDetails(log: DaemonLog, key: string): string { let value = (log as any)[key]; if (DaemonLog.Url.is(value)) { value = DaemonLog.Url.toString(value); } else if (DaemonLog.Tool.is(value)) { value = DaemonLog.Tool.toString(value); } else if (typeof value === 'object') { value = JSON.stringify(value).replace(/\"([^(\")"]+)\":/g, '$1:'); // Remove the quotes from the property keys. } return `${key.toLowerCase()}: ${value}`; } }