Files
godoxy/internal/logging/logging.go
yusing 6da7227f9b refactor(errs): migrate from gperr.Error to standard Go error interface
This is a large-scale refactoring across the codebase that replaces the custom
`gperr.Error` type with Go's standard `error` interface. The changes include:

- Replacing `gperr.Error` return types with `error` in function signatures
- Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()`
- Using `%w` format verb for error wrapping instead of `.With()` method
- Replacing `gperr.Subject()` calls with `gperr.PrependSubject()`
- Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern
- Update NewLogger to handle multiline error message
- Updating `goutils` submodule to latest commit

This refactoring aligns with Go idioms and removes the dependency on
custom error handling abstractions in favor of standard library patterns.
2026-02-08 12:07:36 +08:00

135 lines
3.0 KiB
Go

package logging
import (
"io"
"log"
"os"
"strings"
"github.com/rs/zerolog"
"github.com/yusing/godoxy/internal/common"
"github.com/rs/zerolog/diode"
zerologlog "github.com/rs/zerolog/log"
)
func InitLogger(out ...io.Writer) {
logger = NewLogger(out...)
log.SetOutput(logger)
log.SetPrefix("")
log.SetFlags(0)
zerolog.TimeFieldFormat = timeFmt
zerologlog.Logger = logger
}
var (
logger zerolog.Logger
timeFmt string
level zerolog.Level
prefix string
)
func init() {
switch {
case common.IsTrace:
timeFmt = "04:05"
level = zerolog.TraceLevel
case common.IsDebug:
timeFmt = "01-02 15:04"
level = zerolog.DebugLevel
default:
timeFmt = "01-02 15:04"
level = zerolog.InfoLevel
}
prefixLength := len(timeFmt) + 5 // level takes 3 + 2 spaces
prefix = strings.Repeat(" ", prefixLength)
InitLogger(os.Stdout)
}
func fmtMessage(msg string) string {
nLines := strings.Count(msg, "\n")
if nLines == 0 {
return msg
}
var sb strings.Builder
sb.Grow(len(msg) + nLines*len(prefix))
// write first line unindented
idx := strings.IndexByte(msg, '\n')
sb.WriteString(msg[:idx])
sb.WriteByte('\n')
msg = msg[idx+1:]
// write remaining lines indented
for line := range strings.Lines(msg) {
sb.WriteString(prefix)
sb.WriteString(line)
}
return sb.String()
}
func diodeMultiWriter(out ...io.Writer) io.Writer {
return diode.NewWriter(multiWriter(out...), 1024, 0, func(missed int) {
zerologlog.Warn().Int("missed", missed).Msg("missed log messages")
})
}
func multiWriter(out ...io.Writer) io.Writer {
if len(out) == 0 {
return os.Stdout
}
if len(out) == 1 {
return out[0]
}
return io.MultiWriter(out...)
}
func NewLogger(out ...io.Writer) zerolog.Logger {
writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
if !common.IsTest {
w.Out = diodeMultiWriter(out...)
} else {
w.Out = multiWriter(out...)
}
w.TimeFormat = timeFmt
w.FormatPrepare = func(evt map[string]any) error {
// move error field to join message if it's multiline
if err, ok := evt[zerolog.ErrorFieldName].(string); ok {
if strings.Count(err, "\n") == 0 {
return nil
}
msg, ok := evt[zerolog.MessageFieldName].(string)
if ok && msg != "" {
evt[zerolog.MessageFieldName] = msg + "\n" + err
} else {
evt[zerolog.MessageFieldName] = err
}
delete(evt, zerolog.ErrorFieldName)
}
return nil
}
w.FormatMessage = func(msgI any) string { // pad spaces for each line
if msgI == nil {
return ""
}
return fmtMessage(msgI.(string))
}
})
return zerolog.New(writer).Level(level).With().Timestamp().Logger()
}
func NewLoggerWithFixedLevel(lvl zerolog.Level, out ...io.Writer) zerolog.Logger {
writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = diodeMultiWriter(out...)
w.TimeFormat = timeFmt
w.FormatMessage = func(msgI any) string { // pad spaces for each line
if msgI == nil {
return ""
}
return fmtMessage(msgI.(string))
}
})
return zerolog.New(writer).Level(level).With().Str("level", lvl.String()).Timestamp().Logger()
}