Enhance build system and logging configuration

- Add scripts/build.sh to compile binaries into bin/ directory

- Move all zerolog setup logic from cmd/server/main.go to pkg/config

- Add log level configuration support (trace, debug, info, warn, error, fatal, panic)

- Simplify cmd/server/main.go from 57 to 27 lines (53% reduction)

- Update .gitignore to use bin/ directory instead of individual files

- Document build process and bin directory in AGENTS.md

- Maintain backward compatibility with all existing functionality
This commit is contained in:
2026-04-04 13:24:33 +02:00
parent 9855f521f3
commit 00e796c608
6 changed files with 119 additions and 36 deletions

3
.gitignore vendored
View File

@@ -2,8 +2,7 @@
*.exe *.exe
*.test *.test
*.out *.out
server bin/
greet
# Dependency directories # Dependency directories
vendor/ vendor/

View File

@@ -355,12 +355,37 @@ go test ./...
go test ./pkg/greet/ go test ./pkg/greet/
``` ```
### 5. Make Changes ### 5. Build Binaries
The project uses a build script to compile binaries into the `bin/` directory:
```bash
# Build both server and greet binaries
./scripts/build.sh
# This creates:
# - ./bin/server - The web server binary
# - ./bin/greet - The CLI greeting tool
```
**Binary Usage:**
```bash
# Run the server
./bin/server
# Use the greet CLI
./bin/greet # Output: Hello world!
./bin/greet John # Output: Hello John!
```
**The `bin/` directory is gitignored** to prevent binary files from being committed to the repository.
### 6. Make Changes
- Edit source files in `pkg/` or `cmd/` - Edit source files in `pkg/` or `cmd/`
- Follow existing patterns and interfaces - Follow existing patterns and interfaces
- Add tests for new functionality - Add tests for new functionality
### 6. Stop and Restart ### 7. Stop and Restart
```bash ```bash
./scripts/start-server.sh restart ./scripts/start-server.sh restart
``` ```

View File

