mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Simplify logging
This commit is contained in:
@@ -26,9 +26,7 @@ type Config struct {
|
|||||||
RateLimitRequests int
|
RateLimitRequests int
|
||||||
RateLimitWindow time.Duration
|
RateLimitWindow time.Duration
|
||||||
IsDevelopment bool
|
IsDevelopment bool
|
||||||
LogDir string
|
|
||||||
LogLevel logging.LogLevel
|
LogLevel logging.LogLevel
|
||||||
ConsoleOutput bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns a new Config instance with default values
|
// DefaultConfig returns a new Config instance with default values
|
||||||
@@ -41,9 +39,6 @@ func DefaultConfig() *Config {
|
|||||||
RateLimitRequests: 100,
|
RateLimitRequests: 100,
|
||||||
RateLimitWindow: time.Minute * 15,
|
RateLimitWindow: time.Minute * 15,
|
||||||
IsDevelopment: false,
|
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
|
// Configure log level, if isDevelopment is set, default to debug
|
||||||
if logLevel := os.Getenv("NOVAMD_LOG_LEVEL"); logLevel != "" {
|
if logLevel := os.Getenv("NOVAMD_LOG_LEVEL"); logLevel != "" {
|
||||||
if parsed, err := logging.ParseLogLevel(logLevel); err == nil {
|
parsed := logging.ParseLogLevel(logLevel)
|
||||||
config.LogLevel = parsed
|
config.LogLevel = parsed
|
||||||
}
|
|
||||||
} else if config.IsDevelopment {
|
} else if config.IsDevelopment {
|
||||||
config.LogLevel = logging.DEBUG
|
config.LogLevel = logging.DEBUG
|
||||||
}
|
} else {
|
||||||
|
config.LogLevel = logging.INFO
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate all settings
|
// Validate all settings
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ type Options struct {
|
|||||||
Config *Config
|
Config *Config
|
||||||
Database db.Database
|
Database db.Database
|
||||||
Storage storage.Manager
|
Storage storage.Manager
|
||||||
Logger logging.Logger
|
|
||||||
JWTManager auth.JWTManager
|
JWTManager auth.JWTManager
|
||||||
SessionManager auth.SessionManager
|
SessionManager auth.SessionManager
|
||||||
CookieService auth.CookieManager
|
CookieService auth.CookieManager
|
||||||
@@ -36,10 +35,7 @@ func DefaultOptions(cfg *Config) (*Options, error) {
|
|||||||
storageManager := storage.NewService(cfg.WorkDir)
|
storageManager := storage.NewService(cfg.WorkDir)
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
logger, err := logging.New(cfg.LogDir, cfg.LogLevel, cfg.ConsoleOutput)
|
logging.Setup(cfg.LogLevel)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize auth services
|
// Initialize auth services
|
||||||
jwtManager, sessionService, cookieService, err := initAuth(cfg, database)
|
jwtManager, sessionService, cookieService, err := initAuth(cfg, database)
|
||||||
@@ -56,7 +52,6 @@ func DefaultOptions(cfg *Config) (*Options, error) {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
Database: database,
|
Database: database,
|
||||||
Storage: storageManager,
|
Storage: storageManager,
|
||||||
Logger: logger,
|
|
||||||
JWTManager: jwtManager,
|
JWTManager: jwtManager,
|
||||||
SessionManager: sessionService,
|
SessionManager: sessionService,
|
||||||
CookieService: cookieService,
|
CookieService: cookieService,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"novamd/internal/logging"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
router *chi.Mux
|
router *chi.Mux
|
||||||
options *Options
|
options *Options
|
||||||
logger *slog.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new server instance with the given options
|
// NewServer creates a new server instance with the given options
|
||||||
@@ -19,7 +18,6 @@ func NewServer(options *Options) *Server {
|
|||||||
return &Server{
|
return &Server{
|
||||||
router: setupRouter(*options),
|
router: setupRouter(*options),
|
||||||
options: options,
|
options: options,
|
||||||
logger: options.Logger.App(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +25,7 @@ func NewServer(options *Options) *Server {
|
|||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
// Start server
|
// Start server
|
||||||
addr := ":" + s.options.Config.Port
|
addr := ":" + s.options.Config.Port
|
||||||
s.logger.Info("Starting server", "address", addr)
|
logging.Info("Starting server", "address", addr)
|
||||||
return http.ListenAndServe(addr, s.router)
|
return http.ListenAndServe(addr, s.router)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"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
|
type LogLevel slog.Level
|
||||||
|
|
||||||
// Log levels
|
// Log levels
|
||||||
@@ -22,208 +20,55 @@ const (
|
|||||||
ERROR LogLevel = LogLevel(slog.LevelError)
|
ERROR LogLevel = LogLevel(slog.LevelError)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger defines the interface for logging operations
|
// Setup initializes the logger with the given minimum log level
|
||||||
type Logger interface {
|
func Setup(minLevel LogLevel) {
|
||||||
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
|
|
||||||
opts := &slog.HandlerOptions{
|
opts := &slog.HandlerOptions{
|
||||||
Level: slog.Level(minLevel),
|
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
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create loggers for each type
|
Logger = slog.New(slog.NewTextHandler(os.Stdout, opts))
|
||||||
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)
|
|
||||||
|
|
||||||
// Prepare outputs
|
// ParseLogLevel converts a string to a LogLevel
|
||||||
outputs := []Output{{Type: OutputTypeJSON, Writer: file}}
|
func ParseLogLevel(level string) LogLevel {
|
||||||
if consoleOut {
|
switch level {
|
||||||
outputs = append(outputs, Output{Type: OutputTypeText, Writer: os.Stdout})
|
case "debug":
|
||||||
}
|
return DEBUG
|
||||||
|
case "warn":
|
||||||
// Create and set logger
|
return WARN
|
||||||
handler := createLogger(opts, outputs)
|
case "error":
|
||||||
lt.setLogger(slog.New(handler))
|
return ERROR
|
||||||
|
default:
|
||||||
|
return INFO
|
||||||
}
|
}
|
||||||
|
|
||||||
return l, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) App() *slog.Logger {
|
// Debug logs a debug message
|
||||||
return l.appLogger
|
func Debug(msg string, args ...any) {
|
||||||
|
Logger.Debug(msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) Audit() *slog.Logger {
|
// Info logs an info message
|
||||||
return l.auditLogger
|
func Info(msg string, args ...any) {
|
||||||
|
Logger.Info(msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) Security() *slog.Logger {
|
// Warn logs a warning message
|
||||||
return l.securityLogger
|
func Warn(msg string, args ...any) {
|
||||||
|
Logger.Warn(msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) Close() error {
|
// Error logs an error message
|
||||||
var lastErr error
|
func Error(msg string, args ...any) {
|
||||||
for _, file := range l.files {
|
Logger.Error(msg, args...)
|
||||||
if file != nil {
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lastErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// multiHandler implements slog.Handler for multiple outputs
|
// WithGroup adds a group to the logger context
|
||||||
type multiHandler []slog.Handler
|
func WithGroup(name string) *slog.Logger {
|
||||||
|
return Logger.WithGroup(name)
|
||||||
func (h multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
||||||
for _, handler := range h {
|
|
||||||
if handler.Enabled(ctx, level) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
// With adds key-value pairs to the logger context
|
||||||
for _, handler := range h {
|
func With(args ...any) *slog.Logger {
|
||||||
if err := handler.Handle(ctx, r); err != nil {
|
return Logger.With(args...)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user