diff --git a/cmd/topf/render.go b/cmd/topf/render.go index 8b824b3..4140900 100644 --- a/cmd/topf/render.go +++ b/cmd/topf/render.go @@ -43,12 +43,14 @@ func newRenderCmd() *cli.Command { return err } - return writeMachineConfigs(nodes, c.String("output")) + return writeMachineConfigs(nodes, c.String("output"), t) }, } } -func writeMachineConfigs(nodes []*topf.Node, outputDir string) error { +func writeMachineConfigs(nodes []*topf.Node, outputDir string, t topf.Topf) error { + logger := t.Logger().With("command", "render") + if err := os.MkdirAll(outputDir, 0o750); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } @@ -80,7 +82,7 @@ func writeMachineConfigs(nodes []*topf.Node, outputDir string) error { return fmt.Errorf("failed to write config for %s: %w", node.Node.Host, err) } - fmt.Fprintf(os.Stdout, "Wrote machine config for %s to %s\n", node.Node.Host, outputPath) + logger.Info("Wrote machine config", "node", node.Node.Host, "path", outputPath) } return errors.Join(errs...) diff --git a/cmd/topf/root.go b/cmd/topf/root.go index ad85fe2..35a87ea 100644 --- a/cmd/topf/root.go +++ b/cmd/topf/root.go @@ -53,6 +53,12 @@ func main() { Usage: "set the logging level (debug, info, warn, error)", Sources: cli.EnvVars("LOG_LEVEL"), }, + &cli.BoolFlag{ + Name: "json-log", + Value: false, + Usage: "emit json logs instead of human readable text", + Sources: cli.EnvVars("TOPF_JSON_LOG"), + }, &cli.BoolFlag{ Name: "redact", Value: true, @@ -80,6 +86,7 @@ func main() { ConfigPath: c.String("topfconfig"), NodesRegexFilter: c.String("nodes-filter"), LogLevel: c.String("log-level"), + JsonLog: c.Bool("json-log"), Redact: c.Bool("redact"), Confirm: c.Bool("confirm"), SubmitToFactory: c.Bool("submit-to-factory"), @@ -89,6 +96,12 @@ func main() { return ctx, err } + // Set the logger configured by the runtime as the default. + // + // While commands should still use the one from the runtime, + // we need to have it available in the app error handler below. + slog.SetDefault(topf.Logger()) + return context.WithValue(ctx, topfRuntimeCtxKey, topf), nil }, Commands: []*cli.Command{ @@ -106,7 +119,7 @@ func main() { } if err := app.Run(context.Background(), os.Args); err != nil { - slog.Error("error", "error", err) + slog.Error("error", "failed to run command", err) os.Exit(1) } } diff --git a/internal/topf/topf.go b/internal/topf/topf.go index e5fd1a1..c6f40a4 100644 --- a/internal/topf/topf.go +++ b/internal/topf/topf.go @@ -84,6 +84,9 @@ type RuntimeConfig struct { // LogLevel sets the logging verbosity (debug, info, warn, error) LogLevel string + // Emit logs as JSON instead of human readable text + JsonLog bool + // Redact controls whether sensitive values are masked in output Redact bool @@ -138,7 +141,14 @@ func NewTopfRuntime(cfg RuntimeConfig) (Topf, error) { opts := &slog.HandlerOptions{ Level: level, } - handler := slog.NewTextHandler(os.Stderr, opts) + + var handler slog.Handler + if cfg.JsonLog { + handler = slog.NewJSONHandler(os.Stderr, opts) + } else { + handler = slog.NewTextHandler(os.Stderr, opts) + } + logger := slog.New(handler) var mw *maskedwriter.Writer