diff --git a/server/internal/app/config.go b/server/internal/app/config.go index 61af395..be9936a 100644 --- a/server/internal/app/config.go +++ b/server/internal/app/config.go @@ -26,9 +26,7 @@ type Config struct { RateLimitRequests int RateLimitWindow time.Duration IsDevelopment bool - LogDir string LogLevel logging.LogLevel - ConsoleOutput bool } // DefaultConfig returns a new Config instance with default values @@ -41,9 +39,6 @@ func DefaultConfig() *Config { RateLimitRequests: 100, RateLimitWindow: time.Minute * 15, IsDevelopment: false, - LogDir: "./logs", - LogLevel: logging.INFO, - ConsoleOutput: false, } } @@ -115,25 +110,14 @@ func LoadConfig() (*Config, error) { } } - // Configure log directory - if logDir := os.Getenv("NOVAMD_LOG_DIR"); logDir != "" { - config.LogDir = logDir - } - // Configure log level, if isDevelopment is set, default to debug if logLevel := os.Getenv("NOVAMD_LOG_LEVEL"); logLevel != "" { - if parsed, err := logging.ParseLogLevel(logLevel); err == nil { - config.LogLevel = parsed - } + parsed := logging.ParseLogLevel(logLevel) + config.LogLevel = parsed } else if config.IsDevelopment { config.LogLevel = logging.DEBUG - } - - // Configure console output, if isDevelopment is set, default to true - if consoleOutput := os.Getenv("NOVAMD_CONSOLE_OUTPUT"); consoleOutput != "" { - if parsed, err := strconv.ParseBool(consoleOutput); err == nil { - config.ConsoleOutput = parsed - } + } else { + config.LogLevel = logging.INFO } // Validate all settings diff --git a/server/internal/app/options.go b/server/internal/app/options.go index 40c8bb7..6042dcb 100644 --- a/server/internal/app/options.go +++ b/server/internal/app/options.go @@ -12,7 +12,6 @@ type Options struct { Config *Config Database db.Database Storage storage.Manager - Logger logging.Logger JWTManager auth.JWTManager SessionManager auth.SessionManager CookieService auth.CookieManager @@ -36,10 +35,7 @@ func DefaultOptions(cfg *Config) (*Options, error) { storageManager := storage.NewService(cfg.WorkDir) // Initialize logger - logger, err := logging.New(cfg.LogDir, cfg.LogLevel, cfg.ConsoleOutput) - if err != nil { - return nil, err - } + logging.Setup(cfg.LogLevel) // Initialize auth services jwtManager, sessionService, cookieService, err := initAuth(cfg, database) @@ -56,7 +52,6 @@ func DefaultOptions(cfg *Config) (*Options, error) { Config: cfg, Database: database, Storage: storageManager, - Logger: logger, JWTManager: jwtManager, SessionManager: sessionService, CookieService: cookieService, diff --git a/server/internal/app/server.go b/server/internal/app/server.go index d9166a1..32f1575 100644 --- a/server/internal/app/server.go +++ b/server/internal/app/server.go @@ -1,8 +1,8 @@ package app import ( - "log/slog" "net/http" + "novamd/internal/logging" "github.com/go-chi/chi/v5" ) @@ -11,7 +11,6 @@ import ( type Server struct { router *chi.Mux options *Options - logger *slog.Logger } // NewServer creates a new server instance with the given options @@ -19,7 +18,6 @@ func NewServer(options *Options) *Server { return &Server{ router: setupRouter(*options), options: options, - logger: options.Logger.App(), } } @@ -27,7 +25,7 @@ func NewServer(options *Options) *Server { func (s *Server) Start() error { // Start server addr := ":" + s.options.Config.Port - s.logger.Info("Starting server", "address", addr) + logging.Info("Starting server", "address", addr) return http.ListenAndServe(addr, s.router) } diff --git a/server/internal/logging/logger.go b/server/internal/logging/logger.go index 2135918..75f9222 100644 --- a/server/internal/logging/logger.go +++ b/server/internal/logging/logger.go @@ -1,17 +1,15 @@ -// Package logging provides a structured logging interface for the application. +// Package logging provides a simple logging interface for the server. package logging import ( - "context" - "fmt" - "io" "log/slog" "os" - "path/filepath" - "time" ) -// LogLevel represents the logging level +// Logger is the global logger instance +var Logger *slog.Logger + +// LogLevel represents the log level type LogLevel slog.Level // Log levels @@ -22,208 +20,55 @@ const ( ERROR LogLevel = LogLevel(slog.LevelError) ) -// Logger defines the interface for logging operations -type Logger interface { - App() *slog.Logger // Returns logger for application logs - Audit() *slog.Logger // Returns logger for audit logs - Security() *slog.Logger // Returns logger for security logs - Close() error // Cleanup and close log files -} - -// logger implements the Logger interface -type logger struct { - appLogger *slog.Logger - auditLogger *slog.Logger - securityLogger *slog.Logger - files []*os.File // Keep track of open file handles -} - -// Output represents a destination for logs -type Output struct { - Type OutputFormat - Writer io.Writer -} - -// OutputFormat represents the format of the log output -type OutputFormat int - -const ( - OutputTypeJSON OutputFormat = iota // OutputTypeJSON JSON format - OutputTypeText // OutputTypeText text format -) - -// createLogFile creates a log file with the given name in the log directory -func createLogFile(logDir, name string) (*os.File, error) { - if err := os.MkdirAll(logDir, 0755); err != nil { - return nil, fmt.Errorf("failed to create log directory: %w", err) - } - - filename := filepath.Join(logDir, fmt.Sprintf("%s.log", name)) - file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return nil, fmt.Errorf("failed to open log file: %w", err) - } - - return file, nil -} - -// createLogger creates a new slog.Handler for the given outputs and options -func createLogger(opts *slog.HandlerOptions, outputs []Output) slog.Handler { - if len(outputs) == 0 { - return slog.NewTextHandler(io.Discard, opts) - } - - if len(outputs) == 1 { - output := outputs[0] - if output.Type == OutputTypeJSON { - return slog.NewJSONHandler(output.Writer, opts) - } - return slog.NewTextHandler(output.Writer, opts) - } - - // Multiple outputs - create handlers for each - handlers := make([]slog.Handler, 0, len(outputs)) - for _, output := range outputs { - if output.Type == OutputTypeJSON { - handlers = append(handlers, slog.NewJSONHandler(output.Writer, opts)) - } else { - handlers = append(handlers, slog.NewTextHandler(output.Writer, opts)) - } - } - - return multiHandler(handlers) -} - -// ParseLogLevel parses a string into a LogLevel -func ParseLogLevel(level string) (LogLevel, error) { - switch level { - case "DEBUG": - return DEBUG, nil - case "INFO": - return INFO, nil - case "WARN": - return WARN, nil - case "ERROR": - return ERROR, nil - default: - return INFO, fmt.Errorf("invalid log level: %s", level) - } -} - -// New creates a new Logger instance -func New(logDir string, minLevel LogLevel, consoleOut bool) (Logger, error) { - l := &logger{ - files: make([]*os.File, 0, 3), - } - - // Define our log types and their filenames - logTypes := []struct { - name string - setLogger func(*slog.Logger) - }{ - {"app", func(lg *slog.Logger) { l.appLogger = lg }}, - {"audit", func(lg *slog.Logger) { l.auditLogger = lg }}, - {"security", func(lg *slog.Logger) { l.securityLogger = lg }}, - } - - // Setup handlers options +// Setup initializes the logger with the given minimum log level +func Setup(minLevel LogLevel) { opts := &slog.HandlerOptions{ - Level: slog.Level(minLevel), - AddSource: slog.Level(minLevel) == slog.LevelDebug, - ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { - if a.Key == slog.TimeKey { - return slog.Attr{ - Key: a.Key, - Value: slog.StringValue(time.Now().UTC().Format(time.RFC3339)), - } - } - return a - }, + Level: slog.Level(minLevel), } - // Create loggers for each type - for _, lt := range logTypes { - // Create file output - file, err := createLogFile(logDir, lt.name) - if err != nil { - if err := l.Close(); err != nil { - return nil, fmt.Errorf("failed to close logger: %w", err) - } - return nil, fmt.Errorf("failed to create %s log file: %w", lt.name, err) - } - l.files = append(l.files, file) + Logger = slog.New(slog.NewTextHandler(os.Stdout, opts)) +} - // Prepare outputs - outputs := []Output{{Type: OutputTypeJSON, Writer: file}} - if consoleOut { - outputs = append(outputs, Output{Type: OutputTypeText, Writer: os.Stdout}) - } - - // Create and set logger - handler := createLogger(opts, outputs) - lt.setLogger(slog.New(handler)) +// ParseLogLevel converts a string to a LogLevel +func ParseLogLevel(level string) LogLevel { + switch level { + case "debug": + return DEBUG + case "warn": + return WARN + case "error": + return ERROR + default: + return INFO } - - return l, nil } -func (l *logger) App() *slog.Logger { - return l.appLogger +// Debug logs a debug message +func Debug(msg string, args ...any) { + Logger.Debug(msg, args...) } -func (l *logger) Audit() *slog.Logger { - return l.auditLogger +// Info logs an info message +func Info(msg string, args ...any) { + Logger.Info(msg, args...) } -func (l *logger) Security() *slog.Logger { - return l.securityLogger +// Warn logs a warning message +func Warn(msg string, args ...any) { + Logger.Warn(msg, args...) } -func (l *logger) Close() error { - var lastErr error - for _, file := range l.files { - if file != nil { - if err := file.Close(); err != nil { - lastErr = err - } - } - } - return lastErr +// Error logs an error message +func Error(msg string, args ...any) { + Logger.Error(msg, args...) } -// multiHandler implements slog.Handler for multiple outputs -type multiHandler []slog.Handler - -func (h multiHandler) Enabled(ctx context.Context, level slog.Level) bool { - for _, handler := range h { - if handler.Enabled(ctx, level) { - return true - } - } - return false +// WithGroup adds a group to the logger context +func WithGroup(name string) *slog.Logger { + return Logger.WithGroup(name) } -func (h multiHandler) Handle(ctx context.Context, r slog.Record) error { - for _, handler := range h { - if err := handler.Handle(ctx, r); err != nil { - return err - } - } - return nil -} - -func (h multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - handlers := make([]slog.Handler, len(h)) - for i, handler := range h { - handlers[i] = handler.WithAttrs(attrs) - } - return multiHandler(handlers) -} - -func (h multiHandler) WithGroup(name string) slog.Handler { - handlers := make([]slog.Handler, len(h)) - for i, handler := range h { - handlers[i] = handler.WithGroup(name) - } - return multiHandler(handlers) +// With adds key-value pairs to the logger context +func With(args ...any) *slog.Logger { + return Logger.With(args...) }