Skip to content

Commit

Permalink
feat(cli): add experimental fullscreen logger type
Browse files Browse the repository at this point in the history
This is still experimental and needs to be tested across different
terminals and platforms, but imo adds a solid usability improvement
to watch mode commands. Enable it by setting `--logger-type=fullscreen`
or `GARDEN_LOG_LEVEL=fullscreen`.

It is disabled by default, so if you're satisfied that it doesn't mess
with normal operation, I think it might be good to merge this and use
internally for a while to work out any kinks and get some usability
ideas.
  • Loading branch information
edvald committed Mar 23, 2020
1 parent e82c64a commit 038328a
Show file tree
Hide file tree
Showing 13 changed files with 980 additions and 26 deletions.
2 changes: 1 addition & 1 deletion docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The following option flags can be used with any of the CLI commands:
| `--root` | `-r` | string | Override project root directory (defaults to working directory).
| `--silent` | `-s` | boolean | Suppress log output. Same as setting --logger-type=quiet.
| `--env` | `-e` | string | The environment (and optionally namespace) to work against.
| `--logger-type` | | `quiet` `basic` `fancy` `json` | Set logger type.
| `--logger-type` | | `quiet` `basic` `fancy` `fullscreen` `json` | Set logger type.
fancy: updates log lines in-place when their status changes (e.g. when tasks complete),
basic: appends a new log line when a log line's status changes,
json: same as basic, but renders log lines as JSON,
Expand Down
10 changes: 7 additions & 3 deletions garden-service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions garden-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"static"
],
"dependencies": {
"@hapi/joi": "https://github.com/garden-io/joi#master",
"@hapi/joi": "git+https://github.com/garden-io/joi.git#master",
"@kubernetes/client-node": "^0.11.1",
"JSONStream": "^1.3.5",
"ajv": "^6.12.0",
Expand Down Expand Up @@ -93,6 +93,7 @@
"minimatch": "^3.0.4",
"mocha-logger": "^1.0.6",
"moment": "^2.24.0",
"neo-blessed": "git+https://github.com/garden-io/neo-blessed.git#garden",
"node-emoji": "^1.10.0",
"node-forge": "^0.9.1",
"normalize-path": "^3.0.0",
Expand Down Expand Up @@ -169,7 +170,7 @@
"@types/lodash": "^4.14.149",
"@types/minimatch": "^3.0.3",
"@types/mocha": "^7.0.2",
"@types/node": "^12.12.21",
"@types/node": "^12.12.29",
"@types/node-emoji": "^1.8.1",
"@types/node-forge": "^0.9.2",
"@types/normalize-path": "^3.0.0",
Expand Down
3 changes: 3 additions & 0 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ export class GardenCli {
// the screen the logs are printed.
const headerLog = logger.placeholder()
const log = logger.placeholder()
logger.info("")
const footerLog = logger.placeholder()

const contextOpts: GardenOpts = {
Expand Down Expand Up @@ -483,6 +484,8 @@ export class GardenCli {
}

logger.stop()
logger.cleanup()

return { argv, code, errors, result: commandResult?.result }
}
}
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export class DevCommand extends Command<DevCommandArgs, DevCommandOpts> {
const data = await readFile(ansiBannerPath)
log.info(data.toString())

log.info(chalk.gray.italic(`Good ${getGreetingTime()}! Let's get your environment wired up...\n`))
log.info(chalk.gray.italic(`Good ${getGreetingTime()}! Let's get your environment wired up...`))
log.info("")

this.server = await startServer(footerLog)

Expand Down
3 changes: 3 additions & 0 deletions garden-service/src/logger/log-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class LogEntry extends LogNode {
public readonly errorData?: GardenError
public readonly childEntriesInheritLevel?: boolean
public readonly id?: string
public isPlaceholder?: boolean

constructor(params: LogEntryConstructor) {
super(params.level, params.parent, params.id)
Expand All @@ -100,6 +101,7 @@ export class LogEntry extends LogNode {
this.childEntriesInheritLevel = params.childEntriesInheritLevel
this.metadata = params.metadata
this.id = params.id
this.isPlaceholder = params.isPlaceholder

if (!params.isPlaceholder) {
this.update({
Expand Down Expand Up @@ -228,6 +230,7 @@ export class LogEntry extends LogNode {

// Preserves status
setState(params?: string | UpdateLogEntryParams): LogEntry {
this.isPlaceholder = false
this.deepUpdate({ ...resolveParams(params) })
this.root.onGraphChange(this)
return this
Expand Down
13 changes: 10 additions & 3 deletions garden-service/src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ import { BasicTerminalWriter } from "./writers/basic-terminal-writer"
import { FancyTerminalWriter } from "./writers/fancy-terminal-writer"
import { JsonTerminalWriter } from "./writers/json-terminal-writer"
import { parseLogLevel } from "../cli/helpers"
import { FullscreenTerminalWriter } from "./writers/fullscreen-terminal-writer"

export type LoggerType = "quiet" | "basic" | "fancy" | "json"
export const LOGGER_TYPES = new Set<LoggerType>(["quiet", "basic", "fancy", "json"])
export type LoggerType = "quiet" | "basic" | "fancy" | "fullscreen" | "json"
export const LOGGER_TYPES = new Set<LoggerType>(["quiet", "basic", "fancy", "fullscreen", "json"])

export function getWriterInstance(loggerType: LoggerType, level: LogLevel) {
switch (loggerType) {
case "basic":
return new BasicTerminalWriter(level)
case "fancy":
return new FancyTerminalWriter(level)
case "fullscreen":
return new FullscreenTerminalWriter(level)
case "json":
return new JsonTerminalWriter(level)
case "quiet":
Expand Down Expand Up @@ -95,7 +98,7 @@ export class Logger extends LogNode {
return instance
}

private constructor(config: LoggerConfig) {
constructor(config: LoggerConfig) {
super(config.level)
this.writers = config.writers || []
this.useEmoji = config.useEmoji === false ? false : true
Expand Down Expand Up @@ -134,6 +137,10 @@ export class Logger extends LogNode {
this.getLogEntries().forEach((e) => e.stop())
this.writers.forEach((writer) => writer.stop())
}

cleanup(): void {
this.writers.forEach((writer) => writer.cleanup())
}
}

export function getLogger() {
Expand Down
69 changes: 64 additions & 5 deletions garden-service/src/logger/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import nodeEmoji from "node-emoji"
import chalk from "chalk"
import CircularJSON from "circular-json"
import { LogNode } from "./log-node"
import { LogNode, LogLevel } from "./log-node"
import { LogEntry, LogEntryParams, EmojiName } from "./log-entry"
import { isBuffer } from "util"
import { deepMap } from "../util/util"
Expand All @@ -22,14 +22,15 @@ export type LogOptsResolvers = { [K in keyof LogEntryParams]?: Function }

export type ProcessNode<T extends Node = Node> = (node: T) => boolean

function traverseChildren<T extends Node, U extends Node>(node: T | U, cb: ProcessNode<U>) {
function traverseChildren<T extends Node, U extends Node>(node: T | U, cb: ProcessNode<U>, reverse = false) {
const children = node.children
for (let idx = 0; idx < children.length; idx++) {
const proceed = cb(children[idx])
for (let i = 0; i < children.length; i++) {
const index = reverse ? children.length - 1 - i : i
const proceed = cb(children[index])
if (!proceed) {
return
}
traverseChildren(children[idx], cb)
traverseChildren(children[index], cb)
}
}

Expand Down Expand Up @@ -63,6 +64,64 @@ export function findLogNode(node: LogNode, predicate: ProcessNode<LogNode>): Log
return found
}

/**
* Given a LogNode, get a list of LogEntries that represent the last `lines` number of log lines nested under the node.
* Note that the returned number of lines may be slightly higher, so you should slice after rendering them (which
* you anyway need to do if you're wrapping the lines to a certain width).
*
* @param node the log node whose child entries we want to tail
* @param level maximum log level to include
* @param lines how many lines to aim for
*/
export function tailChildEntries(node: LogNode | LogEntry, level: LogLevel, lines: number): LogEntry[] {
let output: LogEntry[] = []
let outputLines = 0

traverseChildren<LogNode, LogEntry>(node, (entry) => {
if (entry.level <= level) {
output.push(entry)
const msg = entry.getMessageState().msg || ""
outputLines += msg.length > 0 ? msg.split("\n").length : 0

if (outputLines >= lines) {
return false
}
}
return true
})

return output
}

/**
* Get the log entry preceding the given `entry` in its tree, given the minimum log `level`.
*/
export function getPrecedingEntry(entry: LogEntry) {
if (!entry.parent) {
// This is the first entry in its tree
return
}

const siblings = entry.parent.children
const index = siblings.findIndex((e) => e.key === entry.key)

if (index === 0) {
// The nearest entry is the parent
return entry.parent
} else {
// The nearest entry is the last entry nested under the next sibling above,
// or the sibling itself if it has no child nodes
const sibling = siblings[index - 1]
const siblingChildren = getChildEntries(sibling)

if (siblingChildren.length > 0) {
return siblingChildren[siblingChildren.length - 1]
} else {
return sibling
}
}
}

interface StreamWriteExtraParam {
noIntercept?: boolean
}
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/logger/writers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export abstract class Writer {

constructor(public level: LogLevel = LogLevel.info) {}

abstract render(...args): string | string[] | null
abstract onGraphChange(entry: LogEntry, logger: Logger): void
abstract stop(): void
cleanup(): void {}
}
Loading

0 comments on commit 038328a

Please sign in to comment.