feat: adopt custom logger

This commit is contained in:
Marc 2024-08-03 13:10:01 -04:00
parent 7f97a23945
commit 1a5826149f
Signed by: marc
GPG key ID: 048E042F22B5DC79
6 changed files with 92 additions and 21 deletions

View file

@ -1,11 +1,11 @@
package commands package commands
import ( import (
logger "courgette/internal/logging"
runner "courgette/internal/runner" runner "courgette/internal/runner"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
"fmt" "fmt"
"log"
) )
func ExecuteWorkflow(configuration Configuration, workflowFile string) error { func ExecuteWorkflow(configuration Configuration, workflowFile string) error {
@ -23,19 +23,19 @@ func ExecuteWorkflow(configuration Configuration, workflowFile string) error {
workflow, err := workflow.FromYamlFile(workflowFile) workflow, err := workflow.FromYamlFile(workflowFile)
if err != nil { if err != nil {
log.Fatalf("%#v", err) logger.Error(logger.Red("Failed to read workflow (%s)"), workflowFile)
return err
} }
validationErrors := workflow.Validate() validationErrors := workflow.Validate()
if len(validationErrors) > 0 { if len(validationErrors) > 0 {
for _, err := range validationErrors { for _, err := range validationErrors {
log.Printf("Validation error:: %#v", err) logger.Error(logger.Red("Validation error: %s"), err)
} }
return errors.New("Jobs encountered errors.") return errors.New("Workflow validation failed.")
} }
taskResult := runnerInstance.RunWorkflow(*workflow) taskResult := runnerInstance.RunWorkflow(*workflow)
if !taskResult.HasError() { if !taskResult.HasError() {
@ -43,7 +43,11 @@ func ExecuteWorkflow(configuration Configuration, workflowFile string) error {
} }
for _, job := range taskResult.Context.Jobs { for _, job := range taskResult.Context.Jobs {
log.Printf("Job %s: %s", job.Id, job.Status) if job.Status == "success" {
logger.Info(logger.Green("Job %s: %s"), job.Id, job.Status)
} else if job.Status == "failed" {
logger.Error(logger.Red("Job %s: %s"), job.Id, job.Status)
}
} }
return fmt.Errorf("Task %s failed with at least 1 error.", taskResult.Id) return fmt.Errorf("Task %s failed with at least 1 error.", taskResult.Id)

View file

@ -1,27 +1,32 @@
package commands package commands
import ( import (
logger "courgette/internal/logging"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
"log"
) )
func ValidateWorkflow(configuration Configuration, workflowPath string) error { func ValidateWorkflow(configuration Configuration, workflowPath string) error {
logger.Info("Validating workflow at \"%s\".", workflowPath)
workflow, err := workflow.FromYamlFile(workflowPath) workflow, err := workflow.FromYamlFile(workflowPath)
if err != nil { if err != nil {
log.Fatalf("%#v", err) logger.Error(logger.Red("Failed to read and parse workflow from \"%s\"."), workflowPath)
return err
} }
validationErrors := workflow.Validate() validationErrors := workflow.Validate()
if len(validationErrors) > 0 { if len(validationErrors) > 0 {
for _, err := range validationErrors { for _, err := range validationErrors {
log.Printf("Validation error:: %#v", err) logger.Error(logger.Red("Validation error: %s"), err)
} }
return errors.New("Jobs encountered errors.") return errors.New("Workflow validation failed.")
} }
logger.Info(logger.Green(logger.Bold("✅ Workflow \"%s\" is valid!")), workflowPath)
return nil return nil
} }

View file

@ -0,0 +1,17 @@
// Color utilities to enhance text printed to the screen.
//
// See: https://en.wikipedia.org/wiki/ANSI_escape_code
package logging
func Bold(text string) string {
return "\033[1m" + text + "\033[0m"
}
func Green(text string) string {
return "\033[32m" + text + "\033[0m"
}
func Red(text string) string {
return "\033[31m" + text + "\033[0m"
}

View file

@ -0,0 +1,44 @@
// Logging module
//
// This module is a wrapper around the built-in `log` package
// and adds more control around if and how logging shows up
// in the code.
package logging
import (
"log"
"os"
)
type Logger struct {
Info log.Logger
Error log.Logger
}
var Log Logger
// Configures the loggers and initializes each logging level's instance.
//
// This should be run once and before any logging is done.
func ConfigureLogger() {
Log = Logger{
Info: *log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime),
Error: *log.New(os.Stderr, "[ERROR] ", log.Ldate|log.Ltime),
}
}
func Info(message string, args ...any) {
if len(args) == 0 {
Log.Info.Print(message)
} else {
Log.Info.Printf(message, args...)
}
}
func Error(message string, args ...any) {
if len(args) == 0 {
Log.Error.Print(message)
} else {
Log.Error.Printf(message, args...)
}
}

View file

@ -1,10 +1,10 @@
package runner package runner
import ( import (
logger "courgette/internal/logging"
workflow "courgette/internal/workflow" workflow "courgette/internal/workflow"
"errors" "errors"
"fmt" "fmt"
"log"
"sync" "sync"
) )
@ -35,7 +35,7 @@ func (r *Runner) DeferTask(task func()) {
// Each task is executed within a go routine and the call will // Each task is executed within a go routine and the call will
// wait until all the tasks are completed before returning. // wait until all the tasks are completed before returning.
func (r *Runner) RunDeferredTasks() { func (r *Runner) RunDeferredTasks() {
log.Printf("Running %d deferred tasks.", len(r.deferred)) logger.Info("Running %d deferred tasks.", len(r.deferred))
var tracker sync.WaitGroup var tracker sync.WaitGroup
@ -84,7 +84,7 @@ func (r *Runner) GetTask(taskId string) *Task {
// that the jobs will be executed in, run the jobs's steps and // that the jobs will be executed in, run the jobs's steps and
// tear down the container once no longer useful. // tear down the container once no longer useful.
func (r *Runner) RunWorkflow(workflow workflow.Workflow) Task { func (r *Runner) RunWorkflow(workflow workflow.Workflow) Task {
log.Printf("Executing workflow: %s", workflow.SourcePath) logger.Info("Executing workflow: %s", workflow.SourcePath)
task := r.GetTask(r.AddTask()) task := r.GetTask(r.AddTask())
for _, job := range workflow.Jobs { for _, job := range workflow.Jobs {
@ -94,7 +94,7 @@ func (r *Runner) RunWorkflow(workflow workflow.Workflow) Task {
runnerImage := r.GetImageUriByLabel(job.RunsOn) runnerImage := r.GetImageUriByLabel(job.RunsOn)
containerName := r.GetContainerName(jobContext.Id) containerName := r.GetContainerName(jobContext.Id)
log.Printf("Using image %s (label: %s)", runnerImage, job.RunsOn) logger.Info("Using image %s (label: %s)", runnerImage, job.RunsOn)
if pullError := r.Driver.Pull(runnerImage); pullError != nil { if pullError := r.Driver.Pull(runnerImage); pullError != nil {
jobContext.SetStatus("failed").SetError(pullError) jobContext.SetStatus("failed").SetError(pullError)
@ -139,13 +139,13 @@ func (r *Runner) RunJobInContainer(imageUri string, containerId string, job work
r.Driver.Start(imageUri, containerId) r.Driver.Start(imageUri, containerId)
r.DeferTask(func() { r.DeferTask(func() {
log.Printf("Started cleaning up %s", containerId) logger.Info("Started cleaning up %s", containerId)
r.Driver.Stop(containerId) r.Driver.Stop(containerId)
}) })
log.Printf("Started %s", containerId) logger.Info("Started %s", containerId)
for _, step := range job.Steps { for _, step := range job.Steps {
log.Printf("Run: %s", step.Run) logger.Info("Run: %s", step.Run)
if err := r.RunCommandInContainer(containerId, step.Run); err != nil { if err := r.RunCommandInContainer(containerId, step.Run); err != nil {
return err return err

View file

@ -3,14 +3,15 @@ package main
import ( import (
"context" "context"
commands "courgette/internal/commands" commands "courgette/internal/commands"
logger "courgette/internal/logging"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"log"
"os" "os"
) )
var cli = &cobra.Command{ var cli = &cobra.Command{
Use: "runner", Use: "runner",
PersistentPreRun: func(cmd *cobra.Command, args []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
logger.ConfigureLogger()
configPath, err := cmd.Flags().GetString("config") configPath, err := cmd.Flags().GetString("config")
ctx := cmd.Context() ctx := cmd.Context()
@ -19,7 +20,7 @@ var cli = &cobra.Command{
configuration, err := commands.NewConfigFromFile(configPath) configuration, err := commands.NewConfigFromFile(configPath)
if err != nil { if err != nil {
log.Printf("Failed to parse configuration (%s)!", configPath) logger.Error(logger.Red("Failed to parse configuration (%s)!"), configPath)
os.Exit(1) os.Exit(1)
} }
@ -38,7 +39,7 @@ var execute = &cobra.Command{
config := cmd.Context().Value("config").(*commands.Configuration) config := cmd.Context().Value("config").(*commands.Configuration)
if err := commands.ExecuteWorkflow(*config, args[0]); err != nil { if err := commands.ExecuteWorkflow(*config, args[0]); err != nil {
log.Printf("Failure: %s", err) logger.Error(logger.Red("%s"), err)
os.Exit(1) os.Exit(1)
} }
}, },
@ -52,7 +53,7 @@ var validate = &cobra.Command{
config := cmd.Context().Value("config").(*commands.Configuration) config := cmd.Context().Value("config").(*commands.Configuration)
if err := commands.ValidateWorkflow(*config, args[0]); err != nil { if err := commands.ValidateWorkflow(*config, args[0]); err != nil {
log.Printf("Failure: %s", err) logger.Error(logger.Red("%s"), err)
os.Exit(1) os.Exit(1)
} }
}, },