From 3d3650cde55896b5c28888462c3be50751851c55 Mon Sep 17 00:00:00 2001 From: "P. Barrett Little" Date: Tue, 10 Sep 2024 22:45:30 -0400 Subject: [PATCH] feat: Implement structured logging with zerolog Integrate zerolog for enhanced logging capabilities across the application. Replace standard log package with custom logging functions that provide structured logging and better error handling. This change improves log readability, allows for easier log parsing, and enables more granular control over log levels. --- go.mod | 8 ++++ go.sum | 21 ++++++++++ internal/logging/logger.go | 59 ++++++++++++++++++++++++++++ internal/parsers/shot_data_parser.go | 14 +++++-- main.go | 35 +++++++++++++---- 5 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 go.sum create mode 100644 internal/logging/logger.go diff --git a/go.mod b/go.mod index 6ec2d47..0e38153 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module albatross go 1.21 + +require github.com/rs/zerolog v1.33.0 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..468cbe9 --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/logging/logger.go b/internal/logging/logger.go new file mode 100644 index 0000000..3cb25fd --- /dev/null +++ b/internal/logging/logger.go @@ -0,0 +1,59 @@ +// Package logging provides a robust logging system for the Albatross project. +package logging + +import ( + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +// InitLogger initializes the global logger with custom settings. +func InitLogger() { + // Set up the logger + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}) + + // Set the global log level (can be changed based on environment) + zerolog.SetGlobalLevel(zerolog.InfoLevel) +} + +// Fields is a type alias for log field key-value pairs +type Fields map[string]interface{} + +// Info logs an info level message with optional fields +func Info(message string, fields Fields) { + event := log.Info() + for k, v := range fields { + event = event.Interface(k, v) + } + event.Msg(message) +} + +// Error logs an error level message with optional fields +func Error(message string, err error, fields Fields) { + event := log.Error().Err(err) + for k, v := range fields { + event = event.Interface(k, v) + } + event.Msg(message) +} + +// Debug logs a debug level message with optional fields +func Debug(message string, fields Fields) { + event := log.Debug() + for k, v := range fields { + event = event.Interface(k, v) + } + event.Msg(message) +} + +// Fatal logs a fatal level message with optional fields and then exits +func Fatal(message string, fields Fields) { + event := log.Fatal() + for k, v := range fields { + event = event.Interface(k, v) + } + event.Msg(message) +} diff --git a/internal/parsers/shot_data_parser.go b/internal/parsers/shot_data_parser.go index b96d2a2..7ef7c75 100644 --- a/internal/parsers/shot_data_parser.go +++ b/internal/parsers/shot_data_parser.go @@ -5,11 +5,11 @@ import ( "encoding/csv" "fmt" "io" - "log" "os" "regexp" "strings" + "albatross/internal/logging" "albatross/internal/models" "albatross/internal/reader" ) @@ -64,7 +64,9 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce if isHeader(row) { headers = normalizeHeaders(row) inDataBlock = true - log.Printf("Found headers: %v", headers) + logging.Debug("Found headers", logging.Fields{ + "headers": headers, + }) continue } @@ -81,7 +83,9 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce // Parse and process the row data rawData, err := launchMonitor.ParseRow(row, headers) if err != nil { - log.Printf("Skipping row due to error: %v", err) + logging.Error("Skipping row due to error", err, logging.Fields{ + "row": row, + }) continue } @@ -93,6 +97,10 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce return nil, fmt.Errorf("no valid data found in the file") } + logging.Info("Processed shot data", logging.Fields{ + "shotsProcessed": len(shotData), + }) + return shotData, nil } diff --git a/main.go b/main.go index b67fda6..3babd7f 100755 --- a/main.go +++ b/main.go @@ -3,11 +3,11 @@ package main import ( - "log" "os" "strings" "albatross/internal/calculators" + "albatross/internal/logging" "albatross/internal/parsers" "albatross/internal/writer" "albatross/utils" @@ -16,9 +16,12 @@ import ( // main is the entry point of the application. It handles command-line arguments, // processes shot data, calculates targets, and writes the results to a file. func main() { + // Initialize the logger + logging.InitLogger() + // Parse command-line arguments if len(os.Args) != 3 { - log.Fatal("Usage: go run main.go ") + logging.Fatal("Usage: go run main.go ", nil) } launchMonitorType := normalizeLaunchMonitorType(os.Args[1]) @@ -26,30 +29,46 @@ func main() { // Validate launch monitor type if !isValidLaunchMonitorType(launchMonitorType) { - log.Fatalf("Error: Invalid launch monitor type '%s'. Supported type is mlm2pro.", os.Args[1]) + logging.Fatal("Error: Invalid launch monitor type. Supported type is mlm2pro.", logging.Fields{ + "providedType": launchMonitorType, + }) } // Process shot data from the input file shotData, err := parsers.ProcessShotData(inputFile, launchMonitorType) if err != nil { - log.Fatalf("Error processing shot data: %v", err) + logging.Error("Error processing shot data", err, logging.Fields{ + "inputFile": inputFile, + "launchMonitorType": launchMonitorType, + }) + os.Exit(1) } - log.Printf("Processed shot data: %+v", shotData) + logging.Info("Processed shot data", logging.Fields{ + "count": len(shotData), + }) // Calculate targets based on the processed shot data calculators.CalculateTargets(&shotData) - log.Printf("Calculated targets: %+v", shotData) + logging.Debug("Calculated targets", logging.Fields{ + "shotData": shotData, + }) // Write processed data to an output file outputFile := utils.ReplaceFileExtension(inputFile, "_processed.csv") writer := writer.ShotPatternWriter{} if err := writer.Write(outputFile, shotData); err != nil { - log.Fatalf("Error writing output file: %v", err) + logging.Error("Error writing output file", err, logging.Fields{ + "outputFile": outputFile, + }) + os.Exit(1) } - log.Printf("Successfully processed %d shots and saved results to %s", len(shotData), outputFile) + logging.Info("Successfully processed shots and saved results", logging.Fields{ + "shotsProcessed": len(shotData), + "outputFile": outputFile, + }) } // normalizeLaunchMonitorType converts the launch monitor type to lowercase for consistency.