- Added signal.NotifyContext for modern signal handling in cmd/server/main.go - Implemented BaseContext for proper context propagation to HTTP handlers - Added readiness drain delay before shutdown for graceful degradation - Fixed PID detection in start-server.sh to target actual server process - Added Logging.JSON configuration option with DLC_LOGGING_JSON environment variable - Created comprehensive test script that validates entire server lifecycle - Updated documentation with JSON logging configuration examples - All shutdown logs now appear correctly in JSON format - Server terminates gracefully on SIGTERM with proper log flushing The graceful shutdown implementation follows VictoriaMetrics best practices: 1. Catches termination signals (SIGTERM, SIGINT) 2. Stops accepting new requests but allows ongoing requests to complete 3. Waits for active requests to finish within configured timeout 4. Releases resources and performs cleanup 5. Logs all shutdown steps for observability Test script validates: - Server startup and API functionality - Graceful shutdown sequence - JSON log format validation - Complete log sequence verification - Proper signal handling and context propagation Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config represents the application configuration
|
|
type Config struct {
|
|
Server struct {
|
|
Host string `mapstructure:"host"`
|
|
Port int `mapstructure:"port"`
|
|
}
|
|
Shutdown struct {
|
|
Timeout time.Duration `mapstructure:"timeout"`
|
|
}
|
|
Logging struct {
|
|
JSON bool `mapstructure:"json"`
|
|
}
|
|
}
|
|
|
|
// LoadConfig loads configuration from file, environment variables, and defaults
|
|
// Configuration priority: file > environment variables > defaults
|
|
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable
|
|
func LoadConfig() (*Config, error) {
|
|
v := viper.New()
|
|
|
|
// Set default values
|
|
v.SetDefault("server.host", "0.0.0.0")
|
|
v.SetDefault("server.port", 8080)
|
|
v.SetDefault("shutdown.timeout", 30*time.Second)
|
|
v.SetDefault("logging.json", false)
|
|
|
|
// Check for custom config file path via environment variable
|
|
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
|
|
v.SetConfigFile(configFile)
|
|
log.Info().Str("config_file", configFile).Msg("Using custom config file path")
|
|
} else {
|
|
// Default: look for config.yaml in current directory
|
|
v.SetConfigName("config")
|
|
v.SetConfigType("yaml")
|
|
v.AddConfigPath(".")
|
|
}
|
|
|
|
// Read config file if it exists
|
|
if err := v.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
// Config file was found but there was an error reading it
|
|
log.Warn().Err(err).Msg("Error reading config file, using defaults")
|
|
}
|
|
// Config file not found, continue with environment variables and defaults
|
|
} else {
|
|
log.Info().Str("config_file", v.ConfigFileUsed()).Msg("Config file loaded")
|
|
}
|
|
|
|
// Bind environment variables
|
|
v.AutomaticEnv()
|
|
v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix
|
|
v.BindEnv("server.host", "DLC_SERVER_HOST")
|
|
v.BindEnv("server.port", "DLC_SERVER_PORT")
|
|
v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT")
|
|
v.BindEnv("logging.json", "DLC_LOGGING_JSON")
|
|
|
|
// Unmarshal into Config struct
|
|
var config Config
|
|
if err := v.Unmarshal(&config); err != nil {
|
|
log.Error().Err(err).Msg("Failed to unmarshal config")
|
|
return nil, fmt.Errorf("config unmarshal error: %w", err)
|
|
}
|
|
|
|
log.Info().
|
|
Str("host", config.Server.Host).
|
|
Int("port", config.Server.Port).
|
|
Dur("shutdown_timeout", config.Shutdown.Timeout).
|
|
Bool("logging_json", config.Logging.JSON).
|
|
Msg("Configuration loaded")
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// GetServerAddress returns the formatted server address (host:port)
|
|
func (c *Config) GetServerAddress() string {
|
|
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
|
|
} |