Skip to content

Commit

Permalink
feat: improved logging and server UI (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
zamotany committed Aug 13, 2019
1 parent 5fb3e88 commit c41becc
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 35 deletions.
4 changes: 4 additions & 0 deletions packages/haul-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@
"resolve": "^1.12.0",
"semver": "^6.3.0",
"source-map": "^0.7.3",
"strip-ansi": "5.2.0",
"terminal-kit": "^1.30.0",
"terser": "^4.1.3",
"utility-types": "^3.7.0",
"webpack": "^4.39.1",
"wrap-ansi": "^6.0.0",
"ws": "^6.2.1"
},
"devDependencies": {
Expand All @@ -68,7 +70,9 @@
"@types/node-fetch": "^2.5.0",
"@types/resolve": "^0.0.8",
"@types/semver": "^6.0.1",
"@types/strip-ansi": "^5.2.1",
"@types/terminal-kit": "^1.28.0",
"@types/wrap-ansi": "^3.0.0",
"@types/ws": "^6.0.2"
}
}
61 changes: 54 additions & 7 deletions packages/haul-core/src/runtime/Logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inspect } from 'util';
import fs from 'fs';
import path from 'path';
import stripAnsi from 'strip-ansi';
import { LoggerEvent } from '@haul-bundler/inspector-events';
import {
container,
Expand Down Expand Up @@ -48,6 +49,11 @@ export default class Logger {

constructor(private inspectorClient?: InspectorClient) {}

/**
* Enables logging all messages to file as well as to process' STDOUT.
* If `json` is `true` each log will be in JSON format for easier processing.
* If relative `filename` is passed, it will be resolved based on process' CWD.
*/
enableFileLogging(filename: string, { json }: { json: boolean }) {
const absFilename = path.isAbsolute(filename)
? filename
Expand All @@ -56,6 +62,11 @@ export default class Logger {
this.logAsJson = json;
}

/**
* Disposes the logger by closing all open handles.
* If file logging was enabled, the file descriptor will be closed here.
* Should always be called before exiting from process.
*/
dispose() {
if (this.logFile !== undefined) {
fs.closeSync(this.logFile);
Expand All @@ -68,27 +79,45 @@ export default class Logger {
done = this.createLoggingFunction(LoggerLevel.Done);
debug = this.createLoggingFunction(LoggerLevel.Debug);

/**
* Enables proxy for all logs.
* Messages will be passed to `handler` function and __won't be logged__ to process' STDOUT.
* Returns a dispose function to disable the proxy.
*/
proxy = (handler: ProxyHandler): (() => void) => {
this.proxyHandler = handler;
return () => {
this.proxyHandler = undefined;
};
};

/**
* Prints arguments _as is_ without any processing.
*/
print = (...args: unknown[]) => {
// eslint-disable-next-line no-console
console.log(...args);
};

/**
* Enhances given arguments with ANSI color.
*/
enhanceWithColor = (enhancer: AnsiColor, ...args: unknown[]) => {
return color(enhancer, this.stringify(args).join(' ')).build();
};

/**
* Enhances given arguments with ANSI modifier, for example with `bold`, `italic` etc.
*/
enhanceWithModifier = (enhancer: AnsiModifier, ...args: unknown[]) => {
return modifier(enhancer, this.stringify(args).join(' ')).build();
};

enhance = (level: LoggerLevel, ...args: unknown[]) => {
/**
* Enhances given arguments with level prefix.
* Example: info ▶︎ argument1 argument2
*/
enhanceWithLevel = (level: LoggerLevel, ...args: unknown[]) => {
return container(
color(levelToColorMappings[level], modifier('bold', level)),
pad(1),
Expand All @@ -98,8 +127,20 @@ export default class Logger {
).build();
};

/**
* Stringify array of elements into a string array.
* Uses Node's built-in `util.inspect` function to stringify non-string elements.
*/
stringify(args: any[]) {
return args.map(item => (typeof item === 'string' ? item : inspect(item)));
return args.map(item =>
typeof item === 'string'
? item
: inspect(item, {
depth: null,
maxArrayLength: null,
breakLength: Infinity,
})
);
}

private createLoggingFunction(level: LoggerLevel) {
Expand All @@ -109,13 +150,19 @@ export default class Logger {
}

if (this.logFile !== undefined) {
const rawArgs = this.stringify(args).map(stripAnsi);
fs.appendFileSync(
this.logFile,
(this.logAsJson
? JSON.stringify({ timestamp: new Date(), level, messages: args })
: `[${new Date().toISOString()}] ${level}: ${this.stringify(
args
).join()}`) + '\n',
? stripAnsi(
JSON.stringify({
timestamp: new Date(),
level,
messages: rawArgs,
})
)
: `[${new Date().toISOString()}] ${level}: ${rawArgs.join(' ')}`) +
'\n',
'utf8'
);
}
Expand All @@ -127,7 +174,7 @@ export default class Logger {
if (this.proxyHandler) {
this.proxyHandler(level, ...args);
} else {
this.print(this.enhance(level, ...args));
this.print(this.enhanceWithLevel(level, ...args));
}
}
};
Expand Down
18 changes: 6 additions & 12 deletions packages/haul-core/src/server/InteractiveUI.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Terminal } from 'terminal-kit';
import { container, color, modifier, pad } from 'ansi-fragments';
import wrapAnsi from 'wrap-ansi';
import UserInterface from './UI';

class Logs {
Expand Down Expand Up @@ -27,17 +28,10 @@ class Logs {
addItem(item: string) {
const lines = item.split('\n').reduce(
(acc, line) => {
if (line.length > this.maxLineWidth) {
const subLines =
line.match(new RegExp(`.{1,${this.maxLineWidth}}`, 'g')) || [];
if (subLines) {
return acc.concat(...subLines);
}

return acc;
}

return acc.concat(line);
const wrappedLine = wrapAnsi(line, this.maxLineWidth, {
hard: true,
});
return acc.concat(...wrappedLine.split('\n'));
},
[] as string[]
);
Expand Down Expand Up @@ -180,7 +174,7 @@ export default class InteractiveUserInterface implements UserInterface {
start(platforms: string[]) {
this.logs.sliceStart = 0;
this.logs.sliceMaxLength = this.terminal.height - platforms.length - 6;
this.logs.maxLineWidth = this.terminal.width - 10;
this.logs.maxLineWidth = this.terminal.width - 2;

this.terminal.fullscreen(true);
this.terminal.grabInput({ mouse: 'motion' });
Expand Down
10 changes: 7 additions & 3 deletions packages/haul-core/src/server/NonInteractiveUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ export default class NonInteractiveUserInterface implements UserInterface {
) {
if (running) {
const percent = `${Math.floor(Math.min(1, value) * 100)}%`;
this.runtime.logger.info(`Compilation - running (${percent})`);
this.runtime.logger.info(
`[${platform.toUpperCase()}] Compilation - running (${percent})`
);
} else {
this.runtime.logger.info(`Compilation - idle`);
this.runtime.logger.info(
`[${platform.toUpperCase()}] Compilation - idle`
);
}
}

addLogItem(item: string) {
// `item` should be already enhanced with ANSI escape codes
this.runtime.logger.print(item);
this.runtime.logger.print('print', item);
}

dispose(exitCode: number = 0, exit: boolean = true) {
Expand Down
17 changes: 11 additions & 6 deletions packages/haul-core/src/server/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default class Server {
value: 0,
});
this.ui.addLogItem(
this.runtime.logger.enhance(Logger.Level.Error, message)
this.runtime.logger.enhanceWithLevel(Logger.Level.Error, message)
);
}
);
Expand All @@ -107,7 +107,7 @@ export default class Server {
});
errors.forEach(error => {
this.ui.addLogItem(
this.runtime.logger.enhance(Logger.Level.Error, error)
this.runtime.logger.enhanceWithLevel(Logger.Level.Error, error)
);
});
}
Expand Down Expand Up @@ -142,7 +142,9 @@ export default class Server {
// in interactive mode.
if (!this.options.noInteractive) {
this.disposeLoggerProxy = this.runtime.logger.proxy((level, ...args) => {
this.ui.addLogItem(this.runtime.logger.enhance(level, ...args));
this.ui.addLogItem(
this.runtime.logger.enhanceWithLevel(level, ...args)
);
});
}

Expand Down Expand Up @@ -219,13 +221,13 @@ export default class Server {

console.log = (...args) => {
this.ui.addLogItem(
this.runtime.logger.enhance(Logger.Level.Info, ...args)
this.runtime.logger.enhanceWithLevel(Logger.Level.Info, ...args)
);
};

console.error = (...args) => {
this.ui.addLogItem(
this.runtime.logger.enhance(Logger.Level.Error, ...args)
this.runtime.logger.enhanceWithLevel(Logger.Level.Error, ...args)
);
};

Expand All @@ -239,12 +241,15 @@ export default class Server {
logServerEvent(request: Hapi.Request, event?: Hapi.RequestEvent) {
const { statusCode } = request.response as ResponseObject;
let logColor: AnsiColor = 'green';
let level: 'info' | 'warn' | 'error' = 'info';
if (statusCode >= 300 && statusCode < 400) {
logColor = 'yellow';
level = 'warn';
} else if (statusCode >= 400) {
logColor = 'red';
level = 'error';
}
this.ui.addLogItem(
this.runtime.logger[level](
container(
color(logColor, modifier('bold', request.method.toUpperCase())),
pad(1),
Expand Down
Loading

0 comments on commit c41becc

Please sign in to comment.