Skip to content

Commit 5b781f0

Browse files
author
mirkobrombin
committed
feat: replace logrus with zerolog
plus general code improvements, fixed bad conditions, improved readability
1 parent 32a3be3 commit 5b781f0

File tree

21 files changed

+470
-270
lines changed

21 files changed

+470
-270
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/armon/go-radix v1.0.0
77
github.com/quic-go/quic-go v0.48.2
88
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592
9+
github.com/rs/zerolog v1.33.0
910
github.com/sirupsen/logrus v1.9.3
1011
github.com/spf13/cobra v1.8.1
1112
github.com/valyala/fasthttp v1.58.0
@@ -21,6 +22,8 @@ require (
2122
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2223
github.com/klauspost/compress v1.17.11 // indirect
2324
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
25+
github.com/mattn/go-colorable v0.1.13 // indirect
26+
github.com/mattn/go-isatty v0.0.19 // indirect
2427
github.com/mattn/go-runewidth v0.0.15 // indirect
2528
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
2629
github.com/quic-go/qpack v0.5.1 // indirect

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
55
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
66
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
77
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
8+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
89
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
910
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1011
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -18,6 +19,7 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
1819
github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM=
1920
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
2021
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
22+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
2123
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
2224
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
2325
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -34,12 +36,18 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX
3436
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
3537
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
3638
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
39+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
40+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
41+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
42+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
43+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
3744
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
3845
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
3946
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
4047
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
4148
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
4249
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
50+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
4351
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4452
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4553
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
@@ -52,6 +60,9 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
5260
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
5361
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
5462
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
63+
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
64+
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
65+
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
5566
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5667
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
5768
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -116,7 +127,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
116127
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117128
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
118129
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
130+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
119131
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
132+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
133+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
120134
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
121135
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
122136
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

internal/logger/logger.go

Lines changed: 161 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,82 @@ import (
88
"time"
99

1010
"github.com/mirkobrombin/goup/internal/config"
11-
log "github.com/sirupsen/logrus"
11+
"github.com/rs/zerolog"
1212
)
1313

14-
// FieldHook is a custom Logrus hook that adds predefined fields to every log entry.
15-
type FieldHook struct {
16-
Fields log.Fields
14+
// Fields is a map of string keys to arbitrary values, emulating logrus.Fields
15+
// for compatibility with existing code.
16+
type Fields map[string]interface{}
17+
18+
// Logger wraps a zerolog.Logger while exposing methods similar to logrus.
19+
type Logger struct {
20+
base zerolog.Logger
21+
out io.Writer
1722
}
1823

19-
// Levels defines the log levels where the hook is applied.
20-
func (hook *FieldHook) Levels() []log.Level {
21-
return log.AllLevels
24+
// SetOutput changes the output writer (stdout, file, etc.).
25+
func (l *Logger) SetOutput(w io.Writer) {
26+
l.out = w
27+
l.base = l.base.Output(w)
2228
}
2329

24-
// Fire adds the predefined fields to the log entry.
25-
func (hook *FieldHook) Fire(entry *log.Entry) error {
26-
for k, v := range hook.Fields {
27-
entry.Data[k] = v
30+
// WithFields returns a new Logger that includes the provided fields.
31+
func (l *Logger) WithFields(fields Fields) *Logger {
32+
newBase := l.base.With().Fields(fields).Logger()
33+
return &Logger{
34+
base: newBase,
35+
out: l.out,
2836
}
29-
return nil
3037
}
3138

32-
// NewLogger creates a new logger with optional predefined fields.
33-
func NewLogger(identifier string, fields log.Fields) (*log.Logger, error) {
34-
logger := log.New()
39+
// Info logs a message at Info level.
40+
func (l *Logger) Info(msg string) {
41+
l.base.Info().Msg(msg)
42+
}
43+
44+
// Infof logs a formatted message at Info level.
45+
func (l *Logger) Infof(format string, args ...interface{}) {
46+
l.base.Info().Msgf(format, args...)
47+
}
48+
49+
// Error logs a message at Error level.
50+
func (l *Logger) Error(msg string) {
51+
l.base.Error().Msg(msg)
52+
}
53+
54+
// Errorf logs a formatted message at Error level.
55+
func (l *Logger) Errorf(format string, args ...interface{}) {
56+
l.base.Error().Msgf(format, args...)
57+
}
58+
59+
// Debug logs a message at Debug level (not heavily used by default).
60+
func (l *Logger) Debug(msg string) {
61+
l.base.Debug().Msg(msg)
62+
}
63+
64+
// Debugf logs a formatted message at Debug level.
65+
func (l *Logger) Debugf(format string, args ...interface{}) {
66+
l.base.Debug().Msgf(format, args...)
67+
}
68+
69+
// Warn logs a message at Warn level.
70+
func (l *Logger) Warn(msg string) {
71+
l.base.Warn().Msg(msg)
72+
}
3573

36-
// Standard GoUp log directory structure
37-
logDir := filepath.Join(config.GetLogDir(), identifier, fmt.Sprintf("%d", time.Now().Year()), fmt.Sprintf("%02d", time.Now().Month()))
74+
// Warnf logs a formatted message at Warn level.
75+
func (l *Logger) Warnf(format string, args ...interface{}) {
76+
l.base.Warn().Msgf(format, args...)
77+
}
78+
79+
// NewLogger creates a new Logger that writes both to stdout and a site-specific file.
80+
func NewLogger(identifier string, fields Fields) (*Logger, error) {
81+
logDir := filepath.Join(
82+
config.GetLogDir(),
83+
identifier,
84+
fmt.Sprintf("%d", time.Now().Year()),
85+
fmt.Sprintf("%02d", time.Now().Month()),
86+
)
3887
if err := os.MkdirAll(logDir, os.ModePerm); err != nil {
3988
return nil, err
4089
}
@@ -46,32 +95,31 @@ func NewLogger(identifier string, fields log.Fields) (*log.Logger, error) {
4695
return nil, err
4796
}
4897

49-
// Set output to both stdout and log file
50-
logger.SetOutput(io.MultiWriter(os.Stdout, file))
51-
logger.SetFormatter(&log.JSONFormatter{})
98+
mw := io.MultiWriter(os.Stdout, file)
99+
100+
// Zerolog logger with time + multiwriter
101+
base := zerolog.New(mw).With().Timestamp().Logger()
102+
103+
l := &Logger{
104+
base: base,
105+
out: mw,
106+
}
52107

53-
// Add the FieldHook if fields are provided
54108
if fields != nil {
55-
logger.AddHook(&FieldHook{Fields: fields})
109+
l = l.WithFields(fields)
56110
}
57111

58-
return logger, nil
112+
return l, nil
59113
}
60114

61-
// NewPluginLogger creates a plugin-specific log file in the same directory as
62-
// the main log.
63-
//
64-
// Note: the standard plugin logs must be placed in the site specific log, use
65-
// this function only for boring logs, e.g. 3rd party tools like Node.js, etc.
66-
//
67-
// FIXME: this function currently requires to implicitly import the logger
68-
// package, it should be refactored to avoid this and just be a function provided
69-
// by the plugin interface.
70-
func NewPluginLogger(siteDomain, pluginName string) (*log.Logger, error) {
71-
logger := log.New()
72-
73-
// Base directory for logs (same as the main site log)
74-
logDir := filepath.Join(config.GetLogDir(), siteDomain, fmt.Sprintf("%d", time.Now().Year()), fmt.Sprintf("%02d", time.Now().Month()))
115+
// NewPluginLogger creates a plugin-specific log file (no stdout).
116+
func NewPluginLogger(siteDomain, pluginName string) (*Logger, error) {
117+
logDir := filepath.Join(
118+
config.GetLogDir(),
119+
siteDomain,
120+
fmt.Sprintf("%d", time.Now().Year()),
121+
fmt.Sprintf("%02d", time.Now().Month()),
122+
)
75123
if err := os.MkdirAll(logDir, os.ModePerm); err != nil {
76124
return nil, err
77125
}
@@ -83,9 +131,81 @@ func NewPluginLogger(siteDomain, pluginName string) (*log.Logger, error) {
83131
return nil, err
84132
}
85133

86-
// Set output to log file only
87-
logger.SetOutput(file)
88-
logger.SetFormatter(&log.JSONFormatter{})
134+
// Only file output with timestamp
135+
base := zerolog.New(file).With().Timestamp().Logger()
136+
137+
l := &Logger{
138+
base: base,
139+
out: file,
140+
}
141+
return l, nil
142+
}
143+
144+
// Writer returns an io.WriteCloser that logs each written line.
145+
func (l *Logger) Writer() io.WriteCloser {
146+
pr, pw := io.Pipe()
147+
148+
go func() {
149+
defer pr.Close()
150+
buf := make([]byte, 1024)
151+
var tmp []byte
152+
153+
for {
154+
n, err := pr.Read(buf)
155+
if n > 0 {
156+
tmp = append(tmp, buf[:n]...)
157+
for {
158+
idx := indexOfNewline(tmp)
159+
if idx == -1 {
160+
break
161+
}
162+
line := tmp[:idx]
163+
line = trimCR(line)
164+
l.Info(string(line))
165+
tmp = tmp[idx+1:]
166+
}
167+
}
168+
if err != nil {
169+
// Exit on error or EOF
170+
break
171+
}
172+
}
173+
// Logging any remaining data
174+
if len(tmp) > 0 {
175+
l.Info(string(tmp))
176+
}
177+
}()
178+
179+
return &pipeWriteCloser{
180+
pipeWriter: pw,
181+
}
182+
}
183+
184+
// pipeWriteCloser implements Write and Close delegating to a PipeWriter.
185+
type pipeWriteCloser struct {
186+
pipeWriter *io.PipeWriter
187+
}
188+
189+
func (pwc *pipeWriteCloser) Write(data []byte) (int, error) {
190+
return pwc.pipeWriter.Write(data)
191+
}
192+
193+
func (pwc *pipeWriteCloser) Close() error {
194+
return pwc.pipeWriter.Close()
195+
}
89196

90-
return logger, nil
197+
func indexOfNewline(buf []byte) int {
198+
for i, b := range buf {
199+
if b == '\n' {
200+
return i
201+
}
202+
}
203+
return -1
204+
}
205+
206+
func trimCR(buf []byte) []byte {
207+
if len(buf) > 0 && buf[len(buf)-1] == '\r' {
208+
return buf[:len(buf)-1]
209+
}
210+
return buf
91211
}

0 commit comments

Comments
 (0)