Simplify logging

This commit is contained in:
2024-12-14 23:59:28 +01:00
parent 1ee8d94789
commit 71df436a93
4 changed files with 46 additions and 224 deletions

View File

@@ -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...)
}