Skip to content

Commit d920b78

Browse files
authored
refactor logger to zerolog (#1239)
* refactor logger to zerolog * modify dingtalk and discord logger * fix for lint * fix for review * fix for file leak * fix for review
1 parent 9222351 commit d920b78

File tree

7 files changed

+219
-80
lines changed

7 files changed

+219
-80
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
2222
github.com/openai/openai-go/v3 v3.22.0
2323
github.com/rivo/tview v0.42.0
24+
github.com/rs/zerolog v1.34.0
2425
github.com/slack-go/slack v0.17.3
2526
github.com/spf13/cobra v1.10.2
2627
github.com/stretchr/testify v1.11.1
@@ -50,7 +51,6 @@ require (
5051
github.com/pmezard/go-difflib v1.0.0 // indirect
5152
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
5253
github.com/rivo/uniseg v0.4.7 // indirect
53-
github.com/rs/zerolog v1.34.0 // indirect
5454
github.com/segmentio/asm v1.1.3 // indirect
5555
github.com/segmentio/encoding v0.5.3 // indirect
5656
github.com/spf13/pflag v1.0.10 // indirect

pkg/channels/dingtalk/dingtalk.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot"
1212
"github.com/open-dingtalk/dingtalk-stream-sdk-go/client"
13+
dinglog "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger"
1314

1415
"github.com/sipeed/picoclaw/pkg/bus"
1516
"github.com/sipeed/picoclaw/pkg/channels"
@@ -39,6 +40,9 @@ func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) (
3940
return nil, fmt.Errorf("dingtalk client_id and client_secret are required")
4041
}
4142

43+
// Set the logger for the Stream SDK
44+
dinglog.SetLogger(logger.NewLogger("dingtalk"))
45+
4246
base := channels.NewBaseChannel("dingtalk", cfg, messageBus, cfg.AllowFrom,
4347
channels.WithMaxMessageLength(20000),
4448
channels.WithGroupTrigger(cfg.GroupTrigger),

pkg/channels/discord/discord.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ type DiscordChannel struct {
4545
}
4646

4747
func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
48+
discordgo.Logger = logger.NewLogger("discord").
49+
WithLevels(map[int]logger.LogLevel{
50+
discordgo.LogError: logger.ERROR,
51+
discordgo.LogWarning: logger.WARN,
52+
discordgo.LogInformational: logger.INFO,
53+
discordgo.LogDebug: logger.DEBUG,
54+
}).Log
55+
4856
session, err := discordgo.New("Bot " + cfg.Token)
4957
if err != nil {
5058
return nil, fmt.Errorf("failed to create discord session: %w", err)

pkg/channels/qq/qq.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (c *QQChannel) Start(ctx context.Context) error {
7878
return fmt.Errorf("QQ app_id and app_secret not configured")
7979
}
8080

81+
botgo.SetLogger(logger.NewLogger("botgo"))
8182
logger.InfoC("qq", "Starting QQ bot (WebSocket mode)")
8283

8384
// Reinitialize shutdown signal for clean restart.

pkg/channels/telegram/telegram.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
7777
if baseURL := strings.TrimRight(strings.TrimSpace(telegramCfg.BaseURL), "/"); baseURL != "" {
7878
opts = append(opts, telego.WithAPIServer(baseURL))
7979
}
80+
opts = append(opts, telego.WithLogger(logger.NewLogger("telego")))
8081

8182
bot, err := telego.NewBot(telegramCfg.Token, opts...)
8283
if err != nil {

pkg/logger/logger.go

Lines changed: 109 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package logger
22

33
import (
4-
"encoding/json"
54
"fmt"
6-
"log"
75
"os"
6+
"path/filepath"
87
"runtime"
98
"strings"
109
"sync"
11-
"time"
10+
11+
"github.com/rs/zerolog"
1212
)
1313

14-
type LogLevel int
14+
type LogLevel = zerolog.Level
1515

1616
const (
17-
DEBUG LogLevel = iota
18-
INFO
19-
WARN
20-
ERROR
21-
FATAL
17+
DEBUG = zerolog.DebugLevel
18+
INFO = zerolog.InfoLevel
19+
WARN = zerolog.WarnLevel
20+
ERROR = zerolog.ErrorLevel
21+
FATAL = zerolog.FatalLevel
2222
)
2323

2424
var (
@@ -31,34 +31,32 @@ var (
3131
}
3232

3333
currentLevel = INFO
34-
logger *Logger
34+
logger zerolog.Logger
35+
fileLogger zerolog.Logger
36+
logFile *os.File
3537
once sync.Once
3638
mu sync.RWMutex
3739
)
3840

39-
type Logger struct {
40-
file *os.File
41-
}
42-
43-
type LogEntry struct {
44-
Level string `json:"level"`
45-
Timestamp string `json:"timestamp"`
46-
Component string `json:"component,omitempty"`
47-
Message string `json:"message"`
48-
Fields map[string]any `json:"fields,omitempty"`
49-
Caller string `json:"caller,omitempty"`
50-
}
51-
5241
func init() {
5342
once.Do(func() {
54-
logger = &Logger{}
43+
zerolog.SetGlobalLevel(zerolog.InfoLevel)
44+
45+
consoleWriter := zerolog.ConsoleWriter{
46+
Out: os.Stdout,
47+
TimeFormat: "15:04:05", // TODO: make it configurable???
48+
}
49+
50+
logger = zerolog.New(consoleWriter).With().Timestamp().Logger()
51+
fileLogger = zerolog.Logger{}
5552
})
5653
}
5754

5855
func SetLevel(level LogLevel) {
5956
mu.Lock()
6057
defer mu.Unlock()
6158
currentLevel = level
59+
zerolog.SetGlobalLevel(level)
6260
}
6361

6462
func GetLevel() LogLevel {
@@ -71,93 +69,121 @@ func EnableFileLogging(filePath string) error {
7169
mu.Lock()
7270
defer mu.Unlock()
7371

74-
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
72+
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
73+
return fmt.Errorf("failed to create log directory: %w", err)
74+
}
75+
76+
newFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
7577
if err != nil {
7678
return fmt.Errorf("failed to open log file: %w", err)
7779
}
7880

79-
if logger.file != nil {
80-
logger.file.Close()
81+
// Close old file if exists
82+
if logFile != nil {
83+
logFile.Close()
8184
}
8285

83-
logger.file = file
84-
log.Println("File logging enabled:", filePath)
86+
logFile = newFile
87+
fileLogger = zerolog.New(logFile).With().Timestamp().Caller().Logger()
8588
return nil
8689
}
8790

8891
func DisableFileLogging() {
8992
mu.Lock()
9093
defer mu.Unlock()
9194

92-
if logger.file != nil {
93-
logger.file.Close()
94-
logger.file = nil
95-
log.Println("File logging disabled")
95+
if logFile != nil {
96+
logFile.Close()
97+
logFile = nil
9698
}
99+
fileLogger = zerolog.Logger{}
97100
}
98101

99-
func logMessage(level LogLevel, component string, message string, fields map[string]any) {
100-
if level < currentLevel {
101-
return
102-
}
103-
104-
entry := LogEntry{
105-
Level: logLevelNames[level],
106-
Timestamp: time.Now().UTC().Format(time.RFC3339),
107-
Component: component,
108-
Message: message,
109-
Fields: fields,
110-
}
102+
func getCallerInfo() (string, int, string) {
103+
for i := 2; i < 15; i++ {
104+
pc, file, line, ok := runtime.Caller(i)
105+
if !ok {
106+
continue
107+
}
111108

112-
if pc, file, line, ok := runtime.Caller(2); ok {
113109
fn := runtime.FuncForPC(pc)
114-
if fn != nil {
115-
entry.Caller = fmt.Sprintf("%s:%d (%s)", file, line, fn.Name())
110+
if fn == nil {
111+
continue
116112
}
117-
}
118113

119-
if logger.file != nil {
120-
jsonData, err := json.Marshal(entry)
121-
if err == nil {
122-
logger.file.Write(append(jsonData, '\n'))
114+
// bypass common loggers
115+
if strings.HasSuffix(file, "/logger.go") ||
116+
strings.HasSuffix(file, "/log.go") {
117+
continue
123118
}
124-
}
125119

126-
var fieldStr string
127-
if len(fields) > 0 {
128-
fieldStr = " " + formatFields(fields)
129-
} else {
130-
fieldStr = ""
131-
}
120+
funcName := fn.Name()
121+
if strings.HasPrefix(funcName, "runtime.") {
122+
continue
123+
}
132124

133-
logLine := fmt.Sprintf("[%s] [%s]%s %s%s",
134-
entry.Timestamp,
135-
logLevelNames[level],
136-
formatComponent(component),
137-
message,
138-
fieldStr,
139-
)
125+
return filepath.Base(file), line, filepath.Base(funcName)
126+
}
140127

141-
log.Println(logLine)
128+
return "???", 0, "???"
129+
}
142130

143-
if level == FATAL {
144-
os.Exit(1)
131+
//nolint:zerologlint
132+
func getEvent(logger zerolog.Logger, level LogLevel) *zerolog.Event {
133+
switch level {
134+
case zerolog.DebugLevel:
135+
return logger.Debug()
136+
case zerolog.InfoLevel:
137+
return logger.Info()
138+
case zerolog.WarnLevel:
139+
return logger.Warn()
140+
case zerolog.ErrorLevel:
141+
return logger.Error()
142+
case zerolog.FatalLevel:
143+
return logger.Fatal()
144+
default:
145+
return logger.Info()
145146
}
146147
}
147148

148-
func formatComponent(component string) string {
149-
if component == "" {
150-
return ""
149+
func logMessage(level LogLevel, component string, message string, fields map[string]any) {
150+
if level < currentLevel {
151+
return
152+
}
153+
154+
callerFile, callerLine, callerFunc := getCallerInfo()
155+
156+
event := getEvent(logger, level)
157+
158+
// Build combined field with component and caller
159+
if component != "" {
160+
event.Str("caller", fmt.Sprintf("%-6s %s:%d (%s)", component, callerFile, callerLine, callerFunc))
161+
} else {
162+
event.Str("caller", fmt.Sprintf("<none> %s:%d (%s)", callerFile, callerLine, callerFunc))
151163
}
152-
return fmt.Sprintf(" %s:", component)
153-
}
154164

155-
func formatFields(fields map[string]any) string {
156-
parts := make([]string, 0, len(fields))
157165
for k, v := range fields {
158-
parts = append(parts, fmt.Sprintf("%s=%v", k, v))
166+
event.Interface(k, v)
167+
}
168+
169+
event.Msg(message)
170+
171+
// Also log to file if enabled
172+
if fileLogger.GetLevel() != zerolog.NoLevel {
173+
fileEvent := getEvent(fileLogger, level)
174+
175+
if component != "" {
176+
fileEvent.Str("component", component)
177+
}
178+
for k, v := range fields {
179+
fileEvent.Interface(k, v)
180+
}
181+
fileEvent.Msg(message)
182+
}
183+
184+
if level == FATAL {
185+
os.Exit(1)
159186
}
160-
return fmt.Sprintf("{%s}", strings.Join(parts, ", "))
161187
}
162188

163189
func Debug(message string) {
@@ -232,6 +258,10 @@ func FatalC(component string, message string) {
232258
logMessage(FATAL, component, message, nil)
233259
}
234260

261+
func Fatalf(message string, ss ...any) {
262+
logMessage(FATAL, "", fmt.Sprintf(message, ss...), nil)
263+
}
264+
235265
func FatalF(message string, fields map[string]any) {
236266
logMessage(FATAL, "", message, fields)
237267
}

0 commit comments

Comments
 (0)