feat: implement initial GoDump backup service with management web UI and MySQL support
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
INFO LogLevel = "INFO"
|
||||
WARN LogLevel = "WARN"
|
||||
ERROR LogLevel = "ERROR"
|
||||
)
|
||||
|
||||
type LogEntry struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Level LogLevel `json:"level"`
|
||||
Instance string `json:"instance,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var (
|
||||
ringBuffer *ring.Ring
|
||||
bufferMu sync.Mutex
|
||||
fileLogger *log.Logger
|
||||
stdLogger *log.Logger
|
||||
)
|
||||
|
||||
func Init(logFile string) error {
|
||||
ringBuffer = ring.New(100)
|
||||
|
||||
stdLogger = log.New(os.Stdout, "", 0)
|
||||
|
||||
if logFile != "" {
|
||||
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileLogger = log.New(f, "", 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func logMessage(level LogLevel, instance, format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf(format, v...)
|
||||
entry := LogEntry{
|
||||
Timestamp: time.Now(),
|
||||
Level: level,
|
||||
Instance: instance,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
logLine := fmt.Sprintf("[%s] [%s]", entry.Timestamp.Format(time.RFC3339), entry.Level)
|
||||
if instance != "" {
|
||||
logLine += fmt.Sprintf(" [%s]", instance)
|
||||
}
|
||||
logLine += " " + entry.Message
|
||||
|
||||
// Log to stdout
|
||||
if stdLogger != nil {
|
||||
stdLogger.Println(logLine)
|
||||
}
|
||||
|
||||
// Log to file
|
||||
if fileLogger != nil {
|
||||
fileLogger.Println(logLine)
|
||||
}
|
||||
|
||||
// Log to ring buffer
|
||||
bufferMu.Lock()
|
||||
defer bufferMu.Unlock()
|
||||
ringBuffer.Value = entry
|
||||
ringBuffer = ringBuffer.Next()
|
||||
}
|
||||
|
||||
func Info(instance, format string, v ...interface{}) {
|
||||
logMessage(INFO, instance, format, v...)
|
||||
}
|
||||
|
||||
func Warn(instance, format string, v ...interface{}) {
|
||||
logMessage(WARN, instance, format, v...)
|
||||
}
|
||||
|
||||
func Error(instance, format string, v ...interface{}) {
|
||||
logMessage(ERROR, instance, format, v...)
|
||||
}
|
||||
|
||||
func GetRecentLogs() []LogEntry {
|
||||
bufferMu.Lock()
|
||||
defer bufferMu.Unlock()
|
||||
|
||||
var logs []LogEntry
|
||||
ringBuffer.Do(func(p interface{}) {
|
||||
if p != nil {
|
||||
logs = append(logs, p.(LogEntry))
|
||||
}
|
||||
})
|
||||
|
||||
// Do() iterates forward from the current pointer. In a ring buffer where we just added elements,
|
||||
// the oldest element is at the current pointer, and the newest element is the one just before it.
|
||||
// So logs will be ordered from oldest to newest. We want to return newest to oldest or keep it chronological?
|
||||
// The requirement is "last 100 log lines in a scrollable monospace box". Chronological (oldest to newest) is standard for tailing logs.
|
||||
return logs
|
||||
}
|
||||
|
||||
func GetRecentLogsJSON() ([]byte, error) {
|
||||
logs := GetRecentLogs()
|
||||
return json.Marshal(logs)
|
||||
}
|
||||
Reference in New Issue
Block a user