Skip to content

Commit

Permalink
feat(logger): allow controlling level with env var (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
eysi09 committed Jan 29, 2019
1 parent 99fdb12 commit ec8bd45
Show file tree
Hide file tree
Showing 14 changed files with 506 additions and 397 deletions.
32 changes: 6 additions & 26 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
*/

import * as sywac from "sywac"
import { intersection, merge, range } from "lodash"
import { intersection, merge } from "lodash"
import { resolve } from "path"
import { safeDump } from "js-yaml"
import { coreCommands } from "../commands/commands"
import { DeepPrimitiveMap } from "../config/common"
import { getEnumKeys, shutdown, sleep } from "../util/util"
import { shutdown, sleep } from "../util/util"
import {
BooleanParameter,
ChoicesParameter,
Expand All @@ -22,7 +22,7 @@ import {
Parameter,
StringParameter,
} from "../commands/base"
import { GardenError, InternalError, PluginError, toGardenError } from "../exceptions"
import { GardenError, PluginError, toGardenError } from "../exceptions"
import { Garden, GardenOpts } from "../garden"
import { getLogger, Logger, LoggerType } from "../logger/logger"
import { LogLevel } from "../logger/log-node"
Expand All @@ -43,6 +43,8 @@ import {
prepareOptionConfig,
styleConfig,
getPackageVersion,
getLogLevelChoices,
parseLogLevel,
} from "./helpers"
import { GardenConfig } from "../config/base"
import { defaultEnvironments } from "../config/project"
Expand Down Expand Up @@ -87,11 +89,6 @@ export const MOCK_CONFIG: GardenConfig = {
},
}

const getLogLevelNames = () => getEnumKeys(LogLevel)
const getNumericLogLevels = () => range(getLogLevelNames().length)
// Allow string or numeric log levels as CLI choices
const getLogLevelChoices = () => [...getLogLevelNames(), ...getNumericLogLevels().map(String)]

export const GLOBAL_OPTIONS = {
root: new StringParameter({
alias: "r",
Expand Down Expand Up @@ -125,23 +122,6 @@ export const GLOBAL_OPTIONS = {
}),
}

function parseLogLevel(level: string): LogLevel {
let lvl: LogLevel
const parsed = parseInt(level, 10)
if (parsed) {
lvl = parsed
} else {
lvl = LogLevel[level]
}
if (!getNumericLogLevels().includes(lvl)) {
throw new InternalError(
`Unexpected log level, expected one of ${getLogLevelChoices().join(", ")}, got ${level}`,
{},
)
}
return lvl
}

function initLogger({ level, logEnabled, loggerType, emoji }: {
level: LogLevel, logEnabled: boolean, loggerType: LoggerType, emoji: boolean,
}) {
Expand Down Expand Up @@ -402,7 +382,7 @@ export class GardenCli {
}

export async function run(): Promise<void> {
let code
let code: number | undefined
try {
const cli = new GardenCli()
const result = await cli.parse()
Expand Down
28 changes: 27 additions & 1 deletion garden-service/src/cli/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import chalk from "chalk"
import { difference, flatten, reduce } from "lodash"
import { difference, flatten, range, reduce } from "lodash"
import {
ChoicesParameter,
ParameterValues,
Expand All @@ -16,6 +16,8 @@ import {
import {
InternalError,
} from "../exceptions"
import { LogLevel } from "../logger/log-node"
import { getEnumKeys } from "../util/util"

// Parameter types T which map between the Parameter<T> class and the Sywac cli library.
// In case we add types that aren't supported natively by Sywac, see: http://sywac.io/docs/sync-config.html#custom
Expand Down Expand Up @@ -108,6 +110,30 @@ export function getArgSynopsis(key: string, param: Parameter<any>) {
return param.required ? `<${key}>` : `[${key}]`
}

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)
// Level is numeric
if (parsed || parsed === 0) {
lvl = parsed
// Level is a string
} else {
lvl = LogLevel[level]
}
if (!getNumericLogLevels().includes(lvl)) {
throw new InternalError(
`Unexpected log level, expected one of ${getLogLevelChoices().join(", ")}, got ${level}`,
{},
)
}
return lvl
}

export function prepareArgConfig(param: Parameter<any>) {
return {
desc: param.help,
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/logger/log-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class LogEntry extends LogNode {
this.root.onGraphChange(node)
}

placeholder(level: LogLevel = LogLevel.info, param? : CreateParam): LogEntry {
placeholder(level: LogLevel = LogLevel.info, param?: CreateParam): LogEntry {
// Ensure placeholder child entries align with parent context
const indent = Math.max((this.opts.indent || 0) - 1, - 1)
return this.appendNode(level, { ...resolveParam(param), indent })
Expand Down
14 changes: 13 additions & 1 deletion garden-service/src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { InternalError, ParameterError } from "../exceptions"
import { LogLevel } from "./log-node"
import { FancyTerminalWriter } from "./writers/fancy-terminal-writer"
import { BasicTerminalWriter } from "./writers/basic-terminal-writer"
import { parseLogLevel } from "../cli/helpers"

export enum LoggerType {
quiet = "quiet",
Expand Down Expand Up @@ -64,7 +65,18 @@ export class Logger extends LogNode {

let instance: Logger

// If GARDEN_LOGGER_TYPE env variable is set it takes precedence over the config param
// GARDEN_LOG_LEVEL env variable takes precedence over the config param
if (process.env.GARDEN_LOG_LEVEL) {
try {
config.level = parseLogLevel(process.env.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)
}
}

// GARDEN_LOGGER_TYPE env variable takes precedence over the config param
if (process.env.GARDEN_LOGGER_TYPE) {
const loggerType = LoggerType[process.env.GARDEN_LOGGER_TYPE]

Expand Down
113 changes: 113 additions & 0 deletions garden-service/test/logger/log-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { expect } from "chai"

import { getLogger } from "../../src/logger/logger"

const logger = getLogger()

beforeEach(() => {
(<any>logger).children = []
})

describe("LogEntry", () => {
it("should dedent placeholder log entries", () => {
const ph1 = logger.placeholder()
const ph2 = ph1.placeholder()
const nonEmpty = ph1.info("foo")
const nested = nonEmpty.info("foo")
const nestedPh = nested.placeholder()
const indents = [
ph1.opts.indent,
ph2.opts.indent,
nonEmpty.opts.indent,
nested.opts.indent,
nestedPh.opts.indent,
]
expect(indents).to.eql([-1, -1, 0, 1, 0])
})
it("should indent nested log entries", () => {
const entry = logger.info("hello")
const nested = entry.info("nested")
const deepNested = nested.info("deep nested")
const deepDeepNested = deepNested.info("deep deep inside")
const deepDeepPh = deepDeepNested.placeholder()
const deepDeepNested2 = deepDeepPh.info("")
const indents = [
entry.opts.indent,
nested.opts.indent,
deepNested.opts.indent,
deepDeepNested.opts.indent,
deepDeepPh.opts.indent,
deepDeepNested2.opts.indent,
]
expect(indents).to.eql([undefined, 1, 2, 3, 2, 3])
})
context("preserveLevel is set to true", () => {
it("should create a log entry whose children inherit the parent level", () => {
const verbose = logger.verbose({ preserveLevel: true })
const error = verbose.error("")
const silly = verbose.silly("")
const deepError = error.error("")
const deepSillyError = silly.error("")
const deepSillySilly = silly.silly("")
const levels = [
verbose.warn("").level,
verbose.info("").level,
verbose.verbose("").level,
verbose.debug("").level,
verbose.silly("").level,
deepError.level,
deepSillyError.level,
deepSillySilly.level,
]
expect(levels).to.eql([3, 3, 3, 4, 5, 3, 3, 5])
})
})
describe("setState", () => {
it("should update entry state and optionally append new msg to previous msg", () => {
const entry = logger.info("")
entry.setState("new")
expect(entry.opts.msg).to.equal("new")
entry.setState({ msg: "new2", append: true })
expect(entry.opts.msg).to.eql(["new", "new2"])
})
})
describe("setState", () => {
it("should preserve status", () => {
const entry = logger.info("")
entry.setSuccess()
entry.setState("change text")
expect(entry.opts.status).to.equal("success")
})
})
describe("setDone", () => {
it("should update entry state and set status to done", () => {
const entry = logger.info("")
entry.setDone()
expect(entry.opts.status).to.equal("done")
})
})
describe("setSuccess", () => {
it("should update entry state and set status and symbol to success", () => {
const entry = logger.info("")
entry.setSuccess()
expect(entry.opts.status).to.equal("success")
expect(entry.opts.symbol).to.equal("success")
})
})
describe("setError", () => {
it("should update entry state and set status and symbol to error", () => {
const entry = logger.info("")
entry.setError()
expect(entry.opts.status).to.equal("error")
expect(entry.opts.symbol).to.equal("error")
})
})
describe("setWarn", () => {
it("should update entry state and set status and symbol to warn", () => {
const entry = logger.info("")
entry.setWarn()
expect(entry.opts.status).to.equal("warn")
expect(entry.opts.symbol).to.equal("warning")
})
})
})
31 changes: 31 additions & 0 deletions garden-service/test/logger/log-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from "chai"
import { getLogger } from "../../src/logger/logger"

const logger = getLogger()

beforeEach(() => {
(<any>logger).children = []
})

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)
})
})
})
60 changes: 60 additions & 0 deletions garden-service/test/logger/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect } from "chai"

import { LogLevel } from "../../src/logger/log-node"
import { getLogger } from "../../src/logger/logger"

const logger = getLogger()

beforeEach(() => {
(<any>logger).children = []
})

describe("Logger", () => {
describe("findById", () => {
it("should return the first log entry with a matching id and undefined otherwise", () => {
logger.info({ msg: "0" })
logger.info({ msg: "a1", id: "a" })
logger.info({ msg: "a2", id: "a" })
expect(logger.findById("a")["opts"]["msg"]).to.eql("a1")
expect(logger.findById("z")).to.be.undefined
})
})

describe("filterBySection", () => {
it("should return an array of all entries with the matching section name", () => {
logger.info({ section: "s0" })
logger.info({ section: "s1", id: "a" })
logger.info({ section: "s2" })
logger.info({ section: "s1", id: "b" })
const s1 = logger.filterBySection("s1")
const sEmpty = logger.filterBySection("s99")
expect(s1.map(entry => entry.id)).to.eql(["a", "b"])
expect(sEmpty).to.eql([])
})
})

describe("getLogEntries", () => {
it("should return an ordered list of log entries", () => {
logger.error("error")
logger.warn("warn")
logger.info("info")
logger.verbose("verbose")
logger.debug("debug")
logger.silly("silly")

const entries = logger.getLogEntries()
const levels = entries.map(e => e.level)

expect(entries).to.have.lengthOf(6)
expect(levels).to.eql([
LogLevel.error,
LogLevel.warn,
LogLevel.info,
LogLevel.verbose,
LogLevel.debug,
LogLevel.silly,
])
})
})

})
Loading

0 comments on commit ec8bd45

Please sign in to comment.