From 715417c0ebcb6fa83a59750aade7e3bab1c009b6 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 25 Apr 2026 06:19:48 +0300 Subject: [PATCH] feat: Add structured logging with logrus and configurable output formats - Add sirupsen/logrus v1.9.4 dependency to go.mod - Add logging package with global Logger instance - Add Config struct with Level, Format, and Output fields - Add DefaultConfig with LOG_LEVEL, LOG_FORMAT, and LOG_OUTPUT environment variables - Add Init function with level parsing, JSON/text formatter selection, and stdout/stderr output - Add InitWithDefaults helper function - Add WithField, WithFields, and WithError helper --- apps/api-server/go.mod | 1 + apps/api-server/go.sum | 2 + apps/api-server/internal/logging/logging.go | 141 ++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 apps/api-server/internal/logging/logging.go diff --git a/apps/api-server/go.mod b/apps/api-server/go.mod index b4744f9..1ed8358 100644 --- a/apps/api-server/go.mod +++ b/apps/api-server/go.mod @@ -154,6 +154,7 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/rabbitmq/amqp091-go v1.11.0 + github.com/sirupsen/logrus v1.9.4 github.com/stripe/stripe-go/v82 v82.5.1 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect diff --git a/apps/api-server/go.sum b/apps/api-server/go.sum index f987a66..25ffe06 100644 --- a/apps/api-server/go.sum +++ b/apps/api-server/go.sum @@ -391,6 +391,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= diff --git a/apps/api-server/internal/logging/logging.go b/apps/api-server/internal/logging/logging.go new file mode 100644 index 0000000..6edcd0d --- /dev/null +++ b/apps/api-server/internal/logging/logging.go @@ -0,0 +1,141 @@ +package logging + +import ( + "os" + "time" + + "github.com/sirupsen/logrus" +) + +var Logger *logrus.Logger + +// Config holds logging configuration +type Config struct { + Level string + Format string // "json" or "text" + Output string // "stdout" or "stderr" +} + +// DefaultConfig returns default logging configuration +func DefaultConfig() Config { + return Config{ + Level: os.Getenv("LOG_LEVEL"), + Format: os.Getenv("LOG_FORMAT"), + Output: os.Getenv("LOG_OUTPUT"), + } +} + +// Init initializes the global logger with the given configuration +func Init(config Config) error { + Logger = logrus.New() + + // Set log level + if config.Level == "" { + config.Level = "info" + } + level, err := logrus.ParseLevel(config.Level) + if err != nil { + level = logrus.InfoLevel + } + Logger.SetLevel(level) + + // Set log format + if config.Format == "" { + config.Format = "json" + } + if config.Format == "json" { + Logger.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: time.RFC3339, + FieldMap: logrus.FieldMap{ + logrus.FieldKeyTime: "timestamp", + logrus.FieldKeyLevel: "level", + logrus.FieldKeyMsg: "message", + logrus.FieldKeyFunc: "function", + }, + }) + } else { + Logger.SetFormatter(&logrus.TextFormatter{ + TimestampFormat: time.RFC3339, + FullTimestamp: true, + }) + } + + // Set output + if config.Output == "" || config.Output == "stdout" { + Logger.SetOutput(os.Stdout) + } else { + Logger.SetOutput(os.Stderr) + } + + return nil +} + +// InitWithDefaults initializes the global logger with default configuration +func InitWithDefaults() error { + return Init(DefaultConfig()) +} + +// WithField returns a logger with a single field +func WithField(key string, value interface{}) *logrus.Entry { + return Logger.WithField(key, value) +} + +// WithFields returns a logger with multiple fields +func WithFields(fields logrus.Fields) *logrus.Entry { + return Logger.WithFields(fields) +} + +// WithError returns a logger with an error field +func WithError(err error) *logrus.Entry { + return Logger.WithError(err) +} + +// Info logs an info message +func Info(args ...interface{}) { + Logger.Info(args...) +} + +// Infof logs a formatted info message +func Infof(format string, args ...interface{}) { + Logger.Infof(format, args...) +} + +// Warn logs a warning message +func Warn(args ...interface{}) { + Logger.Warn(args...) +} + +// Warnf logs a formatted warning message +func Warnf(format string, args ...interface{}) { + Logger.Warnf(format, args...) +} + +// Error logs an error message +func Error(args ...interface{}) { + Logger.Error(args...) +} + +// Errorf logs a formatted error message +func Errorf(format string, args ...interface{}) { + Logger.Errorf(format, args...) +} + +// Fatal logs a fatal message and exits +func Fatal(args ...interface{}) { + Logger.Fatal(args...) +} + +// Fatalf logs a formatted fatal message and exits +func Fatalf(format string, args ...interface{}) { + Logger.Fatalf(format, args...) +} + +// Debug logs a debug message +func Debug(args ...interface{}) { + Logger.Debug(args...) +} + +// Debugf logs a formatted debug message +func Debugf(format string, args ...interface{}) { + Logger.Debugf(format, args...) +}