Files
seanime-docker/seanime-2.9.10/internal/util/logger.go
2025-09-20 14:08:38 +01:00

179 lines
4.0 KiB
Go

package util
import (
"bytes"
"fmt"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog"
)
const (
colorBlack = iota + 30
colorRed
colorGreen
colorYellow
colorBlue
colorMagenta
colorCyan
colorWhite
colorBold = 1
colorDarkGray = 90
unknownLevel = "???"
)
// Stores logs from all loggers. Used to write logs to a file when WriteGlobalLogBufferToFile is called.
// It is reset after writing to a file.
var logBuffer bytes.Buffer
var logBufferMutex = &sync.Mutex{}
func NewLogger() *zerolog.Logger {
timeFormat := fmt.Sprintf("%s", time.DateTime)
fieldsOrder := []string{"method", "status", "error", "uri", "latency_human"}
fieldsExclude := []string{"host", "latency", "referer", "remote_ip", "user_agent", "bytes_in", "bytes_out", "file"}
// Set up logger
consoleOutput := zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: timeFormat,
FormatLevel: ZerologFormatLevelPretty,
FormatMessage: ZerologFormatMessagePretty,
FieldsExclude: fieldsExclude,
FieldsOrder: fieldsOrder,
}
fileOutput := zerolog.ConsoleWriter{
Out: &logBuffer,
TimeFormat: timeFormat,
FormatMessage: ZerologFormatMessageSimple,
FormatLevel: ZerologFormatLevelSimple,
NoColor: true, // Needed to prevent color codes from being written to the file
FieldsExclude: fieldsExclude,
FieldsOrder: fieldsOrder,
}
multi := zerolog.MultiLevelWriter(consoleOutput, fileOutput)
logger := zerolog.New(multi).With().Timestamp().Logger()
return &logger
}
func WriteGlobalLogBufferToFile(file *os.File) {
defer HandlePanicInModuleThen("util/WriteGlobalLogBufferToFile", func() {})
if file == nil {
return
}
logBufferMutex.Lock()
defer logBufferMutex.Unlock()
if _, err := logBuffer.WriteTo(file); err != nil {
fmt.Print("Failed to write log buffer to file")
}
logBuffer.Reset()
}
func SetupLoggerSignalHandling(file *os.File) {
if file == nil {
return
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigChan
log.Trace().Msgf("Received signal: %s", sig)
// Flush log buffer to the log file when the app exits
WriteGlobalLogBufferToFile(file)
_ = file.Close()
os.Exit(0)
}()
}
func ZerologFormatMessagePretty(i interface{}) string {
if msg, ok := i.(string); ok {
if bytes.ContainsRune([]byte(msg), ':') {
parts := strings.SplitN(msg, ":", 2)
if len(parts) > 1 {
return colorizeb(parts[0], colorCyan) + colorizeb(" >", colorDarkGray) + parts[1]
}
}
return msg
}
return ""
}
func ZerologFormatMessageSimple(i interface{}) string {
if msg, ok := i.(string); ok {
if bytes.ContainsRune([]byte(msg), ':') {
parts := strings.SplitN(msg, ":", 2)
if len(parts) > 1 {
return parts[0] + " >" + parts[1]
}
}
return msg
}
return ""
}
func ZerologFormatLevelPretty(i interface{}) string {
if ll, ok := i.(string); ok {
s := strings.ToLower(ll)
switch s {
case "debug":
s = "DBG" + colorizeb(" -", colorDarkGray)
case "info":
s = fmt.Sprint(colorizeb("INF", colorBold)) + colorizeb(" -", colorDarkGray)
case "warn":
s = colorizeb("WRN", colorYellow) + colorizeb(" -", colorDarkGray)
case "trace":
s = colorizeb("TRC", colorDarkGray) + colorizeb(" -", colorDarkGray)
case "error":
s = colorizeb("ERR", colorRed) + colorizeb(" -", colorDarkGray)
case "fatal":
s = colorizeb("FTL", colorRed) + colorizeb(" -", colorDarkGray)
case "panic":
s = colorizeb("PNC", colorRed) + colorizeb(" -", colorDarkGray)
}
return fmt.Sprint(s)
}
return ""
}
func ZerologFormatLevelSimple(i interface{}) string {
if ll, ok := i.(string); ok {
s := strings.ToLower(ll)
switch s {
case "debug":
s = "|DBG|"
case "info":
s = "|INF|"
case "warn":
s = "|WRN|"
case "trace":
s = "|TRC|"
case "error":
s = "|ERR|"
case "fatal":
s = "|FTL|"
case "panic":
s = "|PNC|"
}
return fmt.Sprint(s)
}
return ""
}
func colorizeb(s interface{}, c int) string {
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s)
}