From 3640dc48a3da46f7ac354b2e35aa5383a328b1d9 Mon Sep 17 00:00:00 2001 From: srivastava-yash Date: Tue, 22 Oct 2024 09:43:48 -0400 Subject: [PATCH 1/4] config refractored to be initialized once --- config/config.go | 19 +++---------------- internal/middleware/cors.go | 3 +-- internal/tests/integration/commands/setup.go | 3 +-- .../ratelimiter_integration_test.go | 15 ++++++--------- .../tests/stress/ratelimiter_stress_test.go | 5 ++--- main.go | 11 +++++------ util/helpers.go | 3 +-- 7 files changed, 19 insertions(+), 40 deletions(-) diff --git a/config/config.go b/config/config.go index c24a152..32dfcca 100644 --- a/config/config.go +++ b/config/config.go @@ -36,23 +36,10 @@ type Config struct { } } -// LoadConfig loads the application configuration from environment variables or defaults -func LoadConfig() *Config { - err := godotenv.Load() - if err != nil { - slog.Debug("Warning: .env file not found, falling back to system environment variables.") - } +var AppConfig *Config - return &Config{ - DiceDBAdmin: struct { - Addr string - Username string - Password string - }{ - Addr: getEnv("DICEDB_ADMIN_ADDR", "localhost:7379"), // Default DiceDB Admin address - Username: getEnv("DICEDB_ADMIN_USERNAME", "diceadmin"), // Default DiceDB Admin username - Password: getEnv("DICEDB_ADMIN_PASSWORD", ""), // Default DiceDB Admin password - }, +func init() { + AppConfig = &Config{ DiceDB: struct { Addr string Username string diff --git a/internal/middleware/cors.go b/internal/middleware/cors.go index e05c842..709489e 100644 --- a/internal/middleware/cors.go +++ b/internal/middleware/cors.go @@ -7,8 +7,7 @@ import ( // Updated enableCors function to return a boolean indicating if OPTIONS was handled func handleCors(w http.ResponseWriter, r *http.Request) bool { - configValue := config.LoadConfig() - allAllowedOrigins := configValue.Server.AllowedOrigins + allAllowedOrigins := config.AppConfig.Server.AllowedOrigins origin := r.Header.Get("Origin") allowed := false diff --git a/internal/tests/integration/commands/setup.go b/internal/tests/integration/commands/setup.go index 2b2420b..7ed75c0 100644 --- a/internal/tests/integration/commands/setup.go +++ b/internal/tests/integration/commands/setup.go @@ -40,8 +40,7 @@ type TestCase struct { } func NewHTTPCommandExecutor() (*HTTPCommandExecutor, error) { - configValue := config.LoadConfig() - diceClient, err := db.InitDiceClient(configValue, false) + diceClient, err := db.InitDiceClient(config.AppConfig, false) if err != nil { return nil, fmt.Errorf("failed to initialize DiceDB client: %v", err) } diff --git a/internal/tests/integration/ratelimiter_integration_test.go b/internal/tests/integration/ratelimiter_integration_test.go index 017ba95..a1e2c9d 100644 --- a/internal/tests/integration/ratelimiter_integration_test.go +++ b/internal/tests/integration/ratelimiter_integration_test.go @@ -11,9 +11,8 @@ import ( ) func TestRateLimiterWithinLimit(t *testing.T) { - configValue := config.LoadConfig() - limit := configValue.Server.RequestLimitPerMin - window := configValue.Server.RequestWindowSec + limit := config.AppConfig.Server.RequestLimitPerMin + window := config.AppConfig.Server.RequestWindowSec w, r, rateLimiter := util.SetupRateLimiter(limit, window) @@ -24,9 +23,8 @@ func TestRateLimiterWithinLimit(t *testing.T) { } func TestRateLimiterExceedsLimit(t *testing.T) { - configValue := config.LoadConfig() - limit := configValue.Server.RequestLimitPerMin - window := configValue.Server.RequestWindowSec + limit := config.AppConfig.Server.RequestLimitPerMin + window := config.AppConfig.Server.RequestWindowSec w, r, rateLimiter := util.SetupRateLimiter(limit, window) @@ -42,9 +40,8 @@ func TestRateLimiterExceedsLimit(t *testing.T) { } func TestRateLimitHeadersSet(t *testing.T) { - configValue := config.LoadConfig() - limit := configValue.Server.RequestLimitPerMin - window := configValue.Server.RequestWindowSec + limit := config.AppConfig.Server.RequestLimitPerMin + window := config.AppConfig.Server.RequestWindowSec w, r, rateLimiter := util.SetupRateLimiter(limit, window) diff --git a/internal/tests/stress/ratelimiter_stress_test.go b/internal/tests/stress/ratelimiter_stress_test.go index 79872e6..f155809 100644 --- a/internal/tests/stress/ratelimiter_stress_test.go +++ b/internal/tests/stress/ratelimiter_stress_test.go @@ -13,9 +13,8 @@ import ( ) func TestRateLimiterUnderStress(t *testing.T) { - configValue := config.LoadConfig() - limit := configValue.Server.RequestLimitPerMin - window := configValue.Server.RequestWindowSec + limit := config.AppConfig.Server.RequestLimitPerMin + window := config.AppConfig.Server.RequestWindowSec _, r, rateLimiter := util.SetupRateLimiter(limit, window) diff --git a/main.go b/main.go index 6539f8d..ef1b10a 100644 --- a/main.go +++ b/main.go @@ -14,14 +14,13 @@ import ( ) func main() { - configValue := config.LoadConfig() - diceDBAdminClient, err := db.InitDiceClient(configValue, true) + diceDBAdminClient, err := db.InitDiceClient(config.AppConfig, true) if err != nil { slog.Error("Failed to initialize DiceDB Admin client: %v", slog.Any("err", err)) os.Exit(1) } - diceDBClient, err := db.InitDiceClient(configValue, false) + diceDBClient, err := db.InitDiceClient(config.AppConfig, false) if err != nil { slog.Error("Failed to initialize DiceDB client: %v", slog.Any("err", err)) os.Exit(1) @@ -31,14 +30,14 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) wg := sync.WaitGroup{} // Register a cleanup manager, this runs user DiceDB instance cleanup job at configured frequency - cleanupManager := server.NewCleanupManager(diceDBAdminClient, diceDBClient, configValue.Server.CronCleanupFrequency) + cleanupManager := server.NewCleanupManager(diceDBAdminClient, diceDBClient, config.AppConfig.Server.CronCleanupFrequency) wg.Add(1) go cleanupManager.Run(ctx, &wg) // Create mux and register routes mux := http.NewServeMux() - httpServer := server.NewHTTPServer(":8080", mux, diceDBAdminClient, diceDBClient, configValue.Server.RequestLimitPerMin, - configValue.Server.RequestWindowSec) + httpServer := server.NewHTTPServer(":8080", mux, diceDBAdminClient, diceDBClient, config.AppConfig.Server.RequestLimitPerMin, + config.AppConfig.Server.RequestWindowSec) mux.HandleFunc("/health", httpServer.HealthCheck) mux.HandleFunc("/shell/exec/{cmd}", httpServer.CliHandler) mux.HandleFunc("/search", httpServer.SearchHandler) diff --git a/util/helpers.go b/util/helpers.go index 0e9854f..9b6c9bd 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -53,9 +53,8 @@ func ParseHTTPRequest(r *http.Request) (*cmds.CommandRequest, error) { return nil, errors.New("invalid command") } - configValue := config.LoadConfig() // Check if the command is blocklisted - if err := BlockListedCommand(command); err != nil && !configValue.Server.IsTestEnv { + if err := BlockListedCommand(command); err != nil && !config.AppConfig.Server.IsTestEnv { return nil, err } From 02e6dbcb0304842b0d2d9e38c384fcfcc056d132 Mon Sep 17 00:00:00 2001 From: srivastava-yash Date: Mon, 28 Oct 2024 13:17:20 -0400 Subject: [PATCH 2/4] logging config update --- config/config.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/config.go b/config/config.go index 32dfcca..5e89071 100644 --- a/config/config.go +++ b/config/config.go @@ -34,11 +34,18 @@ type Config struct { AllowedOrigins []string // Field for the allowed origins CronCleanupFrequency time.Duration // Field for configuring key cleanup cron } + Logging struct { + Level string + } } var AppConfig *Config func init() { + err := godotenv.Load() + if err != nil { + slog.Debug("Warning: .env file not found, falling back to system environment variables.") + } AppConfig = &Config{ DiceDB: struct { Addr string @@ -64,6 +71,11 @@ func init() { AllowedOrigins: getEnvArray("ALLOWED_ORIGINS", []string{"http://localhost:3000"}), // Default allowed origins CronCleanupFrequency: time.Duration(getEnvInt("CRON_CLEANUP_FREQUENCY_MINS", 15)) * time.Minute, // Default cron cleanup frequency }, + Logging: struct { + Level string + }{ + Level: getEnv("LOGGING_LEVEL", "info"), + }, } } From cbf12cdbd399064769855098a8105fb0bc8feba8 Mon Sep 17 00:00:00 2001 From: srivastava-yash Date: Mon, 28 Oct 2024 13:30:39 -0400 Subject: [PATCH 3/4] custom logger implemented --- internal/logger/logger.go | 87 +++++++++++++++++++++++++++++++++++++++ main.go | 2 + 2 files changed, 89 insertions(+) create mode 100644 internal/logger/logger.go diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..469af95 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,87 @@ +package logger + +import ( + "context" + "fmt" + "log" + "log/slog" + "server/config" + "strings" + "time" +) + +type CustomLogHandler struct { + level slog.Level + attrs map[string]interface{} + group string +} + +func NewCustomLogHandler(level slog.Level) *CustomLogHandler { + return &CustomLogHandler{ + level: level, + attrs: make(map[string]interface{}), + } +} + +func (h *CustomLogHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level +} + +func (h *CustomLogHandler) Handle(_ context.Context, record slog.Record) error { + if !h.Enabled(nil, record.Level) { + return nil + } + + message := fmt.Sprintf("[%s] [%s] %s", time.Now().Format(time.RFC3339), record.Level, record.Message) + + // Append attributes + for k, v := range h.attrs { + message += fmt.Sprintf(" | %s=%v", k, v) + } + + // Log to standard output + log.Println(message) + return nil +} + +func (h *CustomLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + newHandler := *h + newHandler.attrs = make(map[string]interface{}) + + for k, v := range h.attrs { + newHandler.attrs[k] = v + } + + for _, attr := range attrs { + newHandler.attrs[attr.Key] = attr.Value.Any() + } + return &newHandler +} + +func (h *CustomLogHandler) WithGroup(name string) slog.Handler { + newHandler := *h + newHandler.group = name + return &newHandler +} + +func ParseLogLevel(levelStr string) slog.Level { + switch strings.ToLower(levelStr) { + case "debug": + return slog.LevelDebug + case "info": + return slog.LevelInfo + case "warn", "warning": + return slog.LevelWarn + case "error": + return slog.LevelError + default: + return slog.LevelInfo + } +} + +func New() *slog.Logger { + levelStr := config.AppConfig.Logging.Level + level := ParseLogLevel(levelStr) + handler := NewCustomLogHandler(level) + return slog.New(handler) +} diff --git a/main.go b/main.go index ef1b10a..66985b6 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "os" "server/config" "server/internal/db" + "server/internal/logger" "server/internal/server" "sync" @@ -14,6 +15,7 @@ import ( ) func main() { + slog.SetDefault(logger.New()) diceDBAdminClient, err := db.InitDiceClient(config.AppConfig, true) if err != nil { slog.Error("Failed to initialize DiceDB Admin client: %v", slog.Any("err", err)) From 035bec8568f974a6aec390670f0300dbe30207ca Mon Sep 17 00:00:00 2001 From: srivastava-yash Date: Wed, 30 Oct 2024 13:47:40 -0400 Subject: [PATCH 4/4] minor refractor --- internal/logger/logger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 469af95..5a48b53 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -27,8 +27,9 @@ func (h *CustomLogHandler) Enabled(_ context.Context, level slog.Level) bool { return level >= h.level } +//nolint:gocritic // The slog.Record struct triggers hugeParam, but we don't control the interface (it's a standard library one) func (h *CustomLogHandler) Handle(_ context.Context, record slog.Record) error { - if !h.Enabled(nil, record.Level) { + if !h.Enabled(context.TODO(), record.Level) { return nil }