diff --git a/go.mod b/go.mod index 3625d5d..07badda 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module github.com/nutcase/gomian go 1.24 + +require go.uber.org/zap v1.27.0 + +require go.uber.org/multierr v1.10.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2d29b57 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logging/cb_logger.go b/logging/cb_logger.go new file mode 100644 index 0000000..53f3161 --- /dev/null +++ b/logging/cb_logger.go @@ -0,0 +1,88 @@ +package logging + +import ( + "go.uber.org/zap" + "time" +) + +type CircuitBreakerLogger struct { + logger *zap.Logger +} + +func NewCircuitBreakerLogger() *CircuitBreakerLogger { + return &CircuitBreakerLogger{ + logger: Logger().With(zap.String("component", "circuit_breaker")), + } +} + +func (l *CircuitBreakerLogger) LogStateChange(name string, from, to string) { + l.logger.Info("circuit breaker state changed", + zap.String("circuit", name), + zap.String("from_state", from), + zap.String("to_state", to), + zap.Time("timestamp", time.Now()), + ) +} + +func (l *CircuitBreakerLogger) LogTrip(name string, err error) { + fields := []zap.Field{ + zap.String("circuit", name), + zap.Time("timestamp", time.Now()), + } + + if err != nil { + fields = append(fields, zap.Error(err)) + } + + l.logger.Warn("circuit breaker tripped", fields...) +} + +func (l *CircuitBreakerLogger) LogReset(name string) { + l.logger.Info("circuit breaker reset", + zap.String("circuit", name), + zap.Time("timestamp", time.Now()), + ) +} + +func (l *CircuitBreakerLogger) LogSuccess(name string) { + l.logger.Debug("circuit breaker request succeeded", + zap.String("circuit", name), + zap.Time("timestamp", time.Now()), + ) +} + +func (l *CircuitBreakerLogger) LogFailure(name string, err error) { + fields := []zap.Field{ + zap.String("circuit", name), + zap.Time("timestamp", time.Now()), + } + + if err != nil { + fields = append(fields, zap.Error(err)) + } + + l.logger.Debug("circuit breaker request failed", fields...) +} + +func (l *CircuitBreakerLogger) LogRejection(name string) { + l.logger.Debug("circuit breaker request rejected", + zap.String("circuit", name), + zap.Time("timestamp", time.Now()), + ) +} + +func (l *CircuitBreakerLogger) LogMetrics(name string, state string, totalRequests uint64, + totalFailures uint64, consecutiveFailures uint64, consecutiveSuccesses uint64, + timeInState time.Duration) { + + l.logger.Debug("circuit breaker metrics", + zap.String("circuit", name), + zap.String("state", state), + zap.Uint64("total_requests", totalRequests), + zap.Uint64("total_failures", totalFailures), + zap.Uint64("consecutive_failures", consecutiveFailures), + zap.Uint64("consecutive_successes", consecutiveSuccesses), + zap.Duration("time_in_state", timeInState), + zap.Time("timestamp", time.Now()), + ) +} diff --git a/logging/logger.go b/logging/logger.go new file mode 100644 index 0000000..f50555b --- /dev/null +++ b/logging/logger.go @@ -0,0 +1,128 @@ +package logging + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "sync" +) + +var ( + defaultLogger *zap.Logger + loggerMu sync.RWMutex + initialized bool +) + +type LogLevel string + +const ( + LogLevelDebug LogLevel = "debug" + LogLevelInfo LogLevel = "info" + LogLevelWarn LogLevel = "warn" + LogLevelError LogLevel = "error" +) + +type Config struct { + Level LogLevel + + Development bool + + Encoding string +} + +func DefaultConfig() Config { + return Config{ + Level: LogLevelInfo, + Development: false, + Encoding: "json", + } +} + +func Initialize(config Config) error { + loggerMu.Lock() + defer loggerMu.Unlock() + + var level zapcore.Level + switch config.Level { + case LogLevelDebug: + level = zapcore.DebugLevel + case LogLevelInfo: + level = zapcore.InfoLevel + case LogLevelWarn: + level = zapcore.WarnLevel + case LogLevelError: + level = zapcore.ErrorLevel + default: + level = zapcore.InfoLevel + } + + zapConfig := zap.Config{ + Level: zap.NewAtomicLevelAt(level), + Development: config.Development, + Encoding: config.Encoding, + EncoderConfig: zap.NewProductionEncoderConfig(), + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + } + + if config.Development { + zapConfig.EncoderConfig = zap.NewDevelopmentEncoderConfig() + } else { + zapConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + } + + logger, err := zapConfig.Build() + if err != nil { + return err + } + + defaultLogger = logger + initialized = true + return nil +} + +func ensureInitialized() { + loggerMu.RLock() + init := initialized + loggerMu.RUnlock() + + if !init { + _ = Initialize(DefaultConfig()) + } +} + +func Logger() *zap.Logger { + ensureInitialized() + loggerMu.RLock() + defer loggerMu.RUnlock() + return defaultLogger +} + +func With(fields ...zap.Field) *zap.Logger { + return Logger().With(fields...) +} + +func Debug(msg string, fields ...zap.Field) { + Logger().Debug(msg, fields...) +} + +func Info(msg string, fields ...zap.Field) { + Logger().Info(msg, fields...) +} + +func Warn(msg string, fields ...zap.Field) { + Logger().Warn(msg, fields...) +} + +func Error(msg string, fields ...zap.Field) { + Logger().Error(msg, fields...) +} + +func Sync() error { + loggerMu.RLock() + defer loggerMu.RUnlock() + + if defaultLogger != nil { + return defaultLogger.Sync() + } + return nil +}