From d512a4308e07b971a26b650b6a0581a8292b10d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BE=C3=B3r=20Magn=C3=BAsson?= Date: Thu, 20 May 2021 12:35:46 +0200 Subject: [PATCH] refactor(logger): do not store entries in-memory unless needed With this change, the logger only stores log entries on the logger instance when the FancyWriter is enabled or when running tests. This prevents large log data accumulating in-memory. Also refactored the logger a litle bit so that it no longer extends the log node class but rather implements it. It simplifies the relationship between the root logger and the log entries. --- cli/src/add-version-files.ts | 5 +- cli/src/generate-docs.ts | 4 +- core/src/cli/cli.ts | 30 +-- core/src/cli/params.ts | 4 +- core/src/commands/logs.ts | 3 +- core/src/commands/run/workflow.ts | 2 +- core/src/enterprise/buffered-event-stream.ts | 2 +- core/src/logger/log-entry.ts | 103 ++++++--- core/src/logger/log-node.ts | 99 --------- core/src/logger/logger.ts | 204 ++++++++++++++---- core/src/logger/util.ts | 28 ++- core/src/logger/writers/base.ts | 2 +- .../logger/writers/fancy-terminal-writer.ts | 3 +- core/src/logger/writers/file-writer.ts | 2 +- .../writers/fullscreen-terminal-writer.ts | 2 +- core/src/plugins/container/build.ts | 2 +- .../kubernetes/container/build/buildkit.ts | 2 +- .../container/build/cluster-docker.ts | 2 +- .../kubernetes/container/build/kaniko.ts | 2 +- core/src/plugins/kubernetes/logs.ts | 2 +- core/src/plugins/kubernetes/system.ts | 2 +- core/src/process.ts | 12 +- core/src/server/commands.ts | 2 +- core/src/server/server.ts | 2 +- .../types/plugin/service/getServiceLogs.ts | 2 +- core/src/util/sync.ts | 2 +- core/test/helpers.ts | 5 +- core/test/unit/src/cli/cli.ts | 9 +- core/test/unit/src/cli/helpers.ts | 2 +- core/test/unit/src/commands/get/get-status.ts | 2 +- core/test/unit/src/commands/login.ts | 2 +- core/test/unit/src/commands/logout.ts | 2 +- core/test/unit/src/commands/logs.ts | 2 +- core/test/unit/src/commands/plugins.ts | 2 +- core/test/unit/src/commands/run/task.ts | 2 +- core/test/unit/src/commands/run/test.ts | 2 +- core/test/unit/src/commands/tools.ts | 2 +- core/test/unit/src/logger/log-node.ts | 40 ---- core/test/unit/src/logger/logger.ts | 54 ++++- .../unit/src/logger/writers/file-writer.ts | 3 +- .../writers/fullscreen-terminal-writer.ts | 5 +- .../unit/src/plugins/terraform/terraform.ts | 2 +- sdk/testing.ts | 4 +- 43 files changed, 355 insertions(+), 310 deletions(-) delete mode 100644 core/src/logger/log-node.ts delete mode 100644 core/test/unit/src/logger/log-node.ts 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 (_) {}