Skip to content

Commit

Permalink
refactor: add placeholder method to logger + fix rendering issues
Browse files Browse the repository at this point in the history
  • Loading branch information
eysi09 committed Nov 28, 2018
1 parent 13cf263 commit fa8d81e
Show file tree
Hide file tree
Showing 39 changed files with 294 additions and 184 deletions.
9 changes: 8 additions & 1 deletion garden-service/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,14 @@ export class ActionHelper implements TypeGuard {
{ pluginName, log }: ActionHelperParams<GetEnvironmentStatusParams>,
): Promise<EnvironmentStatusMap> {
const handlers = this.garden.getActionHandlers("getEnvironmentStatus", pluginName)
return Bluebird.props(mapValues(handlers, h => h({ ...this.commonParams(h, log) })))
const logEntry = log.debug({
msg: "Getting status...",
status: "active",
section: `${this.garden.environment.name} environment`,
})
const res = await Bluebird.props(mapValues(handlers, h => h({ ...this.commonParams(h, logEntry) })))
logEntry.setSuccess("Ready")
return res
}

/**
Expand Down
119 changes: 85 additions & 34 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
GardenError,
PluginError,
toGardenError,
InternalError,
} from "../exceptions"
import { Garden, ContextOpts } from "../garden"

Expand Down Expand Up @@ -67,17 +68,14 @@ const OUTPUT_RENDERERS = {
},
}

const logLevelKeys = getEnumKeys(LogLevel)
// Allow string or numeric log levels
const logLevelChoices = [...logLevelKeys, ...range(logLevelKeys.length).map(String)]
const FILE_WRITER_CONFIGS = [
{ filename: "development.log" },
{ filename: ERROR_LOG_FILENAME, level: LogLevel.error },
{ filename: ERROR_LOG_FILENAME, level: LogLevel.error, path: ".", truncatePrevious: true },
]

const getLogLevelFromArg = (level: string) => {
const lvl = parseInt(level, 10)
if (lvl) {
return lvl
}
return LogLevel[level]
}
const GLOBAL_OPTIONS_GROUP_NAME = "Global options"
const DEFAULT_CLI_LOGGER_TYPE = LoggerType.fancy

// For initializing garden without a project config
export const MOCK_CONFIG: GardenConfig = {
Expand All @@ -99,6 +97,11 @@ 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 All @@ -113,7 +116,7 @@ export const GLOBAL_OPTIONS = {
env: new EnvironmentOption(),
loglevel: new ChoicesParameter({
alias: "l",
choices: logLevelChoices,
choices: getLogLevelChoices(),
help:
"Set logger level. Values can be either string or numeric and are prioritized from 0 to 5 " +
"(highest to lowest) as follows: error: 0, warn: 1, info: 2, verbose: 3, debug: 4, silly: 5.",
Expand All @@ -131,8 +134,39 @@ export const GLOBAL_OPTIONS = {
defaultValue: envSupportsEmoji(),
}),
}
const GLOBAL_OPTIONS_GROUP_NAME = "Global options"
const DEFAULT_CLI_LOGGER_TYPE = LoggerType.fancy

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,
}) {
let writers: Writer[] = []

if (logEnabled) {
if (loggerType === LoggerType.fancy) {
writers.push(new FancyTerminalWriter())
} else if (loggerType === LoggerType.basic) {
writers.push(new BasicTerminalWriter())
}
}

return Logger.initialize({ level, writers, useEmoji: emoji })
}

export interface ParseResults {
argv: any
Expand All @@ -146,8 +180,9 @@ interface SywacParseResults extends ParseResults {
}

export class GardenCli {
program: any
commands: { [key: string]: Command } = {}
private program: any
private commands: { [key: string]: Command } = {}
private fileWritersInitialized: boolean = false

constructor() {
const version = require("../../package.json").version
Expand All @@ -169,13 +204,28 @@ export class GardenCli {
.style(styleConfig)

const commands = coreCommands

const globalOptions = Object.entries(GLOBAL_OPTIONS)

commands.forEach(command => this.addCommand(command, this.program))
globalOptions.forEach(([key, opt]) => this.addGlobalOption(key, opt))
}

async initFileWriters(logger: Logger, projectRoot: string) {
if (this.fileWritersInitialized) {
return
}
for (const config of FILE_WRITER_CONFIGS) {
logger.writers.push(
await FileWriter.factory({
level: logger.level,
root: projectRoot,
...config,
}),
)
}
this.fileWritersInitialized = true
}

addGlobalOption(key: string, option: Parameter<any>): void {
this.program.option(getOptionSynopsis(key, option), {
...prepareOptionConfig(option),
Expand Down Expand Up @@ -211,35 +261,36 @@ export class GardenCli {

const action = async (argv, cliContext) => {
// Sywac returns positional args and options in a single object which we separate into args and opts

const parsedArgs = filterByKeys(argv, argKeys)
const parsedOpts = filterByKeys(argv, optKeys.concat(globalKeys))
const root = resolve(process.cwd(), parsedOpts.root)
const { emoji, env, loglevel, silent, output } = parsedOpts

// Init logger
const level = getLogLevelFromArg(loglevel)
let writers: Writer[] = []

if (!silent && !output && loggerType !== LoggerType.quiet) {
if (loggerType === LoggerType.fancy) {
writers.push(new FancyTerminalWriter())
} else if (loggerType === LoggerType.basic) {
writers.push(new BasicTerminalWriter())
}
const logEnabled = !silent && !output && loggerType !== LoggerType.quiet
const level = parseLogLevel(loglevel)
const logger = initLogger({ level, logEnabled, loggerType, emoji })

// Currently we initialise an empty placeholder log entry and pass that to the
// framework as opposed to the logger itself. This is mainly for type conformity.
// A log entry has the same capabilities as the logger itself (they both extend a log node)
// but can additionally be updated after it's created, whereas the logger can create only new
// entries (i.e. print new lines).
const log = logger.placeholder()

const contextOpts: ContextOpts = { env, log }
if (command.noProject) {
contextOpts.config = MOCK_CONFIG
}

const logger = Logger.initialize({ level, writers, useEmoji: emoji })
let garden: Garden
let result
do {
const contextOpts: ContextOpts = { env, logger }
if (command.noProject) {
contextOpts.config = MOCK_CONFIG
}
garden = await Garden.factory(root, contextOpts)
// Indent -1 so that the children of this entry get printed with indent 0
const log = garden.log.info({ indent: -1 })

// Register log file writers. We need to do this after the Garden class is initialised because
// the file writers depend on the project root.
await this.initFileWriters(logger, garden.projectRoot)

// TODO: enforce that commands always output DeepPrimitiveMap
result = await command.action({
garden,
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/create/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export class CreateModuleCommand extends Command<Args, Opts> {
} else {
// Prompt for type
log.info("---------")
log.stop()
// Stop logger while prompting
log.stopAll()
type = (await prompts.addConfigForModule(moduleName)).type
log.info("---------")
if (!type) {
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/create/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class CreateProjectCommand extends Command<Args, Opts> {
log.info(`Initializing new Garden project ${projectName}`)
log.info("---------")
// Stop logger while prompting
log.stop()
log.stopAll()

if (moduleParentDirs.length > 0) {
// If module-dirs option provided we scan for modules in the parent dir(s) and add them one by one
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class DevCommand extends Command<Args, Opts> {
async action({ garden, log, opts }: CommandParams<Args, Opts>): Promise<CommandResult> {
// print ANSI banner image
const data = await readFile(ansiBannerPath)
console.log(data.toString())
log.info(data.toString())

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

Expand Down
40 changes: 9 additions & 31 deletions garden-service/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ import {
import {
DEFAULT_NAMESPACE,
MODULE_CONFIG_FILENAME,
ERROR_LOG_FILENAME,
} from "./constants"
import {
ConfigurationError,
Expand All @@ -75,7 +74,6 @@ import {
} from "./task-graph"
import {
getLogger,
Logger,
} from "./logger/logger"
import {
pluginActionNames,
Expand Down Expand Up @@ -103,13 +101,12 @@ import {
} from "./util/ext-source-util"
import { BuildDependencyConfig, ModuleConfig } from "./config/module"
import { ProjectConfigContext, ModuleConfigContext } from "./config/config-context"
import { FileWriter } from "./logger/writers/file-writer"
import { LogLevel } from "./logger/log-node"
import { ActionHelper } from "./actions"
import { createPluginContext } from "./plugin-context"
import { ModuleAndRuntimeActions, Plugins, RegisterPluginParam } from "./types/plugin/plugin"
import { SUPPORTED_PLATFORMS, SupportedPlatform } from "./constants"
import { platform, arch } from "os"
import { LogEntry } from "./logger/log-entry"

export interface ActionHandlerMap<T extends keyof PluginActions> {
[actionName: string]: PluginActions[T]
Expand All @@ -136,20 +133,14 @@ export type ModuleActionMap = {
export interface ContextOpts {
config?: GardenConfig,
env?: string,
logger?: Logger,
log?: LogEntry,
plugins?: Plugins,
}

const scanLock = new AsyncLock()

const fileWriterConfigs = [
{ filename: "development.log" },
{ filename: ERROR_LOG_FILENAME, level: LogLevel.error },
{ filename: ERROR_LOG_FILENAME, level: LogLevel.error, path: ".", truncatePrevious: true },
]

export class Garden {
public readonly log: Logger
public readonly log: LogEntry
public readonly actionHandlers: PluginActionMap
public readonly moduleActionHandlers: ModuleActionMap
public dependencyGraph: DependencyGraph
Expand All @@ -174,7 +165,7 @@ export class Garden {
public readonly environment: Environment,
public readonly projectSources: SourceConfig[] = [],
public readonly buildDir: BuildDir,
logger?: Logger,
log?: LogEntry,
) {
// make sure we're on a supported platform
const currentPlatform = platform()
Expand All @@ -189,7 +180,7 @@ export class Garden {
}

this.modulesScanned = false
this.log = logger || getLogger()
this.log = log || getLogger().placeholder()
// TODO: Support other VCS options.
this.vcs = new GitHandler(this.projectRoot)
this.localConfigStore = new LocalConfigStore(this.projectRoot)
Expand All @@ -203,12 +194,12 @@ export class Garden {
this.actionHandlers = <PluginActionMap>fromPairs(pluginActionNames.map(n => [n, {}]))
this.moduleActionHandlers = <ModuleActionMap>fromPairs(moduleActionNames.map(n => [n, {}]))

this.taskGraph = new TaskGraph(this.log.info({ indent: -1 }))
this.taskGraph = new TaskGraph(this.log)
this.actions = new ActionHelper(this)
this.hotReloadScheduler = new HotReloadScheduler()
}

static async factory(currentDirectory: string, { env, config, logger, plugins = {} }: ContextOpts = {}) {
static async factory(currentDirectory: string, { env, config, log, plugins = {} }: ContextOpts = {}) {
let parsedConfig: GardenConfig

if (config) {
Expand Down Expand Up @@ -293,26 +284,13 @@ export class Garden {

const buildDir = await BuildDir.factory(projectRoot)

// Register log writers
if (logger) {
for (const writerConfig of fileWriterConfigs) {
logger.writers.push(
await FileWriter.factory({
level: logger.level,
root: projectRoot,
...writerConfig,
}),
)
}
}

const garden = new Garden(
projectRoot,
projectName,
environment,
projectSources,
buildDir,
logger,
log,
)

// Register plugins
Expand Down Expand Up @@ -822,7 +800,7 @@ export class Garden {
await this.detectCircularDependencies()

const moduleConfigContext = new ModuleConfigContext(
this, this.log.info({ indent: -1 }), this.environment, Object.values(this.moduleConfigs),
this, this.log, this.environment, Object.values(this.moduleConfigs),
)
this.moduleConfigs = await resolveTemplateStrings(this.moduleConfigs, moduleConfigContext)
})
Expand Down
Loading

0 comments on commit fa8d81e

Please sign in to comment.