Механики логгирования вынесены в отдельный модуль
This commit is contained in:
@@ -2,12 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"aleesa-misc-go/internal/log"
|
||||||
"aleesa-misc-go/internal/misc"
|
"aleesa-misc-go/internal/misc"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
@@ -15,71 +15,24 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
loglevel slog.Level
|
logfile *os.File
|
||||||
)
|
)
|
||||||
|
|
||||||
misc.ReadConfig()
|
misc.ReadConfig()
|
||||||
|
|
||||||
loghandler := os.Stderr
|
|
||||||
|
|
||||||
if misc.Config.Log != "" {
|
if misc.Config.Log != "" {
|
||||||
loghandler, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
logfile, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err))
|
log.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logfile = os.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
// no panic, no trace.
|
log.Init(misc.Config.Loglevel, logfile)
|
||||||
switch misc.Config.Loglevel {
|
|
||||||
case "error":
|
|
||||||
loglevel = slog.LevelError
|
|
||||||
|
|
||||||
case "warn":
|
|
||||||
loglevel = slog.LevelWarn
|
|
||||||
|
|
||||||
case "info":
|
|
||||||
loglevel = slog.LevelInfo
|
|
||||||
|
|
||||||
case "debug":
|
|
||||||
loglevel = slog.LevelDebug
|
|
||||||
|
|
||||||
default:
|
|
||||||
loglevel = slog.LevelInfo
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &slog.HandlerOptions{
|
|
||||||
// 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 {
|
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.SetDefault(
|
|
||||||
slog.New(
|
|
||||||
slog.NewTextHandler(
|
|
||||||
loghandler,
|
|
||||||
opts,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Иницализируем клиента Редиски.
|
// Иницализируем клиента Редиски.
|
||||||
misc.RedisClient = redis.NewClient(&redis.Options{
|
misc.RedisClient = redis.NewClient(&redis.Options{
|
||||||
@@ -100,7 +53,7 @@ func main() {
|
|||||||
// Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений).
|
// Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений).
|
||||||
ch := misc.Subscriber.Channel()
|
ch := misc.Subscriber.Channel()
|
||||||
|
|
||||||
slog.Info("Aleesa-misc-go started")
|
log.Info("Aleesa-misc-go started")
|
||||||
|
|
||||||
for msg := range ch {
|
for msg := range ch {
|
||||||
if !misc.Shutdown {
|
if !misc.Shutdown {
|
||||||
|
|||||||
106
internal/log/log.go
Normal file
106
internal/log/log.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 "error":
|
||||||
|
loglevel = slog.LevelError
|
||||||
|
|
||||||
|
case "warn":
|
||||||
|
loglevel = slog.LevelWarn
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
|
||||||
|
case "debug":
|
||||||
|
loglevel = slog.LevelDebug
|
||||||
|
|
||||||
|
default:
|
||||||
|
loglevel = slog.LevelInfo
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &slog.HandlerOptions{
|
||||||
|
// 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 {
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetDefault(
|
||||||
|
slog.New(
|
||||||
|
slog.NewTextHandler(
|
||||||
|
fileDescriptor,
|
||||||
|
opts,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: */
|
||||||
@@ -3,9 +3,9 @@ package misc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"aleesa-misc-go/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
|
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
|
||||||
@@ -15,29 +15,29 @@ func MsgParser(ctx context.Context, msg string) {
|
|||||||
j rMsg
|
j rMsg
|
||||||
)
|
)
|
||||||
|
|
||||||
slog.Debug(fmt.Sprintf("Incomming raw json: %s", msg))
|
log.Debugf("Incomming raw json: %s", msg)
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Unable to to parse message from redis channel: %s", err))
|
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate our j.
|
// Validate our j.
|
||||||
if exist := j.From; exist == "" {
|
if exist := j.From; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no from field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no from field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Chatid; exist == "" {
|
if exist := j.Chatid; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no chatid field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Userid; exist == "" {
|
if exist := j.Userid; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no userid field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -45,19 +45,19 @@ func MsgParser(ctx context.Context, msg string) {
|
|||||||
// j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально.
|
// j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально.
|
||||||
|
|
||||||
if exist := j.Message; exist == "" {
|
if exist := j.Message; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no message field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no message field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Plugin; exist == "" {
|
if exist := j.Plugin; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no plugin field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Mode; exist == "" {
|
if exist := j.Mode; exist == "" {
|
||||||
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no mode field: %s", msg))
|
log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func MsgParser(ctx context.Context, msg string) {
|
|||||||
|
|
||||||
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
|
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
|
||||||
if j.Misc.Fwdcnt > Config.ForwardsMax {
|
if j.Misc.Fwdcnt > Config.ForwardsMax {
|
||||||
slog.Warn(fmt.Sprintf("Discarding msg with fwd_cnt exceeding max value: %s", msg))
|
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -222,16 +222,16 @@ func MsgParser(ctx context.Context, msg string) {
|
|||||||
data, err := json.Marshal(message)
|
data, err := json.Marshal(message)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Unable to to serialize message for redis: %s", err))
|
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Заталкиваем наш json в редиску.
|
// Заталкиваем наш json в редиску.
|
||||||
if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil {
|
if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Unable to send data to redis channel %s: %s", sendTo, err))
|
log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err)
|
||||||
} else {
|
} else {
|
||||||
slog.Debug(fmt.Sprintf("Send msg to redis channel %s: %s", sendTo, string(data)))
|
log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package misc
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"aleesa-misc-go/internal/log"
|
||||||
|
|
||||||
"github.com/hjson/hjson-go"
|
"github.com/hjson/hjson-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ func ReadConfig() {
|
|||||||
executablePath, err := os.Executable()
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(fmt.Sprintf("Unable to get current executable path: %s", err))
|
log.Errorf("Unable to get current executable path: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||||
@@ -39,7 +40,7 @@ func ReadConfig() {
|
|||||||
|
|
||||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||||
if fileInfo.Size() > 65535 {
|
if fileInfo.Size() > 65535 {
|
||||||
slog.Warn(fmt.Sprintf("Config file %s is too long for config, skipping", location))
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ func ReadConfig() {
|
|||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Skip reading config file %s: %s", location, err))
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ func ReadConfig() {
|
|||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err))
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -74,13 +75,13 @@ func ReadConfig() {
|
|||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
// Не удалось преобразовать map-ку в json
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err))
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||||
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err))
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,7 @@ func ReadConfig() {
|
|||||||
// sampleConfig.Log = "" if not set
|
// sampleConfig.Log = "" if not set
|
||||||
|
|
||||||
if sampleConfig.Channel == "" {
|
if sampleConfig.Channel == "" {
|
||||||
slog.Error(fmt.Sprintf("Channel field in config file %s must be set", location))
|
log.Errorf("Channel field in config file %s must be set", location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения
|
// Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения
|
||||||
@@ -130,7 +131,7 @@ func ReadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Csign == "" {
|
if sampleConfig.Csign == "" {
|
||||||
slog.Error(fmt.Sprintf("Csign field in config file %s must be set", location))
|
log.Errorf("Csign field in config file %s must be set", location)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.ForwardsMax == 0 {
|
if sampleConfig.ForwardsMax == 0 {
|
||||||
@@ -140,13 +141,13 @@ func ReadConfig() {
|
|||||||
Config = sampleConfig
|
Config = sampleConfig
|
||||||
configLoaded = true
|
configLoaded = true
|
||||||
|
|
||||||
slog.Info(fmt.Sprintf("Using %s as config file", location))
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !configLoaded {
|
if !configLoaded {
|
||||||
slog.Error("Config was not loaded! Refusing to start.")
|
log.Error("Config was not loaded! Refusing to start.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"aleesa-misc-go/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения.
|
// SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения.
|
||||||
func SigHandler() {
|
func SigHandler() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
slog.Info("Install signal handler")
|
log.Info("Install signal handler")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var s = <-SigChan
|
var s = <-SigChan
|
||||||
switch s {
|
switch s {
|
||||||
case syscall.SIGINT:
|
case syscall.SIGINT:
|
||||||
slog.Info("Got SIGINT, quitting")
|
log.Info("Got SIGINT, quitting")
|
||||||
case syscall.SIGTERM:
|
case syscall.SIGTERM:
|
||||||
slog.Info("Got SIGTERM, quitting")
|
log.Info("Got SIGTERM, quitting")
|
||||||
case syscall.SIGQUIT:
|
case syscall.SIGQUIT:
|
||||||
slog.Info("Got SIGQUIT, quitting")
|
log.Info("Got SIGQUIT, quitting")
|
||||||
|
|
||||||
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
|
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
|
||||||
default:
|
default:
|
||||||
@@ -33,11 +33,11 @@ func SigHandler() {
|
|||||||
|
|
||||||
// Отпишемся от всех каналов и закроем коннект к редиске.
|
// Отпишемся от всех каналов и закроем коннект к редиске.
|
||||||
if err = Subscriber.Unsubscribe(Ctx); err != nil {
|
if err = Subscriber.Unsubscribe(Ctx); err != nil {
|
||||||
slog.Error(fmt.Sprintf("Unable to unsubscribe from redis channels cleanly: %s", err))
|
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = Subscriber.Close(); err != nil {
|
if err = Subscriber.Close(); err != nil {
|
||||||
slog.Error(fmt.Sprintf("Unable to close redis connection cleanly: %s", err))
|
log.Errorf("Unable to close redis connection cleanly: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user