Skip to content

feat: cleaner log output #5680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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./;

Expand Down
137 changes: 132 additions & 5 deletions lib/common/mobile/device-log-provider.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Expand All @@ -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(
Expand All @@ -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);
}
}
Expand All @@ -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, (line: string) => string> = {
log: (line) => line,
info: chalk.cyanBright,
warn: chalk.yellowBright,
error: chalk.redBright,
trace: chalk.grey,
time: chalk.greenBright,
};

private deviceColorMap = new Map<string, typeof chalk.BackgroundColor>();

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);