From 985bb7d5d3595afe7c14a7512fcb1949b17fe041 Mon Sep 17 00:00:00 2001 From: eleksir Date: Fri, 31 Oct 2025 18:49:25 +0300 Subject: [PATCH] [WIP] Replace logrus with slog --- config.go | 3 +- internal/log/log.go | 176 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 52 +++++-------- msg-parsers.go | 3 +- settings-db-util.go | 3 +- telegram.go | 3 +- util.go | 8 +- 7 files changed, 206 insertions(+), 42 deletions(-) create mode 100644 internal/log/log.go diff --git a/config.go b/config.go index c3f986c..ce26676 100644 --- a/config.go +++ b/config.go @@ -5,8 +5,9 @@ import ( "fmt" "os" + "aleesa-telegram-go/internal/log" + "github.com/hjson/hjson-go" - log "github.com/sirupsen/logrus" ) // parseConfig разбирает и валидирует даденный конфиг. diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000..f7b120e --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,176 @@ +package log + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "log/slog" + "os" + "time" +) + +// Handler exposes slog handler struct. +var Handler slog.Handler + +// Ctx log context. For now it is just sits here and do nothing. +var Ctx = context.Background() + +// Writer go-writer eported for capturing other types of logs, even for std log go facility. +var Writer io.Writer + +// reader is required for Writer to work properly. It is connected via io.Pipe() to Writer. +var reader io.Reader + +// Init setup logger stuff. +// level can be error, warn, info, debug if something other supplied info level selected. +// fileDescriptor should be opened before supplying it to Init(). +func Init(level string, fileDescriptor *os.File) { + var loglevel slog.Level + + // no panic, no trace. + switch level { + case "debug": + loglevel = slog.LevelDebug + + case "info": + loglevel = slog.LevelInfo + + case "warn": + loglevel = slog.LevelWarn + + case "error": + loglevel = slog.LevelError + + default: + loglevel = slog.LevelInfo + } + + opts := &slog.HandlerOptions{ //nolint: exhaustruct + // Use the ReplaceAttr function on the handler options + // to be able to replace any single attribute in the log output + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive + // check that we are handling the time key + if a.Key != slog.TimeKey { + return a + } + + t := a.Value.Time() + + // change the value from a time.Time to a String + // where the string has the correct time format. + a.Value = slog.StringValue(t.Format(time.DateTime)) + + return a + }, + + Level: loglevel, + } + + Handler = slog.NewTextHandler(fileDescriptor, opts) + + slog.SetDefault( + slog.New(Handler), + ) + + reader, Writer = io.Pipe() + + go readIoPipe() +} + +// readIoPipe reads pipe and writes it to slog. +func readIoPipe() { + scanner := bufio.NewScanner(reader) + + for { + for scanner.Scan() { + line := scanner.Text() // Get the current line as a string + slog.Debug(line) + } + + if err := scanner.Err(); err != nil { + // io.EOF is expected when done reading, e.g. on exit. + if errors.Is(err, io.EOF) { + return + } + + // something weird happen, we should it log to guess later what it was. + Errorf("Error during scanning log writer pipe: %v\n", err) + } + } +} + +// GetLevel returns current loglevel. +func GetLevel() string { + switch { + case Handler.Enabled(Ctx, slog.LevelDebug): + return "debug" + + case Handler.Enabled(Ctx, slog.LevelInfo): + return "info" + + case Handler.Enabled(Ctx, slog.LevelWarn): + return "warn" + + case Handler.Enabled(Ctx, slog.LevelError): + return "error" + + default: + return "unknown" + } +} + +// Fatal passes message directly to slog.Error() and exits with error code 1. +func Fatal(message string) { + slog.Error(message) + os.Exit(1) +} + +// Fatalf logs message on error log level and exits with error code 1. It allows to supply arguments in printf() style . +func Fatalf(format string, a ...any) { + slog.Error(fmt.Sprintf(format, a...)) + os.Exit(1) +} + +// Error passes message directly to slog.Error(). +func Error(message string) { + slog.Error(message) +} + +// Errorf logs message on error log level and allows to supply arguments in printf() style. +func Errorf(format string, a ...any) { + slog.Error(fmt.Sprintf(format, a...)) +} + +// Warn passes message directly to slog.Warn(). +func Warn(message string) { + slog.Warn(message) +} + +// Warnf logs message on warn log level and allows to supply arguments in printf() style. +func Warnf(format string, a ...any) { + slog.Warn(fmt.Sprintf(format, a...)) +} + +// Info passes message directly to slog.Info(). +func Info(message string) { + slog.Info(message) +} + +// Infof logs message on info log level and allows to supply arguments in printf() style. +func Infof(format string, a ...any) { + slog.Info(fmt.Sprintf(format, a...)) +} + +// Debug passes message directly to slog.Debug(). +func Debug(message string) { + slog.Debug(message) +} + +// Debugf logs message on debug log level and allows to supply arguments in printf() style. +func Debugf(format string, a ...any) { + slog.Debug(fmt.Sprintf(format, a...)) +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/main.go b/main.go index ae03b50..40dc316 100644 --- a/main.go +++ b/main.go @@ -8,43 +8,21 @@ import ( "path/filepath" "syscall" + "aleesa-telegram-go/internal/log" + "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" ) // init производит некоторую инициализацию перед запуском main(). func init() { - log.SetFormatter(&log.TextFormatter{ - DisableQuote: true, - DisableLevelTruncation: false, - DisableColors: true, - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", - }) - - // no panic, no trace - switch config.Loglevel { - case "fatal": - log.SetLevel(log.FatalLevel) - case "error": - log.SetLevel(log.ErrorLevel) - case "warn": - log.SetLevel(log.WarnLevel) - case "info": - log.SetLevel(log.InfoLevel) - case "debug": - log.SetLevel(log.DebugLevel) - default: - log.SetLevel(log.InfoLevel) - } - - var err error + var ( + err error + ) executablePath, err := os.Executable() if err != nil { - log.Errorf("Unable to get current executable path: %s", err) - os.Exit(1) + log.Fatalf("Unable to get current executable path: %s", err) } configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) @@ -72,20 +50,26 @@ func init() { } func main() { - // Main context - var ctx = context.Background() + var ( + logfile *os.File + err error + ) // Откроем лог и скормим его логгеру. if config.Log != "" { - logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + logfile, err = os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - log.Fatalf("Unable to open log file %s: %s", config.Log, err) + log.Errorf("Unable to open log file %s: %s", config.Log, err) + os.Exit(1) } - - log.SetOutput(logfile) } + log.Init(config.Loglevel, logfile) + + // Main context + var ctx = context.Background() + // Инициализируем redis-клиента. redisClient = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", config.Redis.Server, config.Redis.Port), diff --git a/msg-parsers.go b/msg-parsers.go index 882f70a..c5e087a 100644 --- a/msg-parsers.go +++ b/msg-parsers.go @@ -8,9 +8,10 @@ import ( "strconv" "strings" + "aleesa-telegram-go/internal/log" + "github.com/NicoNex/echotron/v3" "github.com/davecgh/go-spew/spew" - log "github.com/sirupsen/logrus" ) // telego msg parser diff --git a/settings-db-util.go b/settings-db-util.go index 6f427de..0156ab6 100644 --- a/settings-db-util.go +++ b/settings-db-util.go @@ -6,9 +6,10 @@ import ( "fmt" "io/fs" + "aleesa-telegram-go/internal/log" + "github.com/cockroachdb/errors/oserror" "github.com/cockroachdb/pebble" - log "github.com/sirupsen/logrus" ) // StoreKV сохраняет в указанной бд ключ и значение. diff --git a/telegram.go b/telegram.go index 25ddfc9..1c7ef18 100644 --- a/telegram.go +++ b/telegram.go @@ -3,8 +3,9 @@ package main import ( "fmt" + "aleesa-telegram-go/internal/log" + "github.com/NicoNex/echotron/v3" - log "github.com/sirupsen/logrus" ) // telega основная горутинка, реализующая бота. diff --git a/util.go b/util.go index 8ac7eb3..397aa9e 100644 --- a/util.go +++ b/util.go @@ -4,7 +4,7 @@ import ( "os" "syscall" - log "github.com/sirupsen/logrus" + "aleesa-telegram-go/internal/log" ) // sigHandler хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения. @@ -15,11 +15,11 @@ func sigHandler() { var s = <-sigChan switch s { case syscall.SIGINT: - log.Infoln("Got SIGINT, quitting") + log.Info("Got SIGINT, quitting") case syscall.SIGTERM: - log.Infoln("Got SIGTERM, quitting") + log.Info("Got SIGTERM, quitting") case syscall.SIGQUIT: - log.Infoln("Got SIGQUIT, quitting") + log.Info("Got SIGQUIT, quitting") // Заходим на новую итерацию, если у нас "неинтересный" сигнал. default: