diff --git a/src/logger/renderers.ts b/src/logger/renderers.ts index d3ad90b853..039afb9227 100644 --- a/src/logger/renderers.ts +++ b/src/logger/renderers.ts @@ -10,7 +10,7 @@ import * as logSymbols from "log-symbols" import * as nodeEmoji from "node-emoji" import * as yaml from "js-yaml" import chalk from "chalk" -import { curryRight, flow, padEnd, padStart } from "lodash" +import { curryRight, flow, isArray, padEnd, padStart } from "lodash" import hasAnsi = require("has-ansi") import { duration } from "./util" @@ -30,7 +30,7 @@ const truncate = (s: string) => s.length > sectionPrefixWidth : s const sectionStyle = (s: string) => chalk.cyan.italic(padEnd(truncate(s), sectionPrefixWidth)) const msgStyle = (s: string) => hasAnsi(s) ? s : chalk.gray(s) -const errorStyle = (s: string) => hasAnsi(s) ? s : chalk.red(s) +const errorStyle = chalk.red /*** RENDER HELPERS ***/ function insertVal(out: string[], idx: number, toRender: Function | string, renderArgs: any[]): string[] { @@ -88,9 +88,14 @@ export function renderSymbol(entry: LogEntry): string { } export function renderMsg(entry: LogEntry): string { - const { entryStyle, msg } = entry.opts + const { entryStyle, msg, notOriginatedFromLogger } = entry.opts + + if (notOriginatedFromLogger) { + return isArray(msg) ? msg.join(" ") : msg || "" + } + const styleFn = entryStyle === EntryStyle.error ? errorStyle : msgStyle - if (msg && msg instanceof Array) { + if (isArray(msg)) { return msg.map(styleFn).join(chalk.gray(" → ")) } return msg ? styleFn(msg) : "" diff --git a/src/logger/util.ts b/src/logger/util.ts index b042eb9204..2f75db5dc9 100644 --- a/src/logger/util.ts +++ b/src/logger/util.ts @@ -65,12 +65,14 @@ interface StreamWriteExtraParam { noIntercept?: boolean } -// Intercepts the write method of a WriteableStream and calls the provided callback on the -// string to write (or optionally applies the string to the write method) -// Returns a function which sets the write back to default. -// -// Used e.g. by FancyLogger so that writes from other sources can be intercepted -// and pushed to the log stack. +/** + * Intercepts the write method of a WriteableStream and calls the provided callback on the + * string to write (or optionally applies the string to the write method) + * Returns a function which sets the write back to default. + * + * Used e.g. by FancyLogger so that writes from other sources can be intercepted + * and pushed to the log stack. + */ export function interceptStream(stream: NodeJS.WritableStream, callback: Function) { const prevWrite = stream.write diff --git a/src/logger/writers.ts b/src/logger/writers.ts index 83d2d44c54..130e6eb021 100644 --- a/src/logger/writers.ts +++ b/src/logger/writers.ts @@ -165,6 +165,7 @@ export class FancyConsoleWriter extends Writer { private formattedEntries: string[] private logUpdate: any private intervalID: number | null + public persistedAtIdx: number public level: LogLevel @@ -173,6 +174,7 @@ export class FancyConsoleWriter extends Writer { this.intervalID = null this.formattedEntries = [] // Entries are cached on format this.spinners = [] // Each entry has it's own spinner + this.persistedAtIdx = 0 } private initLogUpdate(rootLogNode: RootLogNode): any { @@ -182,18 +184,17 @@ export class FancyConsoleWriter extends Writer { write: (str, enc, cb) => (process.stdout.write)(str, enc, cb, { noIntercept: true }), } const makeOpts = msg => ({ - // Remove trailing new line from console writes since Logger already handles it - msg: typeof msg === "string" ? msg.replace(/\n$/, "") : msg, + msg, notOriginatedFromLogger: true, }) - /* - NOTE: On every write, log-update library calls the cli-cursor library to hide the cursor - which the cli-cursor library does via stderr write. This causes an infinite loop as - the stderr writes are intercepted and funneled back to the Logger. - Therefore we manually toggle the cursor using the custom stream from above. - - log-update types are missing the `opts?: {showCursor?: boolean}` parameter - */ + /** + * NOTE: On every write, log-update library calls the cli-cursor library to hide the cursor + * which the cli-cursor library does via stderr write. This causes an infinite loop as + * the stderr writes are intercepted and funneled back to the Logger. + * Therefore we manually toggle the cursor using the custom stream from above. + * + * log-update types are missing the `opts?: {showCursor?: boolean}` parameter + */ const customLogUpdate = (logUpdate.create)(stream, { showCursor: true }) cliCursor.hide(stream) @@ -241,7 +242,7 @@ export class FancyConsoleWriter extends Writer { private updateStream(rootLogNode: RootLogNode): void { const out = this.render(rootLogNode) if (out) { - this.logUpdate(out.join("\n")) + this.logUpdate(out.join("")) } } @@ -254,13 +255,19 @@ export class FancyConsoleWriter extends Writer { const level = this.level || rootLogNode.level const entries = getChildNodes(rootLogNode) - /* - This is a bit ugly for performance sake. - Rather than just creating a new string with an updated spinner frame in each render cycle - we instead cache the formatted string and splice the updated frame into it. - */ - const out = entries.reduce((acc: string[], entry: LogEntry, idx: number): string[] => { + /** + * This is a bit ugly for performance sake. + * Rather than just creating a new string with an updated spinner frame in each render cycle + * we instead cache the formatted string and splice the updated frame into it. + */ + const out = entries.slice(this.persistedAtIdx).reduce((acc: string[], entry: LogEntry, idx: number): string[] => { let spinnerFrame = "" + + if (entry.notOriginatedFromLogger()) { + acc.push(renderMsg(entry)) + return acc + } + if (entry.status === EntryStatus.ACTIVE) { hasActiveEntries = true spinnerFrame = this.readOrSetSpinner(idx) @@ -271,7 +278,7 @@ export class FancyConsoleWriter extends Writer { const withSpinner = spinnerFrame ? `${formatted.slice(0, startPos)}${spinnerStyle(spinnerFrame)} ${formatted.slice(startPos)}` : formatted - acc.push(withSpinner) + acc.push(withSpinner + "\n") } return acc }, []) @@ -300,6 +307,16 @@ export class FancyConsoleWriter extends Writer { public stop(): void { this.stopLoop() this.logUpdate && this.logUpdate.cleanUp() + this.logUpdate = null + } + + /** + * Escape hatch for reclaiming the stream, e.g. when reading stdin. + * Logger will then continue afterwards but won't be able to update the previous content + */ + public stopAndPersist(rootLogNode: RootLogNode): void { + this.stop() + this.persistedAtIdx = rootLogNode.children.length } }