Files
dbbackup/internal/logger/logger.go
Renz 9b3c3f2b1b Initial commit: Database Backup Tool v1.1.0
- PostgreSQL and MySQL support
- Interactive TUI with fixed menu navigation
- Line-by-line progress display
- CPU-aware parallel processing
- Cross-platform build support
- Configuration settings menu
- Silent mode for TUI operations
2025-10-22 19:27:38 +00:00

185 lines
4.2 KiB
Go

package logger
import (
"fmt"
"io"
"log/slog"
"os"
"strings"
"time"
)
// Logger defines the interface for logging
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
Time(msg string, args ...any)
// Progress logging for operations
StartOperation(name string) OperationLogger
}
// OperationLogger tracks timing for operations
type OperationLogger interface {
Update(msg string, args ...any)
Complete(msg string, args ...any)
Fail(msg string, args ...any)
}
// logger implements Logger interface using slog
type logger struct {
slog *slog.Logger
level slog.Level
format string
}
// operationLogger tracks a single operation
type operationLogger struct {
name string
startTime time.Time
parent *logger
}
// New creates a new logger
func New(level, format string) Logger {
var slogLevel slog.Level
switch strings.ToLower(level) {
case "debug":
slogLevel = slog.LevelDebug
case "info":
slogLevel = slog.LevelInfo
case "warn", "warning":
slogLevel = slog.LevelWarn
case "error":
slogLevel = slog.LevelError
default:
slogLevel = slog.LevelInfo
}
var handler slog.Handler
opts := &slog.HandlerOptions{
Level: slogLevel,
}
switch strings.ToLower(format) {
case "json":
handler = slog.NewJSONHandler(os.Stdout, opts)
default:
handler = slog.NewTextHandler(os.Stdout, opts)
}
return &logger{
slog: slog.New(handler),
level: slogLevel,
format: format,
}
}
func (l *logger) Debug(msg string, args ...any) {
l.slog.Debug(msg, args...)
}
func (l *logger) Info(msg string, args ...any) {
l.slog.Info(msg, args...)
}
func (l *logger) Warn(msg string, args ...any) {
l.slog.Warn(msg, args...)
}
func (l *logger) Error(msg string, args ...any) {
l.slog.Error(msg, args...)
}
func (l *logger) Time(msg string, args ...any) {
// Time logs are always at info level with special formatting
l.slog.Info("[TIME] "+msg, args...)
}
func (l *logger) StartOperation(name string) OperationLogger {
return &operationLogger{
name: name,
startTime: time.Now(),
parent: l,
}
}
func (ol *operationLogger) Update(msg string, args ...any) {
elapsed := time.Since(ol.startTime)
ol.parent.Info(fmt.Sprintf("[%s] %s", ol.name, msg),
append(args, "elapsed", elapsed.String())...)
}
func (ol *operationLogger) Complete(msg string, args ...any) {
elapsed := time.Since(ol.startTime)
ol.parent.Info(fmt.Sprintf("[%s] COMPLETED: %s", ol.name, msg),
append(args, "duration", formatDuration(elapsed))...)
}
func (ol *operationLogger) Fail(msg string, args ...any) {
elapsed := time.Since(ol.startTime)
ol.parent.Error(fmt.Sprintf("[%s] FAILED: %s", ol.name, msg),
append(args, "duration", formatDuration(elapsed))...)
}
// formatDuration formats duration in human-readable format
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
} else if d < time.Hour {
minutes := int(d.Minutes())
seconds := int(d.Seconds()) % 60
return fmt.Sprintf("%dm %ds", minutes, seconds)
} else {
hours := int(d.Hours())
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
}
}
// FileLogger creates a logger that writes to both stdout and a file
func FileLogger(level, format, filename string) (Logger, error) {
var slogLevel slog.Level
switch strings.ToLower(level) {
case "debug":
slogLevel = slog.LevelDebug
case "info":
slogLevel = slog.LevelInfo
case "warn", "warning":
slogLevel = slog.LevelWarn
case "error":
slogLevel = slog.LevelError
default:
slogLevel = slog.LevelInfo
}
// Open log file
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err)
}
// Create multi-writer (stdout + file)
multiWriter := io.MultiWriter(os.Stdout, file)
var handler slog.Handler
opts := &slog.HandlerOptions{
Level: slogLevel,
}
switch strings.ToLower(format) {
case "json":
handler = slog.NewJSONHandler(multiWriter, opts)
default:
handler = slog.NewTextHandler(multiWriter, opts)
}
return &logger{
slog: slog.New(handler),
level: slogLevel,
format: format,
}, nil
}