From 41cddf0679166f9799baf30e33581a97dd7ff616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BE=C3=B3r=20Magn=C3=BAsson?= Date: Thu, 26 Apr 2018 19:42:30 +0200 Subject: [PATCH] feat: pass parent to nested log entries --- bin/integ | 4 +-- src/cli.ts | 19 +++++++---- src/logger/index.ts | 43 ++++++++++++----------- src/logger/renderers.ts | 4 +++ src/logger/types.ts | 9 ++--- src/logger/writers.ts | 17 +++++---- test/setup.ts | 4 +-- test/src/logger.ts | 76 ++++++++++++++++++++++++++++++----------- 8 files changed, 111 insertions(+), 65 deletions(-) diff --git a/bin/integ b/bin/integ index 703c75822b..7492743d95 100755 --- a/bin/integ +++ b/bin/integ @@ -7,8 +7,6 @@ garden_bin=${garden_root}/build/src/bin/garden.js chmod +x ${garden_bin} -export GARDEN_LOGGER_TYPE=basic - cd ${garden_root}/examples/hello-world -${garden_bin} build --force +${garden_bin} build --force --silent diff --git a/src/cli.ts b/src/cli.ts index 1534ea0da4..2cdcc04d69 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,6 +11,7 @@ import chalk from "chalk" import { enumToArray, shutdown } from "./util" import { merge, intersection, reduce } from "lodash" import { + BooleanParameter, Command, ChoicesParameter, ParameterValues, @@ -44,6 +45,11 @@ const GLOBAL_OPTIONS = { help: "override project root directory (defaults to working directory)", defaultValue: process.cwd(), }), + silent: new BooleanParameter({ + alias: "s", + help: "suppress log output", + defaultValue: false, + }), env: new EnvironmentOption(), loglevel: new ChoicesParameter({ alias: "log", @@ -193,7 +199,6 @@ export class GardenCli { constructor() { const version = require("../package.json").version - this.logger = getLogger() this.program = sywac .help("-h, --help", { @@ -261,22 +266,24 @@ export class GardenCli { } const action = async argv => { - const logger = this.logger - // Sywac returns positional args and options in a single object which we separate into args and opts const argsForAction = filterByArray(argv, argKeys) const optsForAction = filterByArray(argv, optKeys.concat(globalKeys)) const root = resolve(process.cwd(), optsForAction.root) const env = optsForAction.env - // Update logger config - const level = LogLevel[argv.loglevel] + // Update logger + const logger = this.logger + const { loglevel, silent } = optsForAction + const level = LogLevel[loglevel] logger.level = level - if (level !== LogLevel.silent) { + if (!silent) { logger.writers.push( new FileWriter({ level, root }), new FileWriter({ level: LogLevel.error, filename: ERROR_LOG_FILENAME, root }), ) + } else { + logger.writers = [] } const garden = await Garden.factory(root, { env, logger }) diff --git a/src/logger/index.ts b/src/logger/index.ts index 81910c223d..4800916cec 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -24,11 +24,11 @@ import { LogEntryOpts, LogSymbolType, } from "./types" -import { FancyConsoleWriter, Writer, BasicConsoleWriter } from "./writers" +import { BasicConsoleWriter, FancyConsoleWriter, Writer } from "./writers" import { ParameterError } from "../exceptions" const ROOT_DEPTH = -1 -const DEFAULT_CONFIGS: { [key in LoggerType]: LoggerConfig } = { +const CONFIG_TYPES: { [key in LoggerType]: LoggerConfig } = { [LoggerType.fancy]: { level: LogLevel.info, writers: [new FancyConsoleWriter()], @@ -52,19 +52,25 @@ export interface LogEntryConstructor { opts: LogEntryOpts depth: number root: RootLogNode + parentEntry?: LogEntry } let loggerInstance: RootLogNode -let defaultLoggerType: LoggerType = LoggerType.fancy -let defaultLoggerConfig: LoggerConfig = DEFAULT_CONFIGS[defaultLoggerType] - -function createLogEntry(level: LogLevel, opts: LogEntryOpts, parent: LogNode) { - const { depth, root } = parent +let loggerType: LoggerType = LoggerType.fancy +let loggerConfig: LoggerConfig = CONFIG_TYPES[loggerType] + +function createLogEntry(level: LogLevel, opts: LogEntryOpts, parentNode: LogNode) { + const { depth, root } = parentNode + let parentEntry + if (parentNode.depth > ROOT_DEPTH) { + parentEntry = parentNode + } const params = { depth: depth + 1, root, level, opts, + parentEntry, } return new LogEntry(params) } @@ -140,11 +146,13 @@ export class LogEntry extends LogNode { public timestamp: number public level: LogLevel public depth: number + public parentEntry: LogEntry | undefined public children: LogEntry[] - constructor({ level, opts, depth, root }: LogEntryConstructor) { + constructor({ level, opts, depth, root, parentEntry }: LogEntryConstructor) { super(level, depth) this.root = root + this.parentEntry = parentEntry this.opts = opts if (opts.entryStyle === EntryStyle.activity) { this.status = EntryStatus.ACTIVE @@ -202,7 +210,6 @@ export class LogEntry extends LogNode { return this } - // public setSuccess: UpdateLogEntry = (entryVal: CreateLogEntryParam = {}): LogEntry => { public setSuccess: UpdateLogEntry = (entryVal: UpdateLogEntryParam = {}): LogEntry => { this.deepSetState({ ...makeLogOpts(entryVal), symbol: LogSymbolType.success }, EntryStatus.SUCCESS) this.root.onGraphChange(this) @@ -291,21 +298,17 @@ export class RootLogNode extends LogNode { } -export function getLogger(config?: LoggerConfig) { +export function getLogger() { if (!loggerInstance) { - loggerInstance = new RootLogNode(config || defaultLoggerConfig) + loggerInstance = new RootLogNode(loggerConfig) } return loggerInstance } -export function setDefaultLoggerType(loggerType: LoggerType) { - defaultLoggerType = loggerType - defaultLoggerConfig = DEFAULT_CONFIGS[loggerType] -} - -export function setDefaultLoggerConfig(config: LoggerConfig) { - defaultLoggerConfig = config +export function setLoggerType(type: LoggerType) { + loggerType = type + loggerConfig = CONFIG_TYPES[type] } // allow configuring logger type via environment variable @@ -320,7 +323,7 @@ if (process.env.GARDEN_LOGGER_TYPE) { }) } - defaultLoggerType = type - defaultLoggerConfig = DEFAULT_CONFIGS[type] + setLoggerType(type) + getLogger().debug(`Setting logger type to ${type} (from GARDEN_LOGGER_TYPE)`) } diff --git a/src/logger/renderers.ts b/src/logger/renderers.ts index 039afb9227..fbaeb32bed 100644 --- a/src/logger/renderers.ts +++ b/src/logger/renderers.ts @@ -55,6 +55,10 @@ export function combine(renderers: Renderers): string { /*** RENDERERS ***/ export function leftPad(entry: LogEntry): string { + const { parentEntry } = entry + if (parentEntry && parentEntry.opts.unindentChildren) { + return "" + } return padStart("", entry.depth * 3) } diff --git a/src/logger/types.ts b/src/logger/types.ts index 4a79af45d6..19b2bc07fd 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -13,7 +13,6 @@ import { GardenError } from "../exceptions" export type EmojiName = keyof typeof nodeEmoji.emoji export enum LogLevel { - silent = -1, error = 0, warn = 1, info = 2, @@ -28,7 +27,6 @@ export enum LoggerType { quiet = "quiet", } -// Defines entry style and format (only one available style at the moment) export enum EntryStyle { activity = "activity", error = "error", @@ -51,11 +49,7 @@ export enum EntryStatus { WARN = "warn", } -export interface HeaderOpts { - emoji?: string - command: string -} - +// TODO Split up export interface LogEntryOpts { msg?: string | string[] section?: string @@ -67,4 +61,5 @@ export interface LogEntryOpts { showDuration?: boolean error?: GardenError | Error id?: string + unindentChildren?: boolean } diff --git a/src/logger/writers.ts b/src/logger/writers.ts index bbcde95e11..78d283b395 100644 --- a/src/logger/writers.ts +++ b/src/logger/writers.ts @@ -48,6 +48,9 @@ const DEFAULT_FILE_TRANSPORT_OPTIONS = { } const levelToStr = (lvl: LogLevel): string => LogLevel[lvl] +const isEntryValid = (level: LogLevel, entry: LogEntry): boolean => { + return level >= entry.level && entry.opts.msg !== undefined +} export interface WriterConfig { level?: LogLevel @@ -100,7 +103,7 @@ export class FileWriter extends Writer { render(entry: LogEntry): string | null { const renderFn = entry.level === LogLevel.error ? renderError : renderMsg - if (entry.opts.msg && this.level >= entry.level) { + if (isEntryValid(this.level, entry)) { return stripAnsi(renderFn(entry)) } return null @@ -144,7 +147,7 @@ export class BasicConsoleWriter extends Writer { render(entry: LogEntry, rootLogNode: RootLogNode): string | null { const level = this.level || rootLogNode.level - if (level >= entry.level) { + if (isEntryValid(level, entry)) { return formatForConsole(entry) } return null @@ -246,10 +249,10 @@ export class FancyConsoleWriter extends Writer { } } - /* - Has a side effect in that it starts/stops the rendering loop depending on - whether or not active entries were found while building output - */ + /** + * Has a side effect in that it starts/stops the rendering loop depending on + * whether or not active entries were found while building output + */ public render(rootLogNode: RootLogNode): string[] | null { let hasActiveEntries = false const level = this.level || rootLogNode.level @@ -272,7 +275,7 @@ export class FancyConsoleWriter extends Writer { hasActiveEntries = true spinnerFrame = this.readOrSetSpinner(idx) } - if (level >= entry.level) { + if (isEntryValid(level, entry)) { const formatted = this.readerOrSetFormattedEntry(entry, idx) const startPos = leftPad(entry).length const withSpinner = spinnerFrame diff --git a/test/setup.ts b/test/setup.ts index 407d4df543..62dd51315a 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,7 +1,7 @@ import { LoggerType } from "../src/logger/types" -import { setDefaultLoggerType } from "../src/logger" +import { setLoggerType, getLogger } from "../src/logger" // Global before hooks before(() => { - setDefaultLoggerType(LoggerType.quiet) + setLoggerType(LoggerType.quiet) }) diff --git a/test/src/logger.ts b/test/src/logger.ts index eca8672705..3c758f3556 100644 --- a/test/src/logger.ts +++ b/test/src/logger.ts @@ -61,7 +61,7 @@ describe("RootLogNode", () => { }) describe("addNode", () => { - it("should add new child entries to respective node", () => { + it("should add new child entries to the respective node", () => { const prevLength = logger.children.length const entry = logger.children[0] const nested = entry.info("nested") @@ -78,32 +78,68 @@ describe("RootLogNode", () => { }) describe("BasicConsoleWriter.render", () => { - it("should return a string if log level is geq than entry level", () => { - const writer = new BasicConsoleWriter({ level: LogLevel.silent }) - const logger = new RootLogNode({ level: LogLevel.silent }) + it("should return a string if level is geq than entry level and entry contains a message", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new BasicConsoleWriter() const entry = logger.info("") - const out1 = writer.render(entry, logger) - writer.level = LogLevel.verbose - const out2 = writer.render(entry, logger) - - expect(out1).to.be.a("null") - expect(out2).to.be.a("string") + const out = writer.render(entry, logger) + expect(out).to.eql("") + }) + it("should override root level if level is set", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new BasicConsoleWriter({ level: LogLevel.verbose }) + const entry = logger.verbose("") + const out = writer.render(entry, logger) + expect(out).to.eql("") + }) + it("should return null if entry level is geq to writer level", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new BasicConsoleWriter() + const entry = logger.verbose("") + const out = writer.render(entry, logger) + expect(out).to.eql(null) + }) + it("should return null if entry has no message", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new BasicConsoleWriter() + const entry = logger.info({}) + const out = writer.render(entry, logger) + expect(out).to.eql(null) }) }) describe("FancyConsoleWriter.render", () => { - it("should return an array of strings if log level is geq than respective entry level", () => { - const writer = new FancyConsoleWriter({ level: LogLevel.silent }) - const logger = new RootLogNode({ level: LogLevel.silent }) + it("should return an array of strings if level is geq than entry level and entry contains a message", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new FancyConsoleWriter() const entry = logger.info("") - const out1 = writer.render(logger) - writer.level = LogLevel.verbose - const out2 = writer.render(logger) - + const out = writer.render(logger) writer.stop() - - expect(out1).to.be.a("null") - expect(out2).to.be.an("array").of.length(1) + expect(out).to.eql(["\n"]) + }) + it("should override root level if level is set", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new FancyConsoleWriter({ level: LogLevel.verbose }) + writer.stop() + const entry = logger.verbose("") + const out = writer.render(logger) + expect(out).to.eql(["\n"]) + }) + it("should return null if entry level is geq to writer level", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new FancyConsoleWriter() + writer.stop() + const entry = logger.verbose("") + const out = writer.render(logger) + expect(out).to.eql(null) + }) + it("should return null if entry has no message", () => { + const logger = new RootLogNode({ level: LogLevel.info }) + const writer = new FancyConsoleWriter() + writer.stop() + const entry = logger.info({}) + const out = writer.render(logger) + expect(out).to.eql(null) }) })