package main import ( "fmt" "os" "os/signal" "syscall" "zwift-activity-loader/internal/config" "zwift-activity-loader/internal/service" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { // Load configuration cfg, err := config.Load() if err != nil { fmt.Fprintf(os.Stderr, "Failed to load configuration: %v\n", err) os.Exit(1) } // Set up logging logger, err := setupLogger(cfg.LogLevel, cfg.LogFile) if err != nil { fmt.Fprintf(os.Stderr, "Failed to setup logger: %v\n", err) os.Exit(1) } defer logger.Sync() // Create service svc, err := service.New(cfg, logger) if err != nil { logger.Fatal("Failed to create service", zap.Error(err)) } // Set up signal handling for graceful shutdown sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Start service in goroutine errChan := make(chan error, 1) go func() { errChan <- svc.Run() }() // Wait for shutdown signal or error select { case sig := <-sigChan: logger.Info("Received signal", zap.String("signal", sig.String())) if err := svc.Shutdown(); err != nil { logger.Error("Error during shutdown", zap.Error(err)) os.Exit(1) } case err := <-errChan: if err != nil { logger.Error("Service error", zap.Error(err)) os.Exit(1) } } logger.Info("Service exited successfully") } // setupLogger creates a zap logger based on configuration func setupLogger(logLevel, logFile string) (*zap.Logger, error) { // Parse log level var level zapcore.Level if err := level.UnmarshalText([]byte(logLevel)); err != nil { level = zapcore.InfoLevel } // Create encoder config encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "time" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Create core var core zapcore.Core if logFile != "" { // Log to both file and stdout file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } fileEncoder := zapcore.NewJSONEncoder(encoderConfig) consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig) core = zapcore.NewTee( zapcore.NewCore(fileEncoder, zapcore.AddSync(file), level), zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), level), ) } else { // Log to stdout only consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig) core = zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), level) } // Create logger logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)) return logger, nil }