refactor: migrate from zerolog to zap logging library

Replace github.com/rs/zerolog with go.uber.org/zap throughout the codebase
to improve performance and provide better structured logging capabilities.

Key changes:
- Replace zerolog imports with zap and zapcore
- Implement custom Logger wrapper in log.go to maintain zerolog-like API
- Add LogEvent struct with chained methods (Str, Int, Err, Bool, etc.)
- Update all logging calls to use the new zap-based wrapper
- Replace JSON encoders with Console encoders for better readability

Benefits:
- Better performance with zap's optimized logging
- Consistent structured logging across all components
- Maintained zerolog-like API for easy migration
- Proper field context preservation for debugging
- Multi-core logging architecture for better output control

All tests pass and build succeeds.
This commit is contained in:
Cuong Manh Le
2025-07-21 19:50:43 +07:00
committed by Cuong Manh Le
parent 016c566307
commit d41334c66f
15 changed files with 400 additions and 135 deletions
+56 -35
View File
@@ -5,10 +5,10 @@ import (
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/kardianos/service"
"github.com/rs/zerolog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/Control-D-Inc/ctrld"
)
@@ -40,9 +40,10 @@ var (
cleanup bool
startOnly bool
mainLog atomic.Pointer[ctrld.Logger]
consoleWriter zerolog.ConsoleWriter
noConfigStart bool
mainLog atomic.Pointer[ctrld.Logger]
consoleWriter zapcore.Core
consoleWriterLevel zapcore.Level
noConfigStart bool
)
const (
@@ -53,8 +54,8 @@ const (
)
func init() {
l := zerolog.New(io.Discard)
mainLog.Store(&ctrld.Logger{Logger: &l})
l := zap.NewNop()
mainLog.Store(&ctrld.Logger{Logger: l})
}
func Main() {
@@ -82,23 +83,23 @@ func normalizeLogFilePath(logFilePath string) string {
// initConsoleLogging initializes console logging, then storing to mainLog.
func initConsoleLogging() {
consoleWriter = zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.TimeFormat = time.StampMilli
})
multi := zerolog.MultiLevelWriter(consoleWriter)
l := mainLog.Load().Output(multi).With().Timestamp().Logger()
mainLog.Store(&ctrld.Logger{Logger: &l})
consoleWriterLevel = zapcore.InfoLevel
switch {
case silent:
zerolog.SetGlobalLevel(zerolog.NoLevel)
// For silent mode, use a no-op logger
l := zap.NewNop()
mainLog.Store(&ctrld.Logger{Logger: l})
case verbose == 1:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
// Info level is default
case verbose > 1:
zerolog.SetGlobalLevel(zerolog.DebugLevel)
// Debug level
consoleWriterLevel = zapcore.DebugLevel
default:
zerolog.SetGlobalLevel(zerolog.NoticeLevel)
// Notice level maps to Info in zap
}
consoleWriter = newHumanReadableZapCore(os.Stdout, consoleWriterLevel)
l := zap.New(consoleWriter)
mainLog.Store(&ctrld.Logger{Logger: l})
}
// initInteractiveLogging is like initLogging, but the ProxyLogger is discarded
@@ -108,7 +109,6 @@ func initConsoleLogging() {
func initInteractiveLogging() {
old := cfg.Service.LogPath
cfg.Service.LogPath = ""
zerolog.TimeFieldFormat = time.RFC3339 + ".000"
initLoggingWithBackup(false)
cfg.Service.LogPath = old
}
@@ -119,7 +119,7 @@ func initInteractiveLogging() {
// This is only used in runCmd for special handling in case of logging config
// change in cd mode. Without special reason, the caller should use initLogging
// wrapper instead of calling this function directly.
func initLoggingWithBackup(doBackup bool) []io.Writer {
func initLoggingWithBackup(doBackup bool) []zapcore.Core {
var writers []io.Writer
if logFilePath := normalizeLogFilePath(cfg.Service.LogPath); logFilePath != "" {
// Create parent directory if necessary.
@@ -146,32 +146,53 @@ func initLoggingWithBackup(doBackup bool) []io.Writer {
}
writers = append(writers, logFile)
}
writers = append(writers, consoleWriter)
multi := zerolog.MultiLevelWriter(writers...)
l := mainLog.Load().Output(multi).With().Logger()
mainLog.Store(&ctrld.Logger{Logger: &l})
zerolog.SetGlobalLevel(zerolog.NoticeLevel)
// Create zap cores for different writers
var cores []zapcore.Core
cores = append(cores, consoleWriter)
// Determine log level
logLevel := cfg.Service.LogLevel
switch {
case silent:
zerolog.SetGlobalLevel(zerolog.NoLevel)
return writers
// For silent mode, use a no-op logger
l := zap.NewNop()
mainLog.Store(&ctrld.Logger{Logger: l})
return cores
case verbose == 1:
logLevel = "info"
case verbose > 1:
logLevel = "debug"
}
if logLevel == "" {
return writers
// Parse log level
var level zapcore.Level
switch logLevel {
case "debug":
level = zapcore.DebugLevel
case "info":
level = zapcore.InfoLevel
case "warn":
level = zapcore.WarnLevel
case "error":
level = zapcore.ErrorLevel
default:
level = zapcore.InfoLevel // default level
}
level, err := zerolog.ParseLevel(logLevel)
if err != nil {
mainLog.Load().Warn().Err(err).Msg("could not set log level")
return writers
consoleWriter.Enabled(level)
// Add cores for all writers
for _, writer := range writers {
core := newMachineFriendlyZapCore(writer, level)
cores = append(cores, core)
}
zerolog.SetGlobalLevel(level)
return writers
// Create a multi-core logger
multiCore := zapcore.NewTee(cores...)
logger := zap.New(multiCore)
mainLog.Store(&ctrld.Logger{Logger: logger})
return cores
}
func initCache() {