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")
|
||||
}
|
||||
|
||||
botgo.SetLogger(logger.NewLogger("botgo"))
|
||||
logger.InfoC("qq", "Starting QQ bot (WebSocket mode)")
|
||||
|
||||
// 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 != "" {
|
||||
opts = append(opts, telego.WithAPIServer(baseURL))
|
||||
}
|
||||
opts = append(opts, telego.WithLogger(logger.NewLogger("telego")))
|
||||
|
||||
bot, err := telego.NewBot(telegramCfg.Token, opts...)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
type LogLevel = zerolog.Level
|
||||
|
||||
const (
|
||||
DEBUG LogLevel = iota
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
FATAL
|
||||
DEBUG = zerolog.DebugLevel
|
||||
INFO = zerolog.InfoLevel
|
||||
WARN = zerolog.WarnLevel
|
||||
ERROR = zerolog.ErrorLevel
|
||||
FATAL = zerolog.FatalLevel
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -31,27 +31,46 @@ var (
|
|||
}
|
||||
|
||||
currentLevel = INFO
|
||||
logger *Logger
|
||||
logger zerolog.Logger
|
||||
fileLogger zerolog.Logger
|
||||
once sync.Once
|
||||
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() {
|
||||
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()
|
||||
defer mu.Unlock()
|
||||
currentLevel = level
|
||||
zerolog.SetGlobalLevel(level)
|
||||
}
|
||||
|
||||
func GetLevel() LogLevel {
|
||||
|
|
@ -71,29 +91,53 @@ func EnableFileLogging(filePath string) error {
|
|||
mu.Lock()
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
|
||||
if logger.file != nil {
|
||||
logger.file.Close()
|
||||
}
|
||||
|
||||
logger.file = file
|
||||
log.Println("File logging enabled:", filePath)
|
||||
fileLogger = zerolog.New(file).With().Timestamp().Caller().Logger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisableFileLogging() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
fileLogger = zerolog.Logger{}
|
||||
}
|
||||
|
||||
if logger.file != nil {
|
||||
logger.file.Close()
|
||||
logger.file = nil
|
||||
log.Println("File logging disabled")
|
||||
func getCallerInfo() (string, int, string) {
|
||||
for i := 2; i < 15; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
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) {
|
||||
|
|
@ -101,65 +145,70 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
|||
return
|
||||
}
|
||||
|
||||
entry := LogEntry{
|
||||
Level: logLevelNames[level],
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Component: component,
|
||||
Message: message,
|
||||
Fields: fields,
|
||||
callerFile, callerLine, callerFunc := getCallerInfo()
|
||||
|
||||
var event *zerolog.Event
|
||||
switch level {
|
||||
case zerolog.DebugLevel:
|
||||
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 {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn != nil {
|
||||
entry.Caller = fmt.Sprintf("%s:%d (%s)", file, line, fn.Name())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// Build combined field with component and caller
|
||||
if component != "" {
|
||||
//event.Str("component", component)
|
||||
event.Str("caller", fmt.Sprintf("%-6s | %s:%d (%s)", component, callerFile, callerLine, callerFunc))
|
||||
} else {
|
||||
fieldStr = ""
|
||||
event.Str("caller", fmt.Sprintf("<none> | %s:%d (%s)", callerFile, callerLine, callerFunc))
|
||||
}
|
||||
|
||||
logLine := fmt.Sprintf("[%s] [%s]%s %s%s",
|
||||
entry.Timestamp,
|
||||
logLevelNames[level],
|
||||
formatComponent(component),
|
||||
message,
|
||||
fieldStr,
|
||||
)
|
||||
for k, v := range fields {
|
||||
event.Interface(k, v)
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
logMessage(DEBUG, "", message, nil)
|
||||
}
|
||||
|
|
@ -232,6 +281,10 @@ func FatalC(component string, message string) {
|
|||
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) {
|
||||
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) {
|
||||
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