Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: nicer logging when watching for changes / hot reloading #417

Merged
merged 1 commit into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions garden-service/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ export class GardenCli {
// entries (i.e. print new lines).
const log = logger.placeholder()

// We pass a separate placeholder to the action method, so that commands can easily have a footer
// section in their log output.
const logFooter = logger.placeholder()

const contextOpts: GardenOpts = { environmentName: env, log }
if (command.noProject) {
contextOpts.config = MOCK_CONFIG
Expand All @@ -285,6 +289,7 @@ export class GardenCli {
result = await command.action({
garden,
log,
logFooter,
args: parsedArgs,
opts: parsedOpts,
})
Expand Down
1 change: 1 addition & 0 deletions garden-service/src/commands/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export interface CommandParams<T extends Parameters = {}, U extends Parameters =
opts: ParameterValues<U>
garden: Garden
log: LogEntry
logFooter?: LogEntry
}

export abstract class Command<T extends Parameters = {}, U extends Parameters = {}> {
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class BuildCommand extends Command<BuildArguments, BuildOptions> {
}

async action(
{ args, opts, garden, log }: CommandParams<BuildArguments, BuildOptions>,
{ args, opts, garden, log, logFooter }: CommandParams<BuildArguments, BuildOptions>,
): Promise<CommandResult<TaskResults>> {
await garden.clearBuilds()

Expand All @@ -74,6 +74,7 @@ export class BuildCommand extends Command<BuildArguments, BuildOptions> {
const results = await processModules({
garden,
log,
logFooter,
modules,
watch: opts.watch,
handler: async (module) => [new BuildTask({ garden, log, module, force: opts.force })],
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class DeployCommand extends Command<Args, Opts> {
logHeader({ log, emoji: "rocket", command: "Deploy" })
}

async action({ garden, log, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<TaskResults>> {
async action({ garden, log, logFooter, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<TaskResults>> {
const services = await garden.getServices(args.services)

if (services.length === 0) {
Expand All @@ -105,6 +105,7 @@ export class DeployCommand extends Command<Args, Opts> {
const results = await processServices({
garden,
log,
logFooter,
services,
watch,
handler: async (module) => getTasksForModule({
Expand Down
6 changes: 3 additions & 3 deletions garden-service/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ export class DevCommand extends Command<Args, Opts> {
log.info(chalk.gray.italic(`\nGood ${getGreetingTime()}! Let's get your environment wired up...\n`))
}

async action({ garden, log, opts }: CommandParams<Args, Opts>): Promise<CommandResult> {
async action({ garden, log, logFooter, opts }: CommandParams<Args, Opts>): Promise<CommandResult> {
await garden.actions.prepareEnvironment({ log })

const modules = await garden.getModules()

if (modules.length === 0) {
logFooter && logFooter.setState({ msg: "" })
log.info({ msg: "No modules found in project." })
log.info({ msg: "Aborting..." })
return {}
Expand Down Expand Up @@ -120,20 +121,19 @@ export class DevCommand extends Command<Args, Opts> {
includeDependants: watch,
}))
}

}

const results = await processModules({
garden,
log,
logFooter,
modules,
watch: true,
handler: tasksForModule(false),
changeHandler: tasksForModule(true),
})

return handleTaskResults(log, "dev", results)

}
}

Expand Down
12 changes: 6 additions & 6 deletions garden-service/src/commands/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,26 @@ export async function validateHotReloadOpt(
}

export async function hotReloadAndLog(garden: Garden, log: LogEntry, module: Module) {
log.info({
const logEntry = log.info({
section: module.name,
msg: "Hot reloading",
msg: "Hot reloading...",
status: "active",
})

const serviceDependencyNames = uniq(flatten(module.services.map(s => s.config.dependencies)))
const runtimeContext = await prepareRuntimeContext(
garden, log, module, await garden.getServices(serviceDependencyNames),
garden, logEntry, module, await garden.getServices(serviceDependencyNames),
)

try {
await garden.actions.hotReload({ log, module, runtimeContext })
await garden.actions.hotReload({ log: logEntry, module, runtimeContext })
} catch (err) {
log.setError()
throw err
}

const msec = log.getDuration(5) * 1000
log.setSuccess({
const msec = logEntry.getDuration(5) * 1000
logEntry.setSuccess({
msg: chalk.green(`Done (took ${msec} ms)`),
append: true,
})
Expand Down
6 changes: 3 additions & 3 deletions garden-service/src/commands/run/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export class RunTaskCommand extends Command<Args, Opts> {

printRuntimeContext(log, runtimeContext)

garden.log.info("")
garden.log.info(chalk.white(result.output.output))
garden.log.info("")
log.info("")
log.info(chalk.white(result.output.output))
log.info("")
logHeader({ log, emoji: "heavy_check_mark", command: `Done!` })

return { result }
Expand Down
4 changes: 2 additions & 2 deletions garden-service/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class ServeCommand extends Command<Args, Opts> {
arguments = serveArgs
options = serveOpts

async action({ garden, opts }: CommandParams<Args, Opts>): Promise<CommandResult<{}>> {
await startServer(garden, opts.port)
async action({ garden, log, opts }: CommandParams<Args, Opts>): Promise<CommandResult<{}>> {
await startServer(garden, log, opts.port)

// The server doesn't block, so we need to loop indefinitely here.
while (true) {
Expand Down
3 changes: 2 additions & 1 deletion garden-service/src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class TestCommand extends Command<Args, Opts> {
})
}

async action({ garden, log, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<TaskResults>> {
async action({ garden, log, logFooter, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<TaskResults>> {
const dependencyGraph = await garden.getDependencyGraph()
let modules: Module[]
if (args.modules) {
Expand All @@ -99,6 +99,7 @@ export class TestCommand extends Command<Args, Opts> {
const results = await processModules({
garden,
log,
logFooter,
modules,
watch: opts.watch,
handler: async (module) => getTestTasks({ garden, log, module, name, force, forceBuild }),
Expand Down
4 changes: 3 additions & 1 deletion garden-service/src/commands/update-remote/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class UpdateRemoteAllCommand extends Command {
garden update-remote all # update all remote sources and modules in the project
`

async action({ garden, log }: CommandParams): Promise<CommandResult<UpdateRemoteAllResult>> {
async action({ garden, log, logFooter }: CommandParams): Promise<CommandResult<UpdateRemoteAllResult>> {
logHeader({ log, emoji: "hammer_and_wrench", command: "update-remote all" })

const sourcesCmd = new UpdateRemoteSourcesCommand()
Expand All @@ -42,12 +42,14 @@ export class UpdateRemoteAllCommand extends Command {
const { result: projectSources } = await sourcesCmd.action({
garden,
log,
logFooter,
args: { sources: undefined },
opts: {},
})
const { result: moduleSources } = await modulesCmd.action({
garden,
log,
logFooter,
args: { modules: undefined },
opts: {},
})
Expand Down
6 changes: 6 additions & 0 deletions garden-service/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ export interface Events {
},
taskComplete: TaskResult,
taskError: TaskResult,
taskGraphProcessing: {
startedAt: Date,
},
taskGraphComplete: {
completedAt: Date,
},
}

export type EventName = keyof Events
27 changes: 23 additions & 4 deletions garden-service/src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type ProcessHandler = (module: Module) => Promise<BaseTask[]>
interface ProcessParams {
garden: Garden
log: LogEntry
logFooter?: LogEntry
watch: boolean
handler: ProcessHandler
// use this if the behavior should be different on watcher changes than on initial processing
Expand All @@ -46,7 +47,7 @@ export interface ProcessResults {
}

export async function processServices(
{ garden, log, services, watch, handler, changeHandler }: ProcessServicesParams,
{ garden, log, logFooter, services, watch, handler, changeHandler }: ProcessServicesParams,
): Promise<ProcessResults> {

const modules = Array.from(new Set(services.map(s => s.module)))
Expand All @@ -55,14 +56,15 @@ export async function processServices(
modules,
garden,
log,
logFooter,
watch,
handler,
changeHandler,
})
}

export async function processModules(
{ garden, log, modules, watch, handler, changeHandler }: ProcessModulesParams,
{ garden, log, logFooter, modules, watch, handler, changeHandler }: ProcessModulesParams,
): Promise<ProcessResults> {

log.debug("Starting processModules")
Expand All @@ -85,6 +87,19 @@ export async function processModules(
await Bluebird.map(tasks, t => garden.addTask(t))
}

if (watch && !!logFooter) {
logFooter.info("")
const statusLine = logFooter.placeholder()

garden.events.on("taskGraphProcessing", () => {
statusLine.setState({ emoji: "hourglass_flowing_sand", msg: "Processing..." })
})

garden.events.on("taskGraphComplete", () => {
statusLine.setState({ emoji: "clock2", msg: chalk.gray("Waiting for code changes") })
})
}

const results = await garden.processTasks()

if (!watch) {
Expand All @@ -104,7 +119,11 @@ export async function processModules(
await watcher.watchModules(modules,
async (changedModule: Module | null, configChanged: boolean) => {
if (configChanged) {
log.debug({ msg: `Config changed, reloading.` })
if (changedModule) {
log.info({ emoji: "gear", section: changedModule.name, msg: `Module configuration changed, reloading...` })
} else {
log.info({ emoji: "gear", msg: `Project configuration changed, reloading...` })
}
resolve()
return
}
Expand All @@ -125,7 +144,7 @@ export async function processModules(

// Experimental HTTP API and dashboard server.
if (process.env.GARDEN_ENABLE_SERVER === "1") {
await startServer(garden)
await startServer(garden, log)
}

await restartPromise
Expand Down
13 changes: 10 additions & 3 deletions garden-service/src/server/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { coreCommands } from "../commands/commands"
import { mapValues, omitBy } from "lodash"
import { Garden } from "../garden"
import { LogLevel } from "../logger/log-node"
import { LogEntry } from "../logger/log-entry"

export interface CommandMap {
[key: string]: {
Expand All @@ -40,7 +41,7 @@ const baseRequestSchema = Joi.object()
* Validate and map a request body to a Command, execute its action, and return its result.
*/
export async function resolveRequest(
ctx: Koa.Context, garden: Garden, commands: CommandMap, request: any,
ctx: Koa.Context, garden: Garden, log: LogEntry, commands: CommandMap, request: any,
) {
// Perform basic validation and find command.
try {
Expand Down Expand Up @@ -70,12 +71,18 @@ export async function resolveRequest(
const cmdGarden = await Garden.factory(garden.projectRoot, garden.opts)

// We generally don't want actions to log anything in the server.
const cmdLog = garden.log.placeholder(LogLevel.silly)
const cmdLog = log.placeholder(LogLevel.silly)

const cmdArgs = mapParams(ctx, request.parameters, command.arguments)
const cmdOpts = mapParams(ctx, request.parameters, command.options)

return command.action({ garden: cmdGarden, log: cmdLog, args: cmdArgs, opts: cmdOpts })
return command.action({
garden: cmdGarden,
log: cmdLog,
logFooter: cmdLog,
args: cmdArgs,
opts: cmdOpts,
})
// TODO: validate result schema
}

Expand Down
15 changes: 8 additions & 7 deletions garden-service/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Garden } from "../garden"
import { addWebsocketEndpoint } from "./websocket"
import { prepareCommands, resolveRequest } from "./commands"
import { isPkg } from "../constants"
import { LogEntry } from "../logger/log-entry"

export const DASHBOARD_BUILD_PATH = resolve(
isPkg ? process.execPath : __dirname, "..", "..", "..", "garden-dashboard", "build",
Expand All @@ -32,8 +33,10 @@ export const DASHBOARD_BUILD_PATH = resolve(
* If `port` is not specified, a random free port is chosen. This is done so that a process can always create its
* own server, but we won't need that functionality once we run a shared service across commands.
*/
export async function startServer(garden: Garden, port?: number) {
const app = await createApp(garden)
export async function startServer(garden: Garden, log: LogEntry, port?: number) {
log = log.placeholder()

const app = await createApp(garden, log)

// TODO: remove this once we stop running a server per CLI command
if (!port) {
Expand All @@ -45,17 +48,15 @@ export async function startServer(garden: Garden, port?: number) {

const url = `http://localhost:${port}`

garden.log.info({
log.info({
emoji: "sunflower",
msg: chalk.cyan("Garden dashboard and API server running on ") + url,
})

return server
}

export async function createApp(garden: Garden) {
const log = garden.log.placeholder()

export async function createApp(garden: Garden, log: LogEntry) {
// prepare request-command map
const commands = await prepareCommands()

Expand All @@ -71,7 +72,7 @@ export async function createApp(garden: Garden) {
*/
http.post("/api", async (ctx) => {
// TODO: set response code when errors are in result object?
const result = await resolveRequest(ctx, garden, commands, ctx.request.body)
const result = await resolveRequest(ctx, garden, log, commands, ctx.request.body)

ctx.status = 200
ctx.response.body = result
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/server/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function addWebsocketEndpoint(app: websockify.App, garden: Garden, log: L
}

if (request.type === "command") {
resolveRequest(ctx, garden, commands, omit(request, ["id", "type"]))
resolveRequest(ctx, garden, log, commands, omit(request, ["id", "type"]))
.then(result => {
send("commandResult", { requestId, result: result.result, errors: result.errors })
})
Expand Down
3 changes: 3 additions & 0 deletions garden-service/src/task-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,13 @@ export class TaskGraph {
const _this = this
const results: TaskResults = {}

this.garden.events.emit("taskGraphProcessing", { startedAt: new Date() })

const loop = async () => {
if (_this.index.length === 0) {
// done!
this.logEntryMap.counter && this.logEntryMap.counter.setDone({ symbol: "info" })
this.garden.events.emit("taskGraphComplete", { completedAt: new Date() })
return
}

Expand Down
Loading