@@ -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