diff --git a/.gitignore b/.gitignore index 87af0e9..3ced225 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ go.work # Server runtime files server.log server.pid +*.log diff --git a/AGENTS.md b/AGENTS.md index d2238f3..186045c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -156,6 +156,7 @@ The server supports flexible configuration through environment variables with th | Port | `DLC_SERVER_PORT` | `8080` | Server listening port | | Shutdown Timeout | `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown timeout | | JSON Logging | `DLC_LOGGING_JSON` | `false` | Enable JSON format logging | +| Log Output | `DLC_LOGGING_OUTPUT` | `""` | Log output file path (empty for stderr) | **Usage Examples:** @@ -176,6 +177,15 @@ export DLC_SHUTDOWN_TIMEOUT=45s # Enable JSON logging export DLC_LOGGING_JSON=true ./scripts/start-server.sh start + +# Log to file +export DLC_LOGGING_OUTPUT="server.log" +./scripts/start-server.sh start + +# Combined: JSON logging to file +export DLC_LOGGING_JSON=true +export DLC_LOGGING_OUTPUT="server.json.log" +./scripts/start-server.sh start ``` **Configuration File Support:** diff --git a/cmd/server/main.go b/cmd/server/main.go index 3196aba..3294821 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "os" "DanceLessonsCoach/pkg/config" "DanceLessonsCoach/pkg/server" @@ -15,6 +16,9 @@ func main() { log.Fatal().Err(err).Msg("Failed to load configuration") } + // Setup log output based on configuration + setupLogOutput(cfg) + // Create readiness context to control readiness state readyCtx, readyCancel := context.WithCancel(context.Background()) defer readyCancel() @@ -25,3 +29,23 @@ func main() { log.Fatal().Err(err).Msg("Server failed") } } + +// setupLogOutput configures the log output based on configuration +func setupLogOutput(cfg *config.Config) { + output := cfg.GetLogOutput() + if output == "" { + // Use stderr by default + return + } + + // Open the log file + file, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Error().Err(err).Str("output", output).Msg("Failed to open log file, using stderr") + return + } + + // Set the log output + log.Logger = log.Output(file) + log.Info().Str("output", output).Msg("Logging to file") +} diff --git a/config.example.yaml b/config.example.yaml deleted file mode 100644 index f7c83f3..0000000 --- a/config.example.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# DanceLessonsCoach Configuration Example -# This file shows the available configuration options -# You can use this as a template for your own configuration - -# Server configuration -server: - # Host address to bind to (default: "0.0.0.0") - host: "0.0.0.0" - - # Port to listen on (default: 8080) - port: 8080 - -# Shutdown configuration -shutdown: - # Timeout duration for graceful shutdown (default: 30s) - # Format: number + unit (s, m, h) - timeout: 30s - -# Logging configuration -logging: - # Enable JSON output for structured logging (default: false) - # When true, logs are output in JSON format instead of console format - json: false - - # Log level (default: "trace") - # Options: "trace", "debug", "info", "warn", "error", "fatal", "panic" - level: trace - -# Telemetry configuration (OpenTelemetry) -telemetry: - # Enable OpenTelemetry tracing (default: false) - enabled: false - - # OTLP endpoint for trace export (default: "localhost:4317") - # Format: host:port - otlp_endpoint: "localhost:4317" - - # Service name for tracing (default: "DanceLessonsCoach") - service_name: "DanceLessonsCoach" - - # Use insecure connection (no TLS) (default: true) - insecure: true - - # Sampler configuration - sampler: - # Sampler type (default: "parentbased_always_on") - # Options: "always_on", "always_off", "traceidratio", "parentbased_always_on", "parentbased_always_off", "parentbased_traceidratio" - type: "parentbased_always_on" - - # Sampling ratio (0.0 to 1.0, default: 1.0) - # Only used with traceidratio and parentbased_traceidratio samplers - ratio: 1.0 - -# Environment Variables -# You can also configure via environment variables with DLC_ prefix: -# DLC_SERVER_HOST=0.0.0.0 -# DLC_SERVER_PORT=8080 -# DLC_SHUTDOWN_TIMEOUT=30s -# DLC_LOGGING_JSON=false -# DLC_LOGGING_LEVEL=trace -# DLC_TELEMETRY_ENABLED=true -# DLC_TELEMETRY_OTLP_ENDPOINT="jaeger:4317" -# DLC_TELEMETRY_SERVICE_NAME="DanceLessonsCoach" -# DLC_TELEMETRY_INSECURE=true -# DLC_TELEMETRY_SAMPLER_TYPE="parentbased_always_on" -# DLC_TELEMETRY_SAMPLER_RATIO=1.0 diff --git a/config.json_test.yaml b/config.json_test.yaml deleted file mode 100644 index 2a006bd..0000000 --- a/config.json_test.yaml +++ /dev/null @@ -1,7 +0,0 @@ -server: - host: "0.0.0.0" - port: 8080 -shutdown: - timeout: 5s -logging: - json: true \ No newline at end of file diff --git a/config.yaml b/config.yaml index 2e88d1b..ef18b21 100644 --- a/config.yaml +++ b/config.yaml @@ -1,5 +1,58 @@ +# DanceLessonsCoach Configuration +# This file serves as both the default configuration and documentation +# All available options are shown with their default values + +# Server configuration server: + # Host address to bind to (default: "0.0.0.0") host: "0.0.0.0" + + # Port to listen on (default: 8080) port: 8080 + +# Shutdown configuration shutdown: - timeout: 5s \ No newline at end of file + # Timeout duration for graceful shutdown (default: 30s) + # Format: number + unit (s, m, h) + timeout: 30s + +# Logging configuration +logging: + # Enable JSON output for structured logging (default: false) + # When true, logs are output in JSON format instead of console format + json: false + + # Log level (default: "trace") + # Options: "trace", "debug", "info", "warn", "error", "fatal", "panic" + level: trace + + # Log output file path (default: "" for stderr) + # If empty, logs will be written to stderr + # If specified, logs will be written to the specified file + # Example: "server.log" or "/var/log/dance-lessons-coach.log" + output: "" + +# Telemetry configuration (OpenTelemetry) +telemetry: + # Enable OpenTelemetry tracing (default: false) + enabled: false + + # OTLP endpoint for trace export (default: "localhost:4317") + # Format: host:port + otlp_endpoint: "localhost:4317" + + # Service name for tracing (default: "DanceLessonsCoach") + service_name: "DanceLessonsCoach" + + # Use insecure connection (no TLS) (default: true) + insecure: true + + # Sampler configuration + sampler: + # Sampler type (default: "parentbased_always_on") + # Options: "always_on", "always_off", "traceidratio", "parentbased_always_on", "parentbased_always_off", "parentbased_traceidratio" + type: "parentbased_always_on" + + # Sampling ratio (0.0 to 1.0, default: 1.0) + # Only used with traceidratio and parentbased_traceidratio samplers + ratio: 1.0 \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index d1aacc8..8685a23 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -34,6 +34,7 @@ type ShutdownConfig struct { type LoggingConfig struct { JSON bool `mapstructure:"json"` Level string `mapstructure:"level"` + Output string `mapstructure:"output"` } // TelemetryConfig holds OpenTelemetry-related configuration @@ -67,6 +68,7 @@ func LoadConfig() (*Config, error) { v.SetDefault("shutdown.timeout", 30*time.Second) v.SetDefault("logging.json", false) v.SetDefault("logging.level", "trace") + v.SetDefault("logging.output", "") // Telemetry defaults v.SetDefault("telemetry.enabled", false) @@ -106,6 +108,7 @@ func LoadConfig() (*Config, error) { v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT") v.BindEnv("logging.json", "DLC_LOGGING_JSON") v.BindEnv("logging.level", "DLC_LOGGING_LEVEL") + v.BindEnv("logging.output", "DLC_LOGGING_OUTPUT") // Telemetry environment variables v.BindEnv("telemetry.enabled", "DLC_TELEMETRY_ENABLED") @@ -139,6 +142,7 @@ func LoadConfig() (*Config, error) { Dur("shutdown_timeout", config.Shutdown.Timeout). Bool("logging_json", config.Logging.JSON). Str("logging_level", config.Logging.Level). + Str("logging_output", config.Logging.Output). Bool("telemetry_enabled", config.Telemetry.Enabled). Str("telemetry_service", config.Telemetry.ServiceName). Msg("Configuration loaded") @@ -186,6 +190,11 @@ func (c *Config) GetLogLevel() string { return c.Logging.Level } +// GetLogOutput returns the log output path +func (c *Config) GetLogOutput() string { + return c.Logging.Output +} + // SetupLogging configures zerolog based on the configuration func (c *Config) SetupLogging() { // Parse log level