Logging file work
This commit is contained in:
parent
506070edcc
commit
dcf431c807
30
config.yml
30
config.yml
@ -1,3 +1,7 @@
|
||||
logging:
|
||||
log_level: "debug" # Can be debug, info, warn, error, or fatal
|
||||
log_file: "/var/log/backea/app.log" # Path to log file (optional)
|
||||
|
||||
defaults:
|
||||
retention:
|
||||
keep_latest: 10
|
||||
@ -31,28 +35,4 @@ services:
|
||||
type: "kopia"
|
||||
provider: "local"
|
||||
destination:
|
||||
path: "/local/backup/path"
|
||||
2:
|
||||
backup_strategy:
|
||||
type: "kopia"
|
||||
provider: "b2"
|
||||
# Credentials are in .env
|
||||
3:
|
||||
backup_strategy:
|
||||
<<: *sftp_strategy # This uses the common sftp strategy defined above
|
||||
|
||||
# Example of another service
|
||||
another_service:
|
||||
source:
|
||||
host: "local"
|
||||
path: "/path/to/another/service"
|
||||
hooks:
|
||||
before_hook: "systemctl stop service-name"
|
||||
after_hook: "systemctl start service-name"
|
||||
backup_configs:
|
||||
1:
|
||||
backup_strategy:
|
||||
type: "kopia"
|
||||
provider: "local"
|
||||
destination:
|
||||
path: "/local/backup/path"
|
||||
path: "/tmp"
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
var (
|
||||
// Global logger instance
|
||||
logger *Logger
|
||||
once sync.Once
|
||||
mu sync.Mutex // Mutex to protect logger initialization and replacement
|
||||
)
|
||||
|
||||
// LogLevel represents the severity of a log message
|
||||
@ -28,59 +28,104 @@ const (
|
||||
FATAL
|
||||
)
|
||||
|
||||
// String returns the string representation of the log level
|
||||
func (l LogLevel) String() string {
|
||||
switch l {
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case WARN:
|
||||
return "WARN"
|
||||
case ERROR:
|
||||
return "ERROR"
|
||||
case FATAL:
|
||||
return "FATAL"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// Logger provides logging functionality
|
||||
type Logger struct {
|
||||
logLevel LogLevel
|
||||
output io.Writer
|
||||
stdLog *log.Logger
|
||||
errLog *log.Logger
|
||||
file *os.File // Store file reference for closing
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Singleton
|
||||
func InitLogger(level LogLevel, logFile string) {
|
||||
once.Do(func() {
|
||||
var output io.Writer = os.Stdout
|
||||
// InitLogger initializes or reinitializes the global logger instance
|
||||
func InitLogger(level LogLevel, logFilePath string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if logFile != "" {
|
||||
logDir := filepath.Dir(logFile)
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create log directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Close the existing logger if there is one
|
||||
if logger != nil {
|
||||
logger.Close()
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to open log file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Initializing logger with level %s and log file: %s\n", level.String(), logFilePath)
|
||||
|
||||
output = io.MultiWriter(os.Stdout, file)
|
||||
var output io.Writer = os.Stdout
|
||||
var fileHandle *os.File
|
||||
|
||||
// Set up log file if specified
|
||||
if logFilePath != "" {
|
||||
logDir := filepath.Dir(logFilePath)
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create log directory %s: %v\n", logDir, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger = &Logger{
|
||||
logLevel: level,
|
||||
output: output,
|
||||
stdLog: log.New(output, "", log.LstdFlags),
|
||||
errLog: log.New(output, "ERROR: ", log.LstdFlags),
|
||||
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to open log file %s: %v\n", logFilePath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Printf("Successfully opened log file: %s\n", logFilePath)
|
||||
fileHandle = file
|
||||
output = io.MultiWriter(os.Stdout, file)
|
||||
}
|
||||
|
||||
logger = &Logger{
|
||||
logLevel: level,
|
||||
output: output,
|
||||
stdLog: log.New(output, "", log.LstdFlags),
|
||||
errLog: log.New(output, "ERROR: ", log.LstdFlags),
|
||||
file: fileHandle,
|
||||
}
|
||||
|
||||
// Log initialization message through the logger itself
|
||||
logger.Info("Logger initialized with level %s and log file %s", level.String(), logFilePath)
|
||||
}
|
||||
|
||||
// Singleton
|
||||
// GetLogger returns the global logger instance, initializing it if necessary
|
||||
func GetLogger() *Logger {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if logger == nil {
|
||||
// Initialize with default settings if not already initialized
|
||||
// We need to unlock before calling InitLogger since it will lock mu
|
||||
mu.Unlock()
|
||||
InitLogger(INFO, "")
|
||||
mu.Lock()
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// SetLogLevel changes the current log level
|
||||
func (l *Logger) SetLogLevel(level LogLevel) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.logLevel = level
|
||||
l.Info("Log level set to %s", level.String())
|
||||
}
|
||||
|
||||
// log is the internal logging function
|
||||
func (l *Logger) log(level LogLevel, format string, v ...interface{}) {
|
||||
if level < l.logLevel {
|
||||
return
|
||||
@ -89,20 +134,22 @@ func (l *Logger) log(level LogLevel, format string, v ...interface{}) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// Get caller information
|
||||
// Get caller information (skip 2 levels to get the actual calling function)
|
||||
_, file, line, ok := runtime.Caller(2)
|
||||
if !ok {
|
||||
file = "unknown"
|
||||
line = 0
|
||||
}
|
||||
|
||||
// Format message with source location
|
||||
message := fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, fmt.Sprintf(format, v...))
|
||||
// Format message with source location and level
|
||||
prefix := fmt.Sprintf("[%s] %s:%d: ", level.String(), filepath.Base(file), line)
|
||||
message := fmt.Sprintf(format, v...)
|
||||
fullMessage := prefix + message
|
||||
|
||||
if level >= ERROR {
|
||||
l.errLog.Println(message)
|
||||
l.errLog.Println(fullMessage)
|
||||
} else {
|
||||
l.stdLog.Println(message)
|
||||
l.stdLog.Println(fullMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +176,7 @@ func (l *Logger) Error(format string, v ...interface{}) {
|
||||
// Fatal logs a fatal message and exits the program
|
||||
func (l *Logger) Fatal(format string, v ...interface{}) {
|
||||
l.log(FATAL, format, v...)
|
||||
l.Close() // Ensure logs are flushed before exit
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -139,9 +187,30 @@ func (l *Logger) ErrorWithStack(err error, format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(format, v...)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
buf := make([]byte, 4096) // Larger buffer for more stack trace info
|
||||
n := runtime.Stack(buf, false)
|
||||
|
||||
l.Error("%s: %v\n%s", message, err, buf[:n])
|
||||
}
|
||||
|
||||
// Close flushes and closes the log file if one is being used
|
||||
func (l *Logger) Close() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if l.file != nil {
|
||||
l.file.Sync() // Flush any buffered data
|
||||
l.file.Close() // Close the file
|
||||
l.file = nil // Clear the reference
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown properly closes the logger
|
||||
func Shutdown() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if logger != nil {
|
||||
logger.Close()
|
||||
logger = nil
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user