@@ -2,49 +2,19 @@ package main
import ( import (
"context" "context"
"os"
"DanceLessonsCoach/pkg/config" "DanceLessonsCoach/pkg/config"
"DanceLessonsCoach/pkg/server" "DanceLessonsCoach/pkg/server"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func main() { func main() {
// Initialize Zerolog with default console format first // Load configuration (this will also setup logging)
zerolog.SetGlobalLevel(zerolog.TraceLevel)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
// Check if JSON logging is requested via environment variable
// This allows JSON logging even during config loading
jsonLogging := os.Getenv("DLC_LOGGING_JSON") == "true"
if jsonLogging {
// JSON output for structured logging
log.Logger = log.Output(os.Stderr)
} else {
// Console output for initial logging
log.Logger = log.Output(consoleWriter)
}
// Load configuration
cfg, err := config.LoadConfig() cfg, err := config.LoadConfig()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to load configuration") log.Fatal().Err(err).Msg("Failed to load configuration")
} }
// Reconfigure logging based on loaded configuration (overrides env var)
if cfg.Logging.JSON {
// JSON output for structured logging
log.Logger = log.Output(os.Stderr)
} else {
// Keep console output
log.Logger = log.Output(consoleWriter)
}
log.Info().Bool("json_logging", cfg.Logging.JSON).Msg("Logging configured")
// Create readiness context to control readiness state // Create readiness context to control readiness state
readyCtx, readyCancel := context.WithCancel(context.Background()) readyCtx, readyCancel := context.WithCancel(context.Background())
defer readyCancel() defer readyCancel()

View File

@@ -21,6 +21,10 @@ logging:
# Enable JSON output for structured logging (default: false) # Enable JSON output for structured logging (default: false)
# When true, logs are output in JSON format instead of console format # When true, logs are output in JSON format instead of console format
json: false json: false
# Log level (default: "trace")
# Options: "trace", "debug", "info", "warn", "error", "fatal", "panic"
level: trace
# Telemetry configuration (OpenTelemetry) # Telemetry configuration (OpenTelemetry)
telemetry: telemetry:
@@ -53,6 +57,7 @@ telemetry:
# DLC_SERVER_PORT=8080 # DLC_SERVER_PORT=8080
# DLC_SHUTDOWN_TIMEOUT=30s # DLC_SHUTDOWN_TIMEOUT=30s
# DLC_LOGGING_JSON=false # DLC_LOGGING_JSON=false
# DLC_LOGGING_LEVEL=trace
# DLC_TELEMETRY_ENABLED=true # DLC_TELEMETRY_ENABLED=true
# DLC_TELEMETRY_OTLP_ENDPOINT="jaeger:4317" # DLC_TELEMETRY_OTLP_ENDPOINT="jaeger:4317"
# DLC_TELEMETRY_SERVICE_NAME="DanceLessonsCoach" # DLC_TELEMETRY_SERVICE_NAME="DanceLessonsCoach"

View File

@@ -3,8 +3,10 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@@ -30,7 +32,8 @@ type ShutdownConfig struct {
// LoggingConfig holds logging-related configuration // LoggingConfig holds logging-related configuration
type LoggingConfig struct { type LoggingConfig struct {
JSON bool `mapstructure:"json"` JSON bool `mapstructure:"json"`
Level string `mapstructure:"level"`
} }
// TelemetryConfig holds OpenTelemetry-related configuration // TelemetryConfig holds OpenTelemetry-related configuration
@@ -53,12 +56,17 @@ type SamplerConfig struct {
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable // To specify a custom config file path, set DLC_CONFIG_FILE environment variable
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
v := viper.New() v := viper.New()
// Set up initial console logging for config loading messages
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
log.Logger = log.Output(consoleWriter)
// Set default values // Set default values
v.SetDefault("server.host", "0.0.0.0") v.SetDefault("server.host", "0.0.0.0")
v.SetDefault("server.port", 8080) v.SetDefault("server.port", 8080)
v.SetDefault("shutdown.timeout", 30*time.Second) v.SetDefault("shutdown.timeout", 30*time.Second)
v.SetDefault("logging.json", false) v.SetDefault("logging.json", false)
v.SetDefault("logging.level", "trace")
// Telemetry defaults // Telemetry defaults
v.SetDefault("telemetry.enabled", false) v.SetDefault("telemetry.enabled", false)
@@ -97,6 +105,7 @@ func LoadConfig() (*Config, error) {
v.BindEnv("server.port", "DLC_SERVER_PORT") v.BindEnv("server.port", "DLC_SERVER_PORT")
v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT") v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT")
v.BindEnv("logging.json", "DLC_LOGGING_JSON") v.BindEnv("logging.json", "DLC_LOGGING_JSON")
v.BindEnv("logging.level", "DLC_LOGGING_LEVEL")
// Telemetry environment variables // Telemetry environment variables
v.BindEnv("telemetry.enabled", "DLC_TELEMETRY_ENABLED") v.BindEnv("telemetry.enabled", "DLC_TELEMETRY_ENABLED")
@@ -113,11 +122,23 @@ func LoadConfig() (*Config, error) {
return nil, fmt.Errorf("config unmarshal error: %w", err) return nil, fmt.Errorf("config unmarshal error: %w", err)
} }
// Configure log output format (JSON or console) first
if config.Logging.JSON {
log.Logger = log.Output(os.Stderr)
} else {
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
log.Logger = log.Output(consoleWriter)
}
// Setup logging based on configuration
config.SetupLogging()
log.Info(). log.Info().
Str("host", config.Server.Host). Str("host", config.Server.Host).
Int("port", config.Server.Port). Int("port", config.Server.Port).
Dur("shutdown_timeout", config.Shutdown.Timeout). Dur("shutdown_timeout", config.Shutdown.Timeout).
Bool("logging_json", config.Logging.JSON). Bool("logging_json", config.Logging.JSON).
Str("logging_level", config.Logging.Level).
Bool("telemetry_enabled", config.Telemetry.Enabled). Bool("telemetry_enabled", config.Telemetry.Enabled).
Str("telemetry_service", config.Telemetry.ServiceName). Str("telemetry_service", config.Telemetry.ServiceName).
Msg("Configuration loaded") Msg("Configuration loaded")
@@ -159,3 +180,40 @@ func (c *Config) GetSamplerType() string {
func (c *Config) GetSamplerRatio() float64 { func (c *Config) GetSamplerRatio() float64 {
return c.Telemetry.Sampler.Ratio return c.Telemetry.Sampler.Ratio
} }
// GetLogLevel returns the logging level
func (c *Config) GetLogLevel() string {
return c.Logging.Level
}
// SetupLogging configures zerolog based on the configuration
func (c *Config) SetupLogging() {
// Parse log level
level := parseLogLevel(c.GetLogLevel())
zerolog.SetGlobalLevel(level)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
}
// parseLogLevel converts a string log level to zerolog.Level
func parseLogLevel(level string) zerolog.Level {
switch strings.ToLower(level) {
case "trace":
return zerolog.TraceLevel
case "debug":
return zerolog.DebugLevel
case "info":
return zerolog.InfoLevel
case "warn", "warning":
return zerolog.WarnLevel
case "error":
return zerolog.ErrorLevel
case "fatal":
return zerolog.FatalLevel
case "panic":
return zerolog.PanicLevel
default:
log.Warn().Str("level", level).Msg("Unknown log level, defaulting to trace")
return zerolog.TraceLevel
}
}

26
scripts/build.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# DanceLessonsCoach Build Script
# Builds binaries into the bin/ directory
set -e
echo "🔨 Building DanceLessonsCoach binaries..."
# Create bin directory if it doesn't exist
mkdir -p bin
# Build server binary
echo "📦 Building server..."
go build -o bin/server ./cmd/server
# Build greet CLI binary
echo "📦 Building greet CLI..."
go build -o bin/greet ./cmd/greet
echo "✅ Build complete!"
echo " Server binary: ./bin/server"
echo " Greet binary: ./bin/greet"
echo ""
echo "💡 To run the server: ./bin/server"
echo "💡 To use the greet CLI: ./bin/greet [name]"