117 lines
2.5 KiB
Go
117 lines
2.5 KiB
Go
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)
|
|
}
|