refactor logger to zerolog
This commit is contained in:
parent
81dfdf5f45
commit
3e754dab0e
3 changed files with 211 additions and 81 deletions
|
|
@ -50,6 +50,7 @@ func (c *QQChannel) Start(ctx context.Context) error {
|
||||||
return fmt.Errorf("QQ app_id and app_secret not configured")
|
return fmt.Errorf("QQ app_id and app_secret not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botgo.SetLogger(logger.NewLogger("botgo"))
|
||||||
logger.InfoC("qq", "Starting QQ bot (WebSocket mode)")
|
logger.InfoC("qq", "Starting QQ bot (WebSocket mode)")
|
||||||
|
|
||||||
// create token source
|
// create token source
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
|
||||||
if baseURL := strings.TrimRight(strings.TrimSpace(telegramCfg.BaseURL), "/"); baseURL != "" {
|
if baseURL := strings.TrimRight(strings.TrimSpace(telegramCfg.BaseURL), "/"); baseURL != "" {
|
||||||
opts = append(opts, telego.WithAPIServer(baseURL))
|
opts = append(opts, telego.WithAPIServer(baseURL))
|
||||||
}
|
}
|
||||||
|
opts = append(opts, telego.WithLogger(logger.NewLogger("telego")))
|
||||||
|
|
||||||
bot, err := telego.NewBot(telegramCfg.Token, opts...)
|
bot, err := telego.NewBot(telegramCfg.Token, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogLevel int
|
type LogLevel = zerolog.Level
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEBUG LogLevel = iota
|
DEBUG = zerolog.DebugLevel
|
||||||
INFO
|
INFO = zerolog.InfoLevel
|
||||||
WARN
|
WARN = zerolog.WarnLevel
|
||||||
ERROR
|
ERROR = zerolog.ErrorLevel
|
||||||
FATAL
|
FATAL = zerolog.FatalLevel
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -31,27 +31,46 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLevel = INFO
|
currentLevel = INFO
|
||||||
logger *Logger
|
logger zerolog.Logger
|
||||||
|
fileLogger zerolog.Logger
|
||||||
once sync.Once
|
once sync.Once
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
file *os.File
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogEntry struct {
|
|
||||||
Level string `json:"level"`
|
|
||||||
Timestamp string `json:"timestamp"`
|
|
||||||
Component string `json:"component,omitempty"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Fields map[string]any `json:"fields,omitempty"`
|
|
||||||
Caller string `json:"caller,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
logger = &Logger{}
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
consoleWriter := zerolog.ConsoleWriter{
|
||||||
|
Out: os.Stdout,
|
||||||
|
TimeFormat: "15:04:05",
|
||||||
|
NoColor: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleWriter.FormatLevel = func(i interface{}) string {
|
||||||
|
level, ok := i.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("| %-5s |", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToUpper(level) {
|
||||||
|
case "DEBUG":
|
||||||
|
return "| \x1b[36mDBG\x1b[0m |"
|
||||||
|
case "INFO":
|
||||||
|
return "| \x1b[32mINF\x1b[0m |"
|
||||||
|
case "WARN":
|
||||||
|
return "| \x1b[33mWRN\x1b[0m |"
|
||||||
|
case "ERROR":
|
||||||
|
return "| \x1b[31mERR\x1b[0m |"
|
||||||
|
case "FATAL":
|
||||||
|
return "| \x1b[35mFTL\x1b[0m |"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("| %-5s |", level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = zerolog.New(consoleWriter).With().Timestamp().Logger()
|
||||||
|
fileLogger = zerolog.Logger{}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +78,7 @@ func SetLevel(level LogLevel) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
currentLevel = level
|
currentLevel = level
|
||||||
|
zerolog.SetGlobalLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLevel() LogLevel {
|
func GetLevel() LogLevel {
|
||||||
|
|
@ -71,29 +91,53 @@ func EnableFileLogging(filePath string) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create log directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open log file: %w", err)
|
return fmt.Errorf("failed to open log file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if logger.file != nil {
|
fileLogger = zerolog.New(file).With().Timestamp().Caller().Logger()
|
||||||
logger.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.file = file
|
|
||||||
log.Println("File logging enabled:", filePath)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableFileLogging() {
|
func DisableFileLogging() {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
fileLogger = zerolog.Logger{}
|
||||||
|
}
|
||||||
|
|
||||||
if logger.file != nil {
|
func getCallerInfo() (string, int, string) {
|
||||||
logger.file.Close()
|
for i := 2; i < 15; i++ {
|
||||||
logger.file = nil
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
log.Println("File logging disabled")
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// bypass common loggers
|
||||||
|
if strings.HasSuffix(file, "/logger.go") ||
|
||||||
|
strings.HasSuffix(file, "/log.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := fn.Name()
|
||||||
|
if strings.HasPrefix(funcName, "runtime.") {
|
||||||
|
fmt.Println("===", funcName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Base(file), line, filepath.Base(funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "???", 0, "???"
|
||||||
}
|
}
|
||||||
|
|
||||||
func logMessage(level LogLevel, component string, message string, fields map[string]any) {
|
func logMessage(level LogLevel, component string, message string, fields map[string]any) {
|
||||||
|
|
@ -101,65 +145,70 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := LogEntry{
|
callerFile, callerLine, callerFunc := getCallerInfo()
|
||||||
Level: logLevelNames[level],
|
|
||||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
var event *zerolog.Event
|
||||||
Component: component,
|
switch level {
|
||||||
Message: message,
|
case zerolog.DebugLevel:
|
||||||
Fields: fields,
|
event = logger.Debug()
|
||||||
|
case zerolog.InfoLevel:
|
||||||
|
event = logger.Info()
|
||||||
|
case zerolog.WarnLevel:
|
||||||
|
event = logger.Warn()
|
||||||
|
case zerolog.ErrorLevel:
|
||||||
|
event = logger.Error()
|
||||||
|
case zerolog.FatalLevel:
|
||||||
|
event = logger.Error()
|
||||||
|
default:
|
||||||
|
event = logger.Info()
|
||||||
}
|
}
|
||||||
|
|
||||||
if pc, file, line, ok := runtime.Caller(2); ok {
|
// Build combined field with component and caller
|
||||||
fn := runtime.FuncForPC(pc)
|
if component != "" {
|
||||||
if fn != nil {
|
//event.Str("component", component)
|
||||||
entry.Caller = fmt.Sprintf("%s:%d (%s)", file, line, fn.Name())
|
event.Str("caller", fmt.Sprintf("%-6s | %s:%d (%s)", component, callerFile, callerLine, callerFunc))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if logger.file != nil {
|
|
||||||
jsonData, err := json.Marshal(entry)
|
|
||||||
if err == nil {
|
|
||||||
logger.file.Write(append(jsonData, '\n'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldStr string
|
|
||||||
if len(fields) > 0 {
|
|
||||||
fieldStr = " " + formatFields(fields)
|
|
||||||
} else {
|
} else {
|
||||||
fieldStr = ""
|
event.Str("caller", fmt.Sprintf("<none> | %s:%d (%s)", callerFile, callerLine, callerFunc))
|
||||||
}
|
}
|
||||||
|
|
||||||
logLine := fmt.Sprintf("[%s] [%s]%s %s%s",
|
for k, v := range fields {
|
||||||
entry.Timestamp,
|
event.Interface(k, v)
|
||||||
logLevelNames[level],
|
}
|
||||||
formatComponent(component),
|
|
||||||
message,
|
|
||||||
fieldStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Println(logLine)
|
event.Msg(message)
|
||||||
|
|
||||||
|
// Also log to file if enabled
|
||||||
|
if fileLogger.GetLevel() != zerolog.NoLevel {
|
||||||
|
var fileEvent *zerolog.Event
|
||||||
|
switch level {
|
||||||
|
case zerolog.DebugLevel:
|
||||||
|
fileEvent = fileLogger.Debug()
|
||||||
|
case zerolog.InfoLevel:
|
||||||
|
fileEvent = fileLogger.Info()
|
||||||
|
case zerolog.WarnLevel:
|
||||||
|
fileEvent = fileLogger.Warn()
|
||||||
|
case zerolog.ErrorLevel:
|
||||||
|
fileEvent = fileLogger.Error()
|
||||||
|
case zerolog.FatalLevel:
|
||||||
|
fileEvent = fileLogger.Error()
|
||||||
|
default:
|
||||||
|
fileEvent = fileLogger.Info()
|
||||||
|
}
|
||||||
|
|
||||||
|
if component != "" {
|
||||||
|
fileEvent.Str("component", component)
|
||||||
|
}
|
||||||
|
for k, v := range fields {
|
||||||
|
fileEvent.Interface(k, v)
|
||||||
|
}
|
||||||
|
fileEvent.Msg(message)
|
||||||
|
}
|
||||||
|
|
||||||
if level == FATAL {
|
if level == FATAL {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatComponent(component string) string {
|
|
||||||
if component == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(" %s:", component)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatFields(fields map[string]any) string {
|
|
||||||
parts := make([]string, 0, len(fields))
|
|
||||||
for k, v := range fields {
|
|
||||||
parts = append(parts, fmt.Sprintf("%s=%v", k, v))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("{%s}", strings.Join(parts, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug(message string) {
|
func Debug(message string) {
|
||||||
logMessage(DEBUG, "", message, nil)
|
logMessage(DEBUG, "", message, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +281,10 @@ func FatalC(component string, message string) {
|
||||||
logMessage(FATAL, component, message, nil)
|
logMessage(FATAL, component, message, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Fatalf(message string, ss ...any) {
|
||||||
|
logMessage(FATAL, "", fmt.Sprintf(message, ss...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
func FatalF(message string, fields map[string]any) {
|
func FatalF(message string, fields map[string]any) {
|
||||||
logMessage(FATAL, "", message, fields)
|
logMessage(FATAL, "", message, fields)
|
||||||
}
|
}
|
||||||
|
|
@ -239,3 +292,78 @@ func FatalF(message string, fields map[string]any) {
|
||||||
func FatalCF(component string, message string, fields map[string]any) {
|
func FatalCF(component string, message string, fields map[string]any) {
|
||||||
logMessage(FATAL, component, message, fields)
|
logMessage(FATAL, component, message, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger implements common Logger interface
|
||||||
|
type Logger struct {
|
||||||
|
component string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs debug messages
|
||||||
|
func (b *Logger) Debug(v ...interface{}) {
|
||||||
|
logMessage(DEBUG, b.component, fmt.Sprint(v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs info messages
|
||||||
|
func (b *Logger) Info(v ...interface{}) {
|
||||||
|
logMessage(INFO, b.component, fmt.Sprint(v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs warning messages
|
||||||
|
func (b *Logger) Warn(v ...interface{}) {
|
||||||
|
logMessage(WARN, b.component, fmt.Sprint(v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs error messages
|
||||||
|
func (b *Logger) Error(v ...interface{}) {
|
||||||
|
logMessage(ERROR, b.component, fmt.Sprint(v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs formatted debug messages
|
||||||
|
func (b *Logger) Debugf(format string, v ...any) {
|
||||||
|
//debugCallerInfo()
|
||||||
|
logMessage(DEBUG, b.component, fmt.Sprintf(format, v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs formatted info messages
|
||||||
|
func (b *Logger) Infof(format string, v ...any) {
|
||||||
|
//debugCallerInfo()
|
||||||
|
logMessage(INFO, b.component, fmt.Sprintf(format, v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs formatted warning messages
|
||||||
|
func (b *Logger) Warnf(format string, v ...any) {
|
||||||
|
logMessage(WARN, b.component, fmt.Sprintf(format, v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs formatted error messages
|
||||||
|
func (b *Logger) Errorf(format string, v ...any) {
|
||||||
|
//debugCallerInfo()
|
||||||
|
logMessage(ERROR, b.component, fmt.Sprintf(format, v...), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync flushes log buffer (no-op for this implementation)
|
||||||
|
func (b *Logger) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogger creates a new logger instance with optional component name
|
||||||
|
func NewLogger(component string) *Logger {
|
||||||
|
return &Logger{component: component}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debugging logger only
|
||||||
|
func debugCallerInfo() {
|
||||||
|
for i := 2; i < 15; i++ {
|
||||||
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(file, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue