diff --git a/cli/src/add-version-files.ts b/cli/src/add-version-files.ts index daf1408d48..39ea7b85ed 100644 --- a/cli/src/add-version-files.ts +++ b/cli/src/add-version-files.ts @@ -8,8 +8,7 @@ import { GitHandler } from "@garden-io/core/build/src/vcs/git" import { Garden } from "@garden-io/core/build/src/garden" -import { Logger } from "@garden-io/core/build/src/logger/logger" -import { LogLevel } from "@garden-io/core/build/src/logger/log-node" +import { Logger, LogLevel } from "@garden-io/core/build/src/logger/logger" import { resolve, relative } from "path" import Bluebird from "bluebird" import { STATIC_DIR, GARDEN_VERSIONFILE_NAME } from "@garden-io/core/build/src/constants" @@ -20,7 +19,7 @@ require("source-map-support").install() // make sure logger is initialized try { - Logger.initialize({ level: LogLevel.info }) + Logger.initialize({ level: LogLevel.info, type: "quiet" }) } catch (_) {} /** diff --git a/cli/src/generate-docs.ts b/cli/src/generate-docs.ts index 37ca9a7496..329dea8231 100644 --- a/cli/src/generate-docs.ts +++ b/cli/src/generate-docs.ts @@ -8,8 +8,7 @@ import { generateDocs } from "@garden-io/core/build/src/docs/generate" import { resolve } from "path" -import { Logger } from "@garden-io/core/build/src/logger/logger" -import { LogLevel } from "@garden-io/core/build/src/logger/log-node" +import { Logger, LogLevel } from "@garden-io/core/build/src/logger/logger" import { GARDEN_CLI_ROOT } from "@garden-io/core/build/src/constants" import { getBundledPlugins } from "./cli" import { getSupportedPlugins } from "@garden-io/core/build/src/plugins/plugins" @@ -20,6 +19,7 @@ require("source-map-support").install() try { Logger.initialize({ level: LogLevel.info, + type: "quiet", // level: LogLevel.debug, // writers: [new BasicTerminalWriter()], }) diff --git a/core/src/cli/cli.ts b/core/src/cli/cli.ts index 013ac1b6ad..a1f93d4145 100644 --- a/core/src/cli/cli.ts +++ b/core/src/cli/cli.ts @@ -14,9 +14,7 @@ import { shutdown, sleep, getPackageVersion, uuidv4 } from "../util/util" import { Command, CommandResult, CommandGroup } from "../commands/base" import { GardenError, PluginError, toGardenError, GardenBaseError } from "../exceptions" import { Garden, GardenOpts, DummyGarden } from "../garden" -import { getLogger, Logger, LoggerType, getWriterInstance, parseLogLevel } from "../logger/logger" -import { LogLevel } from "../logger/log-node" -import { BasicTerminalWriter } from "../logger/writers/basic-terminal-writer" +import { getLogger, Logger, LoggerType, LogLevel, parseLogLevel } from "../logger/logger" import { FileWriter, FileWriterConfig } from "../logger/writers/file-writer" import { @@ -63,22 +61,6 @@ export async function makeDummyGarden(root: string, gardenOpts: GardenOpts) { return DummyGarden.factory(root, { noEnterprise: true, ...gardenOpts }) } -function initLogger({ - level, - loggerType, - emoji, - showTimestamps, -}: { - level: LogLevel - loggerType: LoggerType - emoji: boolean - showTimestamps: boolean -}) { - const writer = getWriterInstance(loggerType, level) - const writers = writer ? [writer] : undefined - return Logger.initialize({ level, writers, showTimestamps, useEmoji: emoji }) -} - export interface RunOutput { argv: any code: number @@ -134,7 +116,7 @@ ${renderCommands(commands)} }, ] for (const config of logConfigs) { - logger.writers.push(await FileWriter.factory(config)) + logger.addWriter(await FileWriter.factory(config)) } this.fileWritersInitialized = true } @@ -194,11 +176,9 @@ ${renderCommands(commands)} if (silent || output) { loggerType = "quiet" - } else if (loggerType === "fancy" && (level > LogLevel.info || showTimestamps)) { - loggerType = "basic" } - const logger = initLogger({ level, loggerType, emoji, showTimestamps }) + const logger = Logger.initialize({ level, type: loggerType, useEmoji: emoji, showTimestamps }) // Currently we initialise empty placeholder entries and pass those to the // framework as opposed to the logger itself. This is to give better control over where on @@ -474,7 +454,7 @@ ${renderCommands(commands)} } catch (_) { logger = Logger.initialize({ level: LogLevel.info, - writers: [new BasicTerminalWriter()], + type: "basic", }) } @@ -492,7 +472,7 @@ ${renderCommands(commands)} }) } - if (logger.writers.find((w) => w instanceof FileWriter)) { + if (logger.getWriters().find((w) => w instanceof FileWriter)) { logger.info(`\nSee ${ERROR_LOG_FILENAME} for detailed error message`) await waitForOutputFlush() } diff --git a/core/src/cli/params.ts b/core/src/cli/params.ts index 3b56fc2f71..f7b3203733 100644 --- a/core/src/cli/params.ts +++ b/core/src/cli/params.ts @@ -13,14 +13,14 @@ import stringify from "json-stringify-safe" import { joi, DeepPrimitiveMap } from "../config/common" import { ParameterError } from "../exceptions" import { parseEnvironment } from "../config/project" -import { LOGGER_TYPES, getLogLevelChoices, envSupportsEmoji } from "../logger/logger" +import { getLogLevelChoices, LOGGER_TYPES, LogLevel } from "../logger/logger" import { dedent, deline } from "../util/string" import chalk = require("chalk") -import { LogLevel } from "../logger/log-node" import { safeDumpYaml } from "../util/util" import { resolve } from "path" import { isArray } from "lodash" import { gardenEnv } from "../constants" +import { envSupportsEmoji } from "../logger/util" export const OUTPUT_RENDERERS = { json: (data: DeepPrimitiveMap) => { diff --git a/core/src/commands/logs.ts b/core/src/commands/logs.ts index 242b51092b..a3c6ddc028 100644 --- a/core/src/commands/logs.ts +++ b/core/src/commands/logs.ts @@ -13,11 +13,10 @@ import { ServiceLogEntry } from "../types/plugin/service/getServiceLogs" import Bluebird = require("bluebird") import { GardenService } from "../types/service" import Stream from "ts-stream" -import { LoggerType, logLevelMap, parseLogLevel } from "../logger/logger" +import { LoggerType, logLevelMap, LogLevel, parseLogLevel } from "../logger/logger" import { StringsParameter, BooleanParameter, IntegerParameter, DurationParameter } from "../cli/params" import { printHeader, renderDivider } from "../logger/util" import stripAnsi = require("strip-ansi") -import { LogLevel } from "../logger/log-node" import hasAnsi = require("has-ansi") import { dedent } from "../util/string" import { formatSection } from "../logger/renderers" diff --git a/core/src/commands/run/workflow.ts b/core/src/commands/run/workflow.ts index 5775746279..935fa74db9 100644 --- a/core/src/commands/run/workflow.ts +++ b/core/src/commands/run/workflow.ts @@ -28,7 +28,7 @@ import Bluebird from "bluebird" import { getDurationMsec, toEnvVars } from "../../util/util" import { runScript } from "../../util/util" import { ExecaError } from "execa" -import { LogLevel } from "../../logger/log-node" +import { LogLevel } from "../../logger/logger" import { registerWorkflowRun } from "../../enterprise/workflow-lifecycle" import { parseCliArgs, pickCommand, processCliArgs } from "../../cli/helpers" import { StringParameter } from "../../cli/params" diff --git a/core/src/enterprise/buffered-event-stream.ts b/core/src/enterprise/buffered-event-stream.ts index 6bfad357ec..d2d83f192f 100644 --- a/core/src/enterprise/buffered-event-stream.ts +++ b/core/src/enterprise/buffered-event-stream.ts @@ -13,7 +13,7 @@ import { Events, EventName, EventBus, eventNames } from "../events" import { LogEntryMetadata, LogEntry, LogEntryMessage } from "../logger/log-entry" import { got } from "../util/http" -import { LogLevel } from "../logger/log-node" +import { LogLevel } from "../logger/logger" import { Garden } from "../garden" import { EnterpriseApi, makeAuthHeader } from "./api" diff --git a/core/src/logger/log-entry.ts b/core/src/logger/log-entry.ts index cf26dca541..937b9aaec2 100644 --- a/core/src/logger/log-entry.ts +++ b/core/src/logger/log-entry.ts @@ -8,13 +8,14 @@ import logSymbols from "log-symbols" import nodeEmoji from "node-emoji" -import { cloneDeep, merge } from "lodash" +import { cloneDeep, merge, round } from "lodash" -import { LogNode, LogLevel, CreateNodeParams, PlaceholderOpts } from "./log-node" +import { LogLevel, LogNode } from "./logger" import { Omit } from "../util/util" import { getChildEntries, findParentEntry } from "./util" import { GardenError } from "../exceptions" -import { Logger } from "./logger" +import { CreateNodeParams, Logger, PlaceholderOpts } from "./logger" +import uniqid from "uniqid" export type EmojiName = keyof typeof nodeEmoji.emoji export type LogSymbol = keyof typeof logSymbols | "empty" @@ -39,8 +40,6 @@ export interface WorkflowStepMetadata { index: number } -export const EVENT_LOG_LEVEL = LogLevel.debug - interface MessageBase { msg?: string emoji?: EmojiName @@ -76,7 +75,14 @@ export interface LogEntryConstructor extends LogEntryParams { isPlaceholder?: boolean } -function resolveParams(params?: string | UpdateLogEntryParams): UpdateLogEntryParams { +function resolveCreateParams(level: LogLevel, params: string | LogEntryParams): CreateNodeParams { + if (typeof params === "string") { + return { msg: params, level } + } + return { ...params, level } +} + +function resolveUpdateParams(params?: string | UpdateLogEntryParams): UpdateLogEntryParams { if (typeof params === "string") { return { msg: params } } else if (!params) { @@ -86,25 +92,30 @@ function resolveParams(params?: string | UpdateLogEntryParams): UpdateLogEntryPa } } -// FIXME: Refactor to better distinguish between normal log entries and placeholder -// log entries and to get rid of these "god" interfaces. -// We should also better distinguish between message data and log entry data -// and enforce that some message data is set for non-placeholder entries. -export class LogEntry extends LogNode { +export class LogEntry implements LogNode { private messages: LogEntryMessage[] private metadata?: LogEntryMetadata + public readonly parent?: LogEntry + public readonly timestamp: Date + public readonly key: string + public readonly level: LogLevel public readonly root: Logger public readonly fromStdStream?: boolean public readonly indent?: number public readonly errorData?: GardenError public readonly childEntriesInheritLevel?: boolean public readonly id?: string + public children: LogEntry[] public isPlaceholder: boolean public revision: number constructor(params: LogEntryConstructor) { - super(params.level, params.parent, params.id) - + this.key = uniqid() + this.children = [] + this.timestamp = new Date() + this.level = params.level + this.parent = params.parent + this.id = params.id this.root = params.root this.fromStdStream = params.fromStdStream this.indent = params.indent @@ -138,7 +149,7 @@ export class LogEntry extends LogNode { * 2. append is always set explicitly (the next message does not inherit the previous value) * 3. next metadata is merged with the previous metadata */ - protected update(updateParams: UpdateLogEntryParams): void { + private update(updateParams: UpdateLogEntryParams): void { this.revision = this.revision + 1 const latestMessage = this.getLatestMessage() @@ -196,7 +207,7 @@ export class LogEntry extends LogNode { } } - protected createNode(params: CreateNodeParams) { + private createNode(params: CreateNodeParams) { const indent = params.indent !== undefined ? params.indent : (this.indent || 0) + 1 // If childEntriesInheritLevel is set to true, all children must have a level geq to the level @@ -219,8 +230,37 @@ export class LogEntry extends LogNode { }) } - protected onGraphChange(node: LogEntry) { - this.root.onGraphChange(node) + private addNode(params: CreateNodeParams): LogEntry { + const entry = this.createNode(params) + if (this.root.storeEntries) { + this.children.push(entry) + } + this.root.onGraphChange(entry) + return entry + } + + silly(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.silly, params)) + } + + debug(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.debug, params)) + } + + verbose(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.verbose, params)) + } + + info(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.info, params)) + } + + warn(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.warn, params)) + } + + error(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveCreateParams(LogLevel.error, params)) } getMetadata() { @@ -265,44 +305,44 @@ export class LogEntry extends LogNode { // Preserves status setState(params?: string | UpdateLogEntryParams): LogEntry { - this.deepUpdate({ ...resolveParams(params) }) - this.onGraphChange(this) + this.deepUpdate({ ...resolveUpdateParams(params) }) + this.root.onGraphChange(this) return this } setDone(params?: string | Omit): LogEntry { - this.deepUpdate({ ...resolveParams(params), status: "done" }) - this.onGraphChange(this) + this.deepUpdate({ ...resolveUpdateParams(params), status: "done" }) + this.root.onGraphChange(this) return this } setSuccess(params?: string | Omit): LogEntry { this.deepUpdate({ - ...resolveParams(params), + ...resolveUpdateParams(params), symbol: "success", status: "success", }) - this.onGraphChange(this) + this.root.onGraphChange(this) return this } setError(params?: string | Omit): LogEntry { this.deepUpdate({ - ...resolveParams(params), + ...resolveUpdateParams(params), symbol: "error", status: "error", }) - this.onGraphChange(this) + this.root.onGraphChange(this) return this } setWarn(param?: string | Omit): LogEntry { this.deepUpdate({ - ...resolveParams(param), + ...resolveUpdateParams(param), symbol: "warning", status: "warn", }) - this.onGraphChange(this) + this.root.onGraphChange(this) return this } @@ -314,7 +354,7 @@ export class LogEntry extends LogNode { // Stop gracefully if still in active state if (this.getLatestMessage().status === "active") { this.update({ symbol: "empty", status: "done" }) - this.onGraphChange(this) + this.root.onGraphChange(this) } return this } @@ -335,4 +375,11 @@ export class LogEntry extends LogNode { .flatMap((entry) => entry.getMessages()?.map((message) => message.msg)) .join("\n") } + + /** + * Returns the duration in seconds, defaults to 2 decimal precision + */ + getDuration(precision: number = 2): number { + return round((new Date().getTime() - this.timestamp.getTime()) / 1000, precision) + } } diff --git a/core/src/logger/log-node.ts b/core/src/logger/log-node.ts deleted file mode 100644 index 9f973f3c20..0000000000 --- a/core/src/logger/log-node.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2018-2021 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import uniqid from "uniqid" -import { round } from "lodash" - -import { LogEntry, LogEntryParams, LogEntryMetadata } from "./log-entry" - -export enum LogLevel { - error = 0, - warn = 1, - info = 2, - verbose = 3, - debug = 4, - silly = 5, -} - -export interface CreateNodeParams extends LogEntryParams { - level: LogLevel - isPlaceholder?: boolean -} - -export interface PlaceholderOpts { - level?: number - childEntriesInheritLevel?: boolean - indent?: number - metadata?: LogEntryMetadata -} - -export function resolveParams(level: LogLevel, params: string | LogEntryParams): CreateNodeParams { - if (typeof params === "string") { - return { msg: params, level } - } - return { ...params, level } -} - -export abstract class LogNode { - public readonly timestamp: Date - public readonly key: string - public readonly children: LogEntry[] - - constructor(public readonly level: LogLevel, public readonly parent?: LogEntry, public readonly id?: string) { - this.key = uniqid() - this.timestamp = new Date() - this.children = [] - } - - protected abstract createNode(params: CreateNodeParams): LogEntry - protected abstract onGraphChange(node: LogEntry): void - - /** - * A placeholder entry is an empty entry whose children should be aligned with the parent context. - * Useful for setting a placeholder in the middle of the log that can later be populated. - */ - abstract placeholder(opts?: PlaceholderOpts): LogEntry - - protected addNode(params: CreateNodeParams): LogEntry { - const node = this.createNode(params) - this.children.push(node) - this.onGraphChange(node) - return node - } - - silly(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.silly, params)) - } - - debug(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.debug, params)) - } - - verbose(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.verbose, params)) - } - - info(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.info, params)) - } - - warn(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.warn, params)) - } - - error(params: string | LogEntryParams): LogEntry { - return this.addNode(resolveParams(LogLevel.error, params)) - } - - /** - * Returns the duration in seconds, defaults to 2 decimal precision - */ - getDuration(precision: number = 2): number { - return round((new Date().getTime() - this.timestamp.getTime()) / 1000, precision) - } -} diff --git a/core/src/logger/logger.ts b/core/src/logger/logger.ts index bfdd6c3f3e..23432eb421 100644 --- a/core/src/logger/logger.ts +++ b/core/src/logger/logger.ts @@ -6,12 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LogNode, CreateNodeParams, PlaceholderOpts } from "./log-node" -import { LogEntry, EVENT_LOG_LEVEL } from "./log-entry" -import { getChildEntries, findLogNode } from "./util" +import { LogEntry, LogEntryMetadata, LogEntryParams } from "./log-entry" +import { getChildEntries, findLogEntry } from "./util" import { Writer } from "./writers/base" -import { InternalError, ParameterError } from "../exceptions" -import { LogLevel } from "./log-node" +import { CommandError, InternalError, ParameterError } from "../exceptions" import { BasicTerminalWriter } from "./writers/basic-terminal-writer" import { FancyTerminalWriter } from "./writers/fancy-terminal-writer" import { JsonTerminalWriter } from "./writers/json-terminal-writer" @@ -25,19 +23,19 @@ import { range } from "lodash" export type LoggerType = "quiet" | "basic" | "fancy" | "fullscreen" | "json" export const LOGGER_TYPES = new Set(["quiet", "basic", "fancy", "fullscreen", "json"]) -export const logLevelMap = { - [LogLevel.error]: "error", - [LogLevel.warn]: "warn", - [LogLevel.info]: "info", - [LogLevel.verbose]: "verbose", - [LogLevel.debug]: "debug", - [LogLevel.silly]: "silly", +export enum LogLevel { + error = 0, + warn = 1, + info = 2, + verbose = 3, + debug = 4, + silly = 5, } + const getLogLevelNames = () => getEnumKeys(LogLevel) const getNumericLogLevels = () => range(getLogLevelNames().length) // Allow string or numeric log levels as CLI choices export const getLogLevelChoices = () => [...getLogLevelNames(), ...getNumericLogLevels().map(String)] - export function parseLogLevel(level: string): LogLevel { let lvl: LogLevel const parsed = parseInt(level, 10) @@ -57,13 +55,17 @@ export function parseLogLevel(level: string): LogLevel { return lvl } -// Add platforms/terminals? -export function envSupportsEmoji() { - return ( - process.platform === "darwin" || process.env.TERM_PROGRAM === "Hyper" || process.env.TERM_PROGRAM === "HyperTerm" - ) +export const logLevelMap = { + [LogLevel.error]: "error", + [LogLevel.warn]: "warn", + [LogLevel.info]: "info", + [LogLevel.verbose]: "verbose", + [LogLevel.debug]: "debug", + [LogLevel.silly]: "silly", } +const eventLogLevel = LogLevel.debug + export function getWriterInstance(loggerType: LoggerType, level: LogLevel) { switch (loggerType) { case "basic": @@ -79,19 +81,64 @@ export function getWriterInstance(loggerType: LoggerType, level: LogLevel) { } } -export interface LoggerConfig { +export interface LoggerConfigBase { level: LogLevel + storeEntries?: boolean showTimestamps?: boolean - writers?: Writer[] useEmoji?: boolean } -export class Logger extends LogNode { - public writers: Writer[] +export interface LoggerConfig extends LoggerConfigBase { + type: LoggerType +} + +export interface LoggerConstructor extends LoggerConfigBase { + writers: Writer[] + storeEntries: boolean +} + +export interface CreateNodeParams extends LogEntryParams { + level: LogLevel + isPlaceholder?: boolean +} + +export interface PlaceholderOpts { + level?: number + childEntriesInheritLevel?: boolean + indent?: number + metadata?: LogEntryMetadata +} + +export interface LogNode { + silly(params: string | LogEntryParams): LogEntry + debug(params: string | LogEntryParams): LogEntry + verbose(params: string | LogEntryParams): LogEntry + info(params: string | LogEntryParams): LogEntry + warn(params: string | LogEntryParams): LogEntry + error(params: string | LogEntryParams): LogEntry +} + +function resolveParams(level: LogLevel, params: string | LogEntryParams): CreateNodeParams { + if (typeof params === "string") { + return { msg: params, level } + } + return { ...params, level } +} + +export class Logger implements LogNode { public events: EventBus public useEmoji: boolean public showTimestamps: boolean + public level: LogLevel + public children: LogEntry[] + /** + * Whether or not the log entries are stored in-memory on the logger instance. + * Defaults to false except when the FancyWriter is used, in which case storing the entries + * is required. Otherwise useful for testing. + */ + public storeEntries: boolean + private writers: Writer[] private static instance?: Logger static getInstance() { @@ -101,6 +148,10 @@ export class Logger extends LogNode { return Logger.instance } + /** + * Initializes the logger as a singleton from config. Also ensures that the logger settings make sense + * in the context of environment variables and writer types. + */ static initialize(config: LoggerConfig): Logger { if (Logger.instance) { return Logger.instance @@ -113,31 +164,42 @@ export class Logger extends LogNode { try { config.level = parseLogLevel(gardenEnv.GARDEN_LOG_LEVEL) } catch (err) { - // Log warning if level invalid but continue process. - // Using console logger since Garden logger hasn't been intialised. - console.warn("Warning:", err.message) + throw new CommandError(`Invalid log level set for GARDEN_LOG_LEVEL: ${err.message}`, {}) } } // GARDEN_LOGGER_TYPE env variable takes precedence over the config param if (gardenEnv.GARDEN_LOGGER_TYPE) { - const loggerType = gardenEnv.GARDEN_LOGGER_TYPE + const loggerTypeFromEnv = gardenEnv.GARDEN_LOGGER_TYPE - if (!LOGGER_TYPES.has(loggerType)) { - throw new ParameterError(`Invalid logger type specified: ${loggerType}`, { + if (!LOGGER_TYPES.has(loggerTypeFromEnv)) { + throw new ParameterError(`Invalid logger type specified: ${loggerTypeFromEnv}`, { loggerType: gardenEnv.GARDEN_LOGGER_TYPE, availableTypes: LOGGER_TYPES, }) } - const writer = getWriterInstance(loggerType, config.level) - instance = new Logger({ - writers: writer ? [writer] : undefined, - level: config.level, - }) - instance.debug(`Setting logger type to ${loggerType} (from GARDEN_LOGGER_TYPE)`) - } else { - instance = new Logger(config) + config.type = loggerTypeFromEnv + } + + // The fancy logger doesn't play well with high log levels and/or timestamps + // so we enforce that the type is set to basic. + if (config.type === "fancy" && (config.level > LogLevel.info || config.showTimestamps)) { + config.type = "basic" + } + + const writer = getWriterInstance(config.type, config.level) + // This should probably be a property on the writer itself but feels like an unncessary + // indirection for now. + const storeEntries = writer instanceof FancyTerminalWriter || config.storeEntries || false + + instance = new Logger({ ...config, storeEntries, writers: writer ? [writer] : [] }) + + if (gardenEnv.GARDEN_LOG_LEVEL) { + instance.debug(`Setting log level to ${gardenEnv.GARDEN_LOG_LEVEL} (from GARDEN_LOG_LEVEL)`) + } + if (gardenEnv.GARDEN_LOGGER_TYPE) { + instance.debug(`Setting logger type to ${gardenEnv.GARDEN_LOGGER_TYPE} (from GARDEN_LOGGER_TYPE)`) } Logger.instance = instance @@ -151,25 +213,35 @@ export class Logger extends LogNode { Logger.instance = undefined } - constructor(config: LoggerConfig) { - super(config.level) + constructor(config: LoggerConstructor) { + this.level = config.level + this.children = [] this.writers = config.writers || [] this.useEmoji = config.useEmoji === false ? false : true this.showTimestamps = !!config.showTimestamps this.events = new EventBus() + this.storeEntries = config.storeEntries + } + + private addNode(params: CreateNodeParams): LogEntry { + const entry = new LogEntry({ ...params, root: this }) + if (this.storeEntries) { + this.children.push(entry) + } + this.onGraphChange(entry) + return entry } - protected createNode(params: CreateNodeParams): LogEntry { - return new LogEntry({ ...params, root: this }) + addWriter(writer: Writer) { + this.writers.push(writer) } - placeholder({ level = LogLevel.info, indent, metadata }: PlaceholderOpts = {}): LogEntry { - // Ensure placeholder child entries align with parent context - return this.addNode({ level, indent: indent || -1, isPlaceholder: true, metadata }) + getWriters() { + return this.writers } onGraphChange(entry: LogEntry) { - if (entry.level <= EVENT_LOG_LEVEL && !entry.isPlaceholder) { + if (entry.level <= eventLogLevel && !entry.isPlaceholder) { this.events.emit("logEntry", formatLogEntryForEventStream(entry)) } for (const writer of this.writers) { @@ -179,20 +251,60 @@ export class Logger extends LogNode { } } + silly(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.silly, params)) + } + + debug(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.debug, params)) + } + + verbose(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.verbose, params)) + } + + info(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.info, params)) + } + + warn(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.warn, params)) + } + + error(params: string | LogEntryParams): LogEntry { + return this.addNode(resolveParams(LogLevel.error, params)) + } + + placeholder({ level = LogLevel.info, indent, metadata }: PlaceholderOpts = {}): LogEntry { + // Ensure placeholder child entries align with parent context + return this.addNode({ level, indent: indent || -1, isPlaceholder: true, metadata }) + } + getLogEntries(): LogEntry[] { + if (!this.storeEntries) { + throw new InternalError(`Cannot get entries when storeEntries=false`, {}) + } return getChildEntries(this).filter((entry) => !entry.fromStdStream) } filterBySection(section: string): LogEntry[] { + if (!this.storeEntries) { + throw new InternalError(`Cannot filter entries when storeEntries=false`, {}) + } return getChildEntries(this).filter((entry) => entry.getLatestMessage().section === section) } findById(id: string): LogEntry | void { - return findLogNode(this, (node) => node.id === id) + if (!this.storeEntries) { + throw new InternalError(`Cannot find entry when storeEntries=false`, {}) + } + return findLogEntry(this, (node) => node.id === id) } stop(): void { - this.getLogEntries().forEach((e) => e.stop()) + if (this.storeEntries) { + this.getLogEntries().forEach((e) => e.stop()) + } this.writers.forEach((writer) => writer.stop()) } diff --git a/core/src/logger/util.ts b/core/src/logger/util.ts index 9535bf8f2a..05572ea5a7 100644 --- a/core/src/logger/util.ts +++ b/core/src/logger/util.ts @@ -9,7 +9,8 @@ import nodeEmoji from "node-emoji" import chalk, { Chalk } from "chalk" import CircularJSON from "circular-json" -import { LogNode, LogLevel } from "./log-node" +import { LogLevel } from "./logger" +import { Logger } from "./logger" import { LogEntry, LogEntryParams, EmojiName } from "./log-entry" import { deepMap, deepFilter, safeDumpYaml } from "../util/util" import { padEnd, isEmpty } from "lodash" @@ -17,6 +18,13 @@ import { dedent } from "../util/string" import hasAnsi from "has-ansi" import { GardenError } from "../exceptions" +// Add platforms/terminals? +export function envSupportsEmoji() { + return ( + process.platform === "darwin" || process.env.TERM_PROGRAM === "Hyper" || process.env.TERM_PROGRAM === "HyperTerm" + ) +} + export interface Node { children: any[] } @@ -25,7 +33,7 @@ export type LogOptsResolvers = { [K in keyof LogEntryParams]?: Function } export type ProcessNode = (node: T) => boolean -function traverseChildren(node: T | U, cb: ProcessNode, reverse = false) { +function traverseChildren(node: Node, cb: ProcessNode, reverse = false) { const children = node.children for (let i = 0; i < children.length; i++) { const index = reverse ? children.length - 1 - i : i @@ -38,26 +46,26 @@ function traverseChildren(node: T | U, cb: Proce } // Parent (T|U) can have different type then child (U) -export function getChildNodes(node: T | U): U[] { +export function getChildNodes(node: T): U[] { let childNodes: U[] = [] - traverseChildren(node, (child) => { + traverseChildren(node, (child) => { childNodes.push(child) return true }) return childNodes } -export function getChildEntries(node: LogNode): LogEntry[] { - return getChildNodes(node) +export function getChildEntries(node: Logger | LogEntry): LogEntry[] { + return getChildNodes(node) } export function findParentEntry(entry: LogEntry, predicate: ProcessNode): LogEntry | null { return predicate(entry) ? entry : entry.parent ? findParentEntry(entry.parent, predicate) : null } -export function findLogNode(node: LogNode, predicate: ProcessNode): LogEntry | void { +export function findLogEntry(node: Logger | LogEntry, predicate: ProcessNode): LogEntry | void { let found: LogEntry | undefined - traverseChildren(node, (entry) => { + traverseChildren(node, (entry) => { if (predicate(entry)) { found = entry return false @@ -76,11 +84,11 @@ export function findLogNode(node: LogNode, predicate: ProcessNode): Log * @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[] { +export function tailChildEntries(node: Logger | LogEntry, level: LogLevel, lines: number): LogEntry[] { let output: LogEntry[] = [] let outputLines = 0 - traverseChildren(node, (entry) => { + traverseChildren(node, (entry) => { if (entry.level <= level) { output.push(entry) const msg = entry.getLatestMessage().msg || "" diff --git a/core/src/logger/writers/base.ts b/core/src/logger/writers/base.ts index 91ed42ebc7..dfb921ab7d 100644 --- a/core/src/logger/writers/base.ts +++ b/core/src/logger/writers/base.ts @@ -8,7 +8,7 @@ import { LogEntry } from "../log-entry" import { Logger } from "../logger" -import { LogLevel } from "../log-node" +import { LogLevel } from "../logger" export abstract class Writer { abstract type: string diff --git a/core/src/logger/writers/fancy-terminal-writer.ts b/core/src/logger/writers/fancy-terminal-writer.ts index 11d8583b90..4d6761f30c 100644 --- a/core/src/logger/writers/fancy-terminal-writer.ts +++ b/core/src/logger/writers/fancy-terminal-writer.ts @@ -14,8 +14,7 @@ import chalk from "chalk" import { formatForTerminal, renderMsg, getLeftOffset } from "../renderers" import { LogEntry } from "../log-entry" -import { Logger } from "../logger" -import { LogLevel } from "../log-node" +import { Logger, LogLevel } from "../logger" import { getChildEntries, getTerminalWidth, interceptStream } from "../util" import { Writer } from "./base" diff --git a/core/src/logger/writers/file-writer.ts b/core/src/logger/writers/file-writer.ts index 255f85de3f..5732247eaf 100644 --- a/core/src/logger/writers/file-writer.ts +++ b/core/src/logger/writers/file-writer.ts @@ -11,7 +11,7 @@ import { dirname, isAbsolute } from "path" import { ensureDir, truncate } from "fs-extra" import stripAnsi from "strip-ansi" -import { LogLevel } from "../log-node" +import { LogLevel } from "../logger" import { LogEntry } from "../log-entry" import { Writer } from "./base" import { renderError, renderMsg } from "../renderers" diff --git a/core/src/logger/writers/fullscreen-terminal-writer.ts b/core/src/logger/writers/fullscreen-terminal-writer.ts index 4cadfd573e..92b89bcfb8 100644 --- a/core/src/logger/writers/fullscreen-terminal-writer.ts +++ b/core/src/logger/writers/fullscreen-terminal-writer.ts @@ -15,7 +15,7 @@ import blessed from "neo-blessed" import { formatForTerminal, leftPad, renderMsg } from "../renderers" import { LogEntry } from "../log-entry" import { Logger } from "../logger" -import { LogLevel } from "../log-node" +import { LogLevel } from "../logger" import { getChildEntries, getPrecedingEntry } from "../util" import { Writer } from "./base" import { shutdown } from "../../util/util" diff --git a/core/src/plugins/container/build.ts b/core/src/plugins/container/build.ts index 6b09911cd0..7dcf0abdf1 100644 --- a/core/src/plugins/container/build.ts +++ b/core/src/plugins/container/build.ts @@ -11,7 +11,7 @@ import { ContainerModule } from "./config" import { ConfigurationError } from "../../exceptions" import { GetBuildStatusParams } from "../../types/plugin/module/getBuildStatus" import { BuildModuleParams } from "../../types/plugin/module/build" -import { LogLevel } from "../../logger/log-node" +import { LogLevel } from "../../logger/logger" import { createOutputStream } from "../../util/util" import { PrimitiveMap } from "../../config/common" diff --git a/core/src/plugins/kubernetes/container/build/buildkit.ts b/core/src/plugins/kubernetes/container/build/buildkit.ts index 174c287ad1..96eb79dc91 100644 --- a/core/src/plugins/kubernetes/container/build/buildkit.ts +++ b/core/src/plugins/kubernetes/container/build/buildkit.ts @@ -30,7 +30,7 @@ import { } from "./common" import { getNamespaceStatus } from "../../namespace" import { containerHelpers } from "../../../container/helpers" -import { LogLevel } from "../../../../logger/log-node" +import { LogLevel } from "../../../../logger/logger" import { renderOutputStream, sleep } from "../../../../util/util" import { ContainerModule } from "../../../container/config" import { getDockerBuildArgs } from "../../../container/build" diff --git a/core/src/plugins/kubernetes/container/build/cluster-docker.ts b/core/src/plugins/kubernetes/container/build/cluster-docker.ts index 2b05c77716..306d7ffc72 100644 --- a/core/src/plugins/kubernetes/container/build/cluster-docker.ts +++ b/core/src/plugins/kubernetes/container/build/cluster-docker.ts @@ -24,7 +24,7 @@ import { } from "./common" import { posix } from "path" import split2 = require("split2") -import { LogLevel } from "../../../../logger/log-node" +import { LogLevel } from "../../../../logger/logger" import { renderOutputStream } from "../../../../util/util" import { getDockerBuildFlags } from "../../../container/build" import { containerHelpers } from "../../../container/helpers" diff --git a/core/src/plugins/kubernetes/container/build/kaniko.ts b/core/src/plugins/kubernetes/container/build/kaniko.ts index 61b51fa4b7..3ffc716912 100644 --- a/core/src/plugins/kubernetes/container/build/kaniko.ts +++ b/core/src/plugins/kubernetes/container/build/kaniko.ts @@ -43,7 +43,7 @@ import { import { cloneDeep, differenceBy, isEmpty } from "lodash" import chalk from "chalk" import split2 from "split2" -import { LogLevel } from "../../../../logger/log-node" +import { LogLevel } from "../../../../logger/logger" import { renderOutputStream, sleep } from "../../../../util/util" import { getDockerBuildFlags } from "../../../container/build" import { containerHelpers } from "../../../container/helpers" diff --git a/core/src/plugins/kubernetes/logs.ts b/core/src/plugins/kubernetes/logs.ts index 9ee62a49c6..6bea7ee870 100644 --- a/core/src/plugins/kubernetes/logs.ts +++ b/core/src/plugins/kubernetes/logs.ts @@ -25,7 +25,7 @@ import { getPodLogs } from "./status/pod" import { splitFirst } from "../../util/util" import { Writable } from "stream" import request from "request" -import { LogLevel } from "../../logger/log-node" +import { LogLevel } from "../../logger/logger" // When not following logs, the entire log is read into memory and sorted. // We therefore set a maximum on the number of lines we fetch. diff --git a/core/src/plugins/kubernetes/system.ts b/core/src/plugins/kubernetes/system.ts index 040122c93b..2978526195 100644 --- a/core/src/plugins/kubernetes/system.ts +++ b/core/src/plugins/kubernetes/system.ts @@ -17,7 +17,7 @@ import { PluginError } from "../../exceptions" import { DeepPrimitiveMap } from "../../config/common" import { combineStates } from "../../types/service" import { defaultDotIgnoreFiles } from "../../util/fs" -import { LogLevel } from "../../logger/log-node" +import { LogLevel } from "../../logger/logger" import { defaultNamespace } from "../../config/project" const systemProjectPath = join(STATIC_DIR, "kubernetes", "system") diff --git a/core/src/process.ts b/core/src/process.ts index ab50fb6e89..78ab6dd975 100644 --- a/core/src/process.ts +++ b/core/src/process.ts @@ -115,13 +115,13 @@ export async function processModules({ garden.events.on("_restart", () => { log.debug({ symbol: "info", msg: `Manual restart triggered` }) - resolve() + resolve({}) }) garden.events.on("_exit", () => { log.debug({ symbol: "info", msg: `Manual exit triggered` }) restartRequired = false - resolve() + resolve({}) }) garden.events.on("projectConfigChanged", async () => { @@ -130,7 +130,7 @@ export async function processModules({ symbol: "info", msg: `Project configuration changed, reloading...`, }) - resolve() + resolve({}) } }) @@ -140,7 +140,7 @@ export async function processModules({ symbol: "info", msg: `Garden config added at ${event.path}, reloading...`, }) - resolve() + resolve({}) } }) @@ -150,7 +150,7 @@ export async function processModules({ symbol: "info", msg: `Garden config at ${event.path} removed, reloading...`, }) - resolve() + resolve({}) } }) @@ -163,7 +163,7 @@ export async function processModules({ section, msg: `Module configuration changed, reloading...`, }) - resolve() + resolve({}) } }) diff --git a/core/src/server/commands.ts b/core/src/server/commands.ts index f9cfe9a1ae..6cb37cfe65 100644 --- a/core/src/server/commands.ts +++ b/core/src/server/commands.ts @@ -13,7 +13,7 @@ import { joi } from "../config/common" import { validateSchema } from "../config/validation" import { extend, mapValues, omitBy } from "lodash" import { Garden } from "../garden" -import { LogLevel } from "../logger/log-node" +import { LogLevel } from "../logger/logger" import { LogEntry } from "../logger/log-entry" import { Parameters, ParameterValues, globalOptions } from "../cli/params" diff --git a/core/src/server/server.ts b/core/src/server/server.ts index 0bc9457064..c0de8ca08a 100644 --- a/core/src/server/server.ts +++ b/core/src/server/server.ts @@ -31,7 +31,7 @@ import { joi } from "../config/common" import { randomString } from "../util/string" import { authTokenHeader } from "../enterprise/api" import { ApiEventBatch } from "../enterprise/buffered-event-stream" -import { LogLevel } from "../logger/log-node" +import { LogLevel } from "../logger/logger" // Note: This is different from the `garden dashboard` default port. // We may no longer embed servers in watch processes from 0.13 onwards. diff --git a/core/src/types/plugin/service/getServiceLogs.ts b/core/src/types/plugin/service/getServiceLogs.ts index 1af73c003a..16930d2ed9 100644 --- a/core/src/types/plugin/service/getServiceLogs.ts +++ b/core/src/types/plugin/service/getServiceLogs.ts @@ -12,7 +12,7 @@ import { dedent } from "../../../util/string" import { GardenModule } from "../../module" import { runtimeContextSchema } from "../../../runtime-context" import { joi } from "../../../config/common" -import { LogLevel } from "../../../logger/log-node" +import { LogLevel } from "../../../logger/logger" import { string } from "@hapi/joi" export interface GetServiceLogsParams diff --git a/core/src/util/sync.ts b/core/src/util/sync.ts index 8b5cd1acff..5f00b0c396 100644 --- a/core/src/util/sync.ts +++ b/core/src/util/sync.ts @@ -8,7 +8,7 @@ import { exec } from "./util" import { LogEntry } from "../logger/log-entry" -import { LogLevel } from "../logger/log-node" +import { LogLevel } from "../logger/logger" /** * Syncs `sourcePath` with `destinationPath` using `syncOpts`. Adds options to `syncOpts` as appropriate for the diff --git a/core/test/helpers.ts b/core/test/helpers.ts index efb584e88b..36f7fec9f0 100644 --- a/core/test/helpers.ts +++ b/core/test/helpers.ts @@ -44,8 +44,7 @@ import { SuiteFunction, TestFunction } from "mocha" import { GardenError } from "../src/exceptions" import { AnalyticsGlobalConfig } from "../src/config-store" import { TestGarden, EventLogEntry, TestGardenOpts } from "../src/util/testing" -import { Logger } from "../src/logger/logger" -import { LogLevel } from "../src/logger/log-node" +import { Logger, LogLevel } from "../src/logger/logger" import { ExecInServiceParams, ExecInServiceResult } from "../src/types/plugin/service/execInService" import { ClientAuthToken } from "../src/db/entities/client-auth-token" @@ -639,6 +638,8 @@ export function initTestLogger() { try { Logger.initialize({ level: LogLevel.info, + storeEntries: true, + type: "quiet", }) } catch (_) {} } diff --git a/core/test/unit/src/cli/cli.ts b/core/test/unit/src/cli/cli.ts index 4abf22029b..30b59e2f13 100644 --- a/core/test/unit/src/cli/cli.ts +++ b/core/test/unit/src/cli/cli.ts @@ -28,13 +28,14 @@ import { UtilCommand } from "../../../../src/commands/util/util" import { StringParameter } from "../../../../src/cli/params" import stripAnsi from "strip-ansi" import { ToolsCommand } from "../../../../src/commands/tools" -import { envSupportsEmoji, Logger, getLogger } from "../../../../src/logger/logger" +import { Logger, getLogger } from "../../../../src/logger/logger" import { safeLoad } from "js-yaml" import { GardenProcess } from "../../../../src/db/entities/garden-process" import { ensureConnected } from "../../../../src/db/connection" import { startServer, GardenServer } from "../../../../src/server/server" import { FancyTerminalWriter } from "../../../../src/logger/writers/fancy-terminal-writer" import { BasicTerminalWriter } from "../../../../src/logger/writers/basic-terminal-writer" +import { envSupportsEmoji } from "../../../../src/logger/util" describe("cli", () => { before(async () => { @@ -142,7 +143,7 @@ describe("cli", () => { await cli.run({ args: ["test-command"], exitOnError: false }) const logger = getLogger() - expect(logger.writers[0]).to.be.instanceOf(FancyTerminalWriter) + expect(logger.getWriters()[0]).to.be.instanceOf(FancyTerminalWriter) }) it("uses the basic logger if log level > info", async () => { class TestCommand extends Command { @@ -166,7 +167,7 @@ describe("cli", () => { }) const logger = getLogger() - expect(logger.writers[0]).to.be.instanceOf(BasicTerminalWriter) + expect(logger.getWriters()[0]).to.be.instanceOf(BasicTerminalWriter) }) it("uses the basic logger if --show-timestamps flag is set to true", async () => { class TestCommand extends Command { @@ -187,7 +188,7 @@ describe("cli", () => { await cli.run({ args: ["--logger-type=fancy", "--show-timestamps", "test-command"], exitOnError: false }) const logger = getLogger() - expect(logger.writers[0]).to.be.instanceOf(BasicTerminalWriter) + expect(logger.getWriters()[0]).to.be.instanceOf(BasicTerminalWriter) }) }) diff --git a/core/test/unit/src/cli/helpers.ts b/core/test/unit/src/cli/helpers.ts index 4ee89854eb..c7d913596f 100644 --- a/core/test/unit/src/cli/helpers.ts +++ b/core/test/unit/src/cli/helpers.ts @@ -24,13 +24,13 @@ import { RunTaskCommand } from "../../../../src/commands/run/task" import { RunTestCommand } from "../../../../src/commands/run/test" import { PublishCommand } from "../../../../src/commands/publish" import { BuildCommand } from "../../../../src/commands/build" -import { getLogLevelChoices, parseLogLevel } from "../../../../src/logger/logger" import stripAnsi from "strip-ansi" import { Command } from "../../../../src/commands/base" import { dedent } from "../../../../src/util/string" import { LogsCommand } from "../../../../src/commands/logs" import { getAllCommands } from "../../../../src/commands/commands" import { DeepPrimitiveMap } from "../../../../src/config/common" +import { getLogLevelChoices, parseLogLevel } from "../../../../src/logger/logger" const validLogLevels = ["error", "warn", "info", "verbose", "debug", "silly", "0", "1", "2", "3", "4", "5"] diff --git a/core/test/unit/src/commands/get/get-status.ts b/core/test/unit/src/commands/get/get-status.ts index fbad10b1a5..7d17d63bde 100644 --- a/core/test/unit/src/commands/get/get-status.ts +++ b/core/test/unit/src/commands/get/get-status.ts @@ -19,7 +19,7 @@ import { TestGarden, getLogMessages } from "../../../../helpers" import { GetStatusCommand } from "../../../../../src/commands/get/get-status" import { withDefaultGlobalOpts } from "../../../../helpers" import { expect } from "chai" -import { LogLevel } from "../../../../../src/logger/log-node" +import { LogLevel } from "../../../../../src/logger/logger" describe("GetStatusCommand", () => { let tmpDir: tmp.DirectoryResult diff --git a/core/test/unit/src/commands/login.ts b/core/test/unit/src/commands/login.ts index 8bfe175e0d..32f0ca1552 100644 --- a/core/test/unit/src/commands/login.ts +++ b/core/test/unit/src/commands/login.ts @@ -17,7 +17,7 @@ import { makeDummyGarden } from "../../../../src/cli/cli" import { ClientAuthToken } from "../../../../src/db/entities/client-auth-token" import { dedent, randomString } from "../../../../src/util/string" import { EnterpriseApi } from "../../../../src/enterprise/api" -import { LogLevel } from "../../../../src/logger/log-node" +import { LogLevel } from "../../../../src/logger/logger" import { gardenEnv } from "../../../../src/constants" import { EnterpriseApiError } from "../../../../src/exceptions" diff --git a/core/test/unit/src/commands/logout.ts b/core/test/unit/src/commands/logout.ts index 8a8ba0d68f..53091f9d47 100644 --- a/core/test/unit/src/commands/logout.ts +++ b/core/test/unit/src/commands/logout.ts @@ -13,7 +13,7 @@ import { makeDummyGarden } from "../../../../src/cli/cli" import { ClientAuthToken } from "../../../../src/db/entities/client-auth-token" import { randomString } from "../../../../src/util/string" import { EnterpriseApi } from "../../../../src/enterprise/api" -import { LogLevel } from "../../../../src/logger/log-node" +import { LogLevel } from "../../../../src/logger/logger" import { LogOutCommand } from "../../../../src/commands/logout" describe("LogoutCommand", () => { diff --git a/core/test/unit/src/commands/logs.ts b/core/test/unit/src/commands/logs.ts index 7d22bc9114..b33c7812f6 100644 --- a/core/test/unit/src/commands/logs.ts +++ b/core/test/unit/src/commands/logs.ts @@ -20,8 +20,8 @@ import execa from "execa" import { DEFAULT_API_VERSION } from "../../../../src/constants" import { formatForTerminal } from "../../../../src/logger/renderers" import chalk from "chalk" -import { LogLevel } from "../../../../src/logger/log-node" import { LogEntry } from "../../../../src/logger/log-entry" +import { LogLevel } from "../../../../src/logger/logger" function makeCommandParams({ garden, diff --git a/core/test/unit/src/commands/plugins.ts b/core/test/unit/src/commands/plugins.ts index a30c7dc90b..5185858399 100644 --- a/core/test/unit/src/commands/plugins.ts +++ b/core/test/unit/src/commands/plugins.ts @@ -12,7 +12,7 @@ import { createGardenPlugin } from "../../../../src/types/plugin/plugin" import { writeFile } from "fs-extra" import { join } from "path" import { dedent } from "../../../../src/util/string" -import { LogLevel } from "../../../../src/logger/log-node" +import { LogLevel } from "../../../../src/logger/logger" import { expect } from "chai" import chalk from "chalk" const _loggerUtil = require("../../../../src/logger/util") diff --git a/core/test/unit/src/commands/run/task.ts b/core/test/unit/src/commands/run/task.ts index e0487092fc..3b72203f27 100644 --- a/core/test/unit/src/commands/run/task.ts +++ b/core/test/unit/src/commands/run/task.ts @@ -20,7 +20,7 @@ import { projectTestFailsRoot, testPluginB, } from "../../../../helpers" -import { LogLevel } from "../../../../../src/logger/log-node" +import { LogLevel } from "../../../../../src/logger/logger" import { renderDivider } from "../../../../../src/logger/util" import { dedent } from "../../../../../src/util/string" import { runExecTask } from "../../../../../src/plugins/exec" diff --git a/core/test/unit/src/commands/run/test.ts b/core/test/unit/src/commands/run/test.ts index ae472b0b9e..6848237957 100644 --- a/core/test/unit/src/commands/run/test.ts +++ b/core/test/unit/src/commands/run/test.ts @@ -17,7 +17,7 @@ import { getLogMessages, } from "../../../../helpers" import { RunTestCommand } from "../../../../../src/commands/run/test" -import { LogLevel } from "../../../../../src/logger/log-node" +import { LogLevel } from "../../../../../src/logger/logger" import { dedent } from "../../../../../src/util/string" import { renderDivider } from "../../../../../src/logger/util" diff --git a/core/test/unit/src/commands/tools.ts b/core/test/unit/src/commands/tools.ts index cd6d75222a..a576a708b8 100644 --- a/core/test/unit/src/commands/tools.ts +++ b/core/test/unit/src/commands/tools.ts @@ -21,7 +21,7 @@ import { DEFAULT_API_VERSION } from "../../../../src/constants" import { createGardenPlugin } from "../../../../src/types/plugin/plugin" import { join } from "path" import { ToolsCommand } from "../../../../src/commands/tools" -import { LogLevel } from "../../../../src/logger/log-node" +import { LogLevel } from "../../../../src/logger/logger" import { dedent } from "../../../../src/util/string" import { LogEntry } from "../../../../src/logger/log-entry" import { makeDummyGarden } from "../../../../src/cli/cli" diff --git a/core/test/unit/src/logger/log-node.ts b/core/test/unit/src/logger/log-node.ts deleted file mode 100644 index 50a8d007f9..0000000000 --- a/core/test/unit/src/logger/log-node.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018-2021 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { expect } from "chai" -import { getLogger, Logger } from "../../../../src/logger/logger" - -const logger: Logger = getLogger() - -beforeEach(() => { - // tslint:disable-next-line: prettier - (logger["children"] as any) = [] -}) - -describe("LogNode", () => { - describe("appendNode", () => { - it("should add new child entries to the respective node", () => { - logger.error("error") - logger.warn("warn") - logger.info("info") - logger.verbose("verbose") - logger.debug("debug") - logger.silly("silly") - - const prevLength = logger.children.length - const entry = logger.children[0] - const nested = entry.info("nested") - const deepNested = nested.info("deep") - - expect(logger.children[0].children).to.have.lengthOf(1) - expect(logger.children[0].children[0]).to.eql(nested) - expect(logger.children[0].children[0].children[0]).to.eql(deepNested) - expect(logger.children).to.have.lengthOf(prevLength) - }) - }) -}) diff --git a/core/test/unit/src/logger/logger.ts b/core/test/unit/src/logger/logger.ts index 0296e5578b..1bc20aed14 100644 --- a/core/test/unit/src/logger/logger.ts +++ b/core/test/unit/src/logger/logger.ts @@ -8,19 +8,18 @@ import { expect } from "chai" -import { LogLevel } from "../../../../src/logger/log-node" -import { getLogger, Logger } from "../../../../src/logger/logger" +import { getLogger, Logger, LogLevel } from "../../../../src/logger/logger" import { LogEntryEventPayload } from "../../../../src/enterprise/buffered-event-stream" import { freezeTime } from "../../../helpers" const logger: Logger = getLogger() -beforeEach(() => { - // tslint:disable-next-line: prettier - (logger["children"] as any) = [] -}) - describe("Logger", () => { + beforeEach(() => { + // tslint:disable-next-line: prettier + (logger["children"] as any) = [] + }) + describe("events", () => { let loggerEvents: LogEntryEventPayload[] = [] let listener = (event: LogEntryEventPayload) => loggerEvents.push(event) @@ -147,7 +146,48 @@ describe("Logger", () => { }) }) }) + describe("addNode", () => { + it("should add new child entries to the respective node", () => { + logger.error("error") + logger.warn("warn") + logger.info("info") + logger.verbose("verbose") + logger.debug("debug") + logger.silly("silly") + + const prevLength = logger.children.length + const entry = logger.children[0] + const nested = entry.info("nested") + const deepNested = nested.info("deep") + + expect(logger.children[0].children).to.have.lengthOf(1) + expect(logger.children[0].children[0]).to.eql(nested) + expect(logger.children[0].children[0].children[0]).to.eql(deepNested) + expect(logger.children).to.have.lengthOf(prevLength) + }) + it("should not store entires if storeEntries=false", () => { + const loggerB = new Logger({ + level: LogLevel.info, + writers: [], + storeEntries: false, + }) + loggerB.error("error") + loggerB.warn("warn") + const entry = loggerB.info("info") + loggerB.verbose("verbose") + loggerB.debug("debug") + loggerB.silly("silly") + + const nested = entry.info("nested") + const deepNested = nested.info("deep") + + expect(logger.children).to.eql([]) + expect(entry.children).to.eql([]) + expect(nested.children).to.eql([]) + expect(deepNested.children).to.eql([]) + }) + }) describe("findById", () => { it("should return the first log entry with a matching id and undefined otherwise", () => { logger.info({ msg: "0" }) diff --git a/core/test/unit/src/logger/writers/file-writer.ts b/core/test/unit/src/logger/writers/file-writer.ts index 93af61ead0..bac8fe2781 100644 --- a/core/test/unit/src/logger/writers/file-writer.ts +++ b/core/test/unit/src/logger/writers/file-writer.ts @@ -10,8 +10,7 @@ import { expect } from "chai" import chalk from "chalk" import stripAnsi from "strip-ansi" -import { LogLevel } from "../../../../../src/logger/log-node" -import { getLogger, Logger } from "../../../../../src/logger/logger" +import { getLogger, Logger, LogLevel } from "../../../../../src/logger/logger" import { renderError } from "../../../../../src/logger/renderers" import { render } from "../../../../../src/logger/writers/file-writer" diff --git a/core/test/unit/src/logger/writers/fullscreen-terminal-writer.ts b/core/test/unit/src/logger/writers/fullscreen-terminal-writer.ts index a1e2f7a3e0..2448259ffe 100644 --- a/core/test/unit/src/logger/writers/fullscreen-terminal-writer.ts +++ b/core/test/unit/src/logger/writers/fullscreen-terminal-writer.ts @@ -9,9 +9,8 @@ import { expect } from "chai" import blessed from "neo-blessed" -import { Logger } from "../../../../../src/logger/logger" +import { Logger, LogLevel } from "../../../../../src/logger/logger" import { FullscreenTerminalWriter } from "../../../../../src/logger/writers/fullscreen-terminal-writer" -import { LogLevel } from "../../../../../src/logger/log-node" import stripAnsi from "strip-ansi" import { dedent } from "../../../../../src/util/string" import { Writable, WritableOptions } from "stream" @@ -41,7 +40,7 @@ describe("FullscreenTerminalWriter", () => { beforeEach(() => { // Setting a very long spin interval so that we can control it manually writer = new TestWriter(LogLevel.info, 99999999) - logger = new Logger({ level: LogLevel.info, writers: [writer] }) + logger = new Logger({ level: LogLevel.info, storeEntries: true, writers: [writer] }) }) function getStrippedContent() { diff --git a/core/test/unit/src/plugins/terraform/terraform.ts b/core/test/unit/src/plugins/terraform/terraform.ts index 89851fab68..802366fb71 100644 --- a/core/test/unit/src/plugins/terraform/terraform.ts +++ b/core/test/unit/src/plugins/terraform/terraform.ts @@ -16,7 +16,7 @@ import { findByName } from "../../../../../src/util/util" import { Garden } from "../../../../../src/garden" import { TaskTask } from "../../../../../src/tasks/task" import { getTerraformCommands } from "../../../../../src/plugins/terraform/commands" -import { LogLevel } from "../../../../../src/logger/log-node" +import { LogLevel } from "../../../../../src/logger/logger" import { ConfigGraph } from "../../../../../src/config-graph" import { TerraformProvider } from "../../../../../src/plugins/terraform/terraform" import { DeployTask } from "../../../../../src/tasks/deploy" diff --git a/sdk/testing.ts b/sdk/testing.ts index 96ccf0e71d..ceccdfa561 100644 --- a/sdk/testing.ts +++ b/sdk/testing.ts @@ -8,8 +8,7 @@ import { TestGarden, TestGardenOpts } from "@garden-io/core/build/src/util/testing" import { uuidv4 } from "@garden-io/core/build/src/util/util" -import { Logger } from "@garden-io/core/build/src/logger/logger" -import { LogLevel } from "@garden-io/core/build/src/logger/log-node" +import { Logger, LogLevel } from "@garden-io/core/build/src/logger/logger" export { makeTempDir } from "@garden-io/core/build/src/util/fs" @@ -18,6 +17,7 @@ export const makeTestGarden = async (projectRoot: string, opts: TestGardenOpts = try { Logger.initialize({ level: LogLevel.info, + type: "quiet", }) } catch (_) {}