From 53302079e30580242e7bf4f9cad88f9b37d1a789 Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Mon, 25 Jul 2022 20:23:34 +0200 Subject: [PATCH] feat: cleaner log output (#5680) * feat: cleaner log output * feat: break logs wider than the terminal width into new lines * fix: reduce ios log predicate scope * chore: add todo notes * feat: allow using classicLogs with a temporary --env.classicLogs flag --- lib/common/constants.ts | 3 +- lib/common/mobile/device-log-provider.ts | 137 ++++++++++++++++++++++- 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/lib/common/constants.ts b/lib/common/constants.ts index 8586bfcb8a..447efc7ca0 100644 --- a/lib/common/constants.ts +++ b/lib/common/constants.ts @@ -62,8 +62,7 @@ export class EmulatorDiscoveryNames { } export const DEVICE_LOG_EVENT_NAME = "deviceLogData"; -export const IOS_LOG_PREDICATE = - 'senderImagePath contains "NativeScript" || eventMessage contains[c] "NativeScript"'; +export const IOS_LOG_PREDICATE = 'senderImagePath contains "NativeScript"'; export const IOS_APP_CRASH_LOG_REG_EXP = /Fatal JavaScript exception \- application has been terminated/; export const FAIL_LIVESYNC_LOG_REGEX = /Failed to refresh the application with RefreshRequest./; diff --git a/lib/common/mobile/device-log-provider.ts b/lib/common/mobile/device-log-provider.ts index 72c9124d96..0eafdbb842 100644 --- a/lib/common/mobile/device-log-provider.ts +++ b/lib/common/mobile/device-log-provider.ts @@ -1,13 +1,17 @@ import { DeviceLogProviderBase } from "./device-log-provider-base"; import { DEVICE_LOG_EVENT_NAME } from "../constants"; -import { LoggerConfigData } from "../../constants"; import { injector } from "../yok"; +import * as chalk from "chalk"; +import { LoggerConfigData } from "../../constants"; +import { IOptions } from "../../declarations"; + export class DeviceLogProvider extends DeviceLogProviderBase { constructor( protected $logFilter: Mobile.ILogFilter, protected $logger: ILogger, - protected $logSourceMapService: Mobile.ILogSourceMapService + protected $logSourceMapService: Mobile.ILogSourceMapService, + protected $options: IOptions ) { super($logFilter, $logger, $logSourceMapService); } @@ -17,6 +21,7 @@ export class DeviceLogProvider extends DeviceLogProviderBase { platform: string, deviceIdentifier: string ): void { + // console.log(lineText) const loggingOptions = this.getDeviceLogOptionsForDevice(deviceIdentifier); let data = this.$logFilter.filterData(platform, lineText, loggingOptions); data = this.$logSourceMapService.replaceWithOriginalFileLocations( @@ -25,7 +30,7 @@ export class DeviceLogProvider extends DeviceLogProviderBase { loggingOptions ); if (data) { - this.logDataCore(data); + this.logDataCore(data, deviceIdentifier); this.emit(DEVICE_LOG_EVENT_NAME, lineText, deviceIdentifier, platform); } } @@ -34,8 +39,130 @@ export class DeviceLogProvider extends DeviceLogProviderBase { this.$logFilter.loggingLevel = logLevel.toUpperCase(); } - private logDataCore(data: string): void { - this.$logger.info(data, { [LoggerConfigData.skipNewLine]: true }); + private consoleLogLevelRegex: RegExp = /^CONSOLE (LOG|INFO|WARN|ERROR|TRACE|INFO( .+)):\s/; + private consoleLevelColor: Record string> = { + log: (line) => line, + info: chalk.cyanBright, + warn: chalk.yellowBright, + error: chalk.redBright, + trace: chalk.grey, + time: chalk.greenBright, + }; + + private deviceColorMap = new Map(); + + private colorPool: typeof chalk.BackgroundColor[] = [ + "bgGray", + "bgMagentaBright", + "bgBlueBright", + "bgWhiteBright", + "bgCyanBright", + "bgYellowBright", + "bgGreenBright", + ]; + private colorPoolIndex = 0; + + private getDeviceColor(deviceIdentifier: string) { + if (this.deviceColorMap.has(deviceIdentifier)) { + return this.deviceColorMap.get(deviceIdentifier); + } + + const color = this.colorPool[this.colorPoolIndex]; + // wrap around if we have no more colors in the pool + this.colorPoolIndex = + this.colorPoolIndex === this.colorPool.length - 1 + ? 0 + : this.colorPoolIndex + 1; + + this.deviceColorMap.set(deviceIdentifier, color); + + return color; + } + + private logDataCore(data: string, deviceIdentifier: string): void { + // todo: use config to set logger - --env.classicLogs is temporary! + if ("classicLogs" in this.$options.env) { + // legacy logging + this.$logger.info(data, { [LoggerConfigData.skipNewLine]: true }); + return; + } + + // todo: extract into an injectable printer/logger service + let shouldPrepend = false; + let splitIndexes: number[] = []; + const lines = data + .split(/\n(CONSOLE)/) + .map((line, index, lines) => { + if (line === "CONSOLE") { + shouldPrepend = true; + + if (lines[index - 1]) { + splitIndexes.push(index - 1); + } + + return null; + } + + if (shouldPrepend) { + shouldPrepend = false; + return `CONSOLE${line}`; + } + + const suffix = line.endsWith("\n") ? "" : "\n"; + return line + suffix; + }) + .map((line, index) => { + if (splitIndexes.includes(index)) { + return line + "\n"; + } + return line; + }) + .filter((line) => { + return line !== null; + }); + + if (!lines.length && data.length) { + lines.push(data); + } + + for (const line of lines) { + let [match, level, timeLabel] = + this.consoleLogLevelRegex.exec(line) ?? []; + + if (timeLabel) { + level = "time"; + timeLabel = timeLabel.replace("INFO ", "").trim() + ": "; + } else { + level = level?.toLowerCase() ?? "log"; + } + + const toLog = [timeLabel ?? "", match ? line.replace(match, "") : line] + .join("") + .trim(); + + toLog.split("\n").forEach((actualLine) => { + this.printLine( + chalk[this.getDeviceColor(deviceIdentifier)](" "), + this.consoleLevelColor[level](actualLine) + ); + }); + } + } + + private printLine(prefix: string, ...parts: string[]) { + const maxWidth = process.stdout.columns - 2; + const fullLine = parts.join(" "); + + // console.log(prefix, fullLine); + // return; + if (fullLine.length < maxWidth) { + console.log(prefix, fullLine); + } else { + for (let i = 0; i < fullLine.length; i += maxWidth) { + const part = fullLine.substring(i, i + maxWidth); + console.log(prefix, part); + } + } } } injector.register("deviceLogProvider", DeviceLogProvider);