This is an automated email from the ASF dual-hosted git repository.

baerwang pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new f472c94f fix: fix logger config overwrite problem (#765)
f472c94f is described below

commit f472c94f2044a289aa5e4b6372cd43e1bd5e86f9
Author: Xuetao Li <[email protected]>
AuthorDate: Wed Oct 8 16:46:46 2025 +0800

    fix: fix logger config overwrite problem (#765)
    
    * fix: fix logger config overwrite problem
    
    * delete Chinese
    
    * add header
    
    * fix sonar
    
    * import format
    
    * fix ci
    
    * refactor logger: simplify log level handling and remove unused code
    
    * import
    
    * refactor(logger): simplify log level parsing and improve fallback handling
    
    * refactor(logger): simplify log level parsing and improve fallback handling
    
    * fix test
    
    * test(logger): add assertions to validate logger configuration after level 
setting
    
    * test(logger): add assertions to validate logger configuration after level 
setting
---
 pkg/cmd/gateway.go                  |  23 +---
 pkg/configcenter/nacos_load_test.go |   6 +-
 pkg/logger/controller.go            |  47 ++------
 pkg/logger/controller_test.go       | 117 ++++++++++++++++++
 pkg/logger/logger.go                |  84 ++++++++++---
 pkg/logger/logger_test.go           | 228 ++++++++++++++++++++++++++++--------
 6 files changed, 382 insertions(+), 123 deletions(-)

diff --git a/pkg/cmd/gateway.go b/pkg/cmd/gateway.go
index 96bb4ae5..84a7a992 100644
--- a/pkg/cmd/gateway.go
+++ b/pkg/cmd/gateway.go
@@ -39,15 +39,6 @@ import (
 )
 
 var (
-       flagToLogLevel = map[string]string{
-               "trace":    "TRACE",
-               "debug":    "DEBUG",
-               "info":     "INFO",
-               "warning":  "WARN",
-               "error":    "ERROR",
-               "critical": "FATAL",
-       }
-
        configPath    string
        apiConfigPath string
        logConfigPath string
@@ -165,7 +156,7 @@ func initDefaultValue() {
        }
 }
 
-// initLog
+// initLog initializes logger according to log config file and log level
 func initLog() error {
        err := logger.InitLog(logConfigPath)
        if err != nil {
@@ -173,13 +164,11 @@ func initLog() error {
                return err
        }
 
-       if level, ok := flagToLogLevel[logLevel]; ok {
-               logger.SetLoggerLevel(level)
-       } else {
-               logger.SetLoggerLevel(flagToLogLevel[constant.DefaultLogLevel])
-               return fmt.Errorf("logLevel is invalid, set log level to 
default: %s", constant.DefaultLogLevel)
+       lvl := logger.ParseLogLevel(logLevel)
+       if ok := logger.SetLoggerLevel(lvl); !ok {
+               err = fmt.Errorf("set logLevel failed")
        }
-       return nil
+       return err
 }
 
 func initLogWithConfig(boot *model.Bootstrap) {
@@ -189,7 +178,7 @@ func initLogWithConfig(boot *model.Bootstrap) {
 }
 
 // nolint
-// initApiConfig return value of the bool is for the judgment of whether is a 
api meta data error, a kind of silly (?)
+// initApiConfig return value of the bool is for the judgment of whether is an 
api metadata error or a kind of silly (?)
 func initApiConfig() (*model.Bootstrap, error) {
        bootstrap := config.Load(configPath)
        return bootstrap, nil
diff --git a/pkg/configcenter/nacos_load_test.go 
b/pkg/configcenter/nacos_load_test.go
index cadbf94a..1eca5440 100644
--- a/pkg/configcenter/nacos_load_test.go
+++ b/pkg/configcenter/nacos_load_test.go
@@ -28,6 +28,8 @@ import (
 
 import (
        . "github.com/smartystreets/goconvey/convey"
+
+       "go.uber.org/zap/zapcore"
 )
 
 import (
@@ -97,14 +99,14 @@ func TestNacosConfig_onChange(t *testing.T) {
 
                Convey("Test onChange with empty input", func() {
                        // Suppress logs during this test.
-                       logger.SetLoggerLevel("fatal")
+                       logger.SetLoggerLevel(zapcore.FatalLevel)
 
                        client.remoteConfig = nil
                        client.onChange(Namespace, Group, DataId, "")
                        So(client.remoteConfig, ShouldBeNil)
 
                        // Restore the logger level.
-                       logger.SetLoggerLevel("info")
+                       logger.SetLoggerLevel(zapcore.InfoLevel)
                })
        })
 }
diff --git a/pkg/logger/controller.go b/pkg/logger/controller.go
index fb2d9708..309442a9 100644
--- a/pkg/logger/controller.go
+++ b/pkg/logger/controller.go
@@ -18,39 +18,32 @@
 package logger
 
 import (
-       "strings"
        "sync"
 )
 
 import (
-       "go.uber.org/zap"
        "go.uber.org/zap/zapcore"
 )
 
 // logController governs the logging output or configuration changes 
throughout the entire project.
 type logController struct {
-       mu sync.RWMutex
-
-       logger *pixiuLogger
+       mu     sync.RWMutex
+       logger *PixiuLogger
 }
 
-// setLoggerLevel safely changes the log level in a concurrent manner.
-func (c *logController) setLoggerLevel(level string) bool {
+// setLoggerLevel changes the level at runtime without rebuilding the logger.
+func (c *logController) setLoggerLevel(level zapcore.Level) bool {
        c.mu.Lock()
        defer c.mu.Unlock()
-       lvl := c.parseLevel(level)
-       if lvl == nil {
+       if c.logger == nil || c.logger.config == nil {
                return false
        }
-
-       c.logger.config.Level = *lvl
-       l, _ := c.logger.config.Build(zap.AddCallerSkip(2))
-       c.logger = &pixiuLogger{SugaredLogger: l.Sugar(), config: 
c.logger.config}
+       c.logger.config.Level.SetLevel(level)
        return true
 }
 
-// updateLogger safely modifies the log object in a concurrent manner.
-func (c *logController) updateLogger(l *pixiuLogger) {
+// updateLogger swaps the underlying logger atomically.
+func (c *logController) updateLogger(l *PixiuLogger) {
        c.mu.Lock()
        defer c.mu.Unlock()
        c.logger = l
@@ -103,27 +96,3 @@ func (c *logController) errorf(fmt string, args ...any) {
        defer c.mu.RUnlock()
        c.logger.Errorf(fmt, args...)
 }
-
-// parseLevel is used to parse the level of the log.
-func (c *logController) parseLevel(level string) *zap.AtomicLevel {
-       var lvl zapcore.Level
-       switch strings.ToLower(level) {
-       case "debug":
-               lvl = zapcore.DebugLevel
-       case "info":
-               lvl = zapcore.InfoLevel
-       case "warn":
-               lvl = zapcore.WarnLevel
-       case "error":
-               lvl = zapcore.ErrorLevel
-       case "panic":
-               lvl = zapcore.PanicLevel
-       case "fatal":
-               lvl = zapcore.FatalLevel
-       default:
-               return nil
-       }
-
-       al := zap.NewAtomicLevelAt(lvl)
-       return &al
-}
diff --git a/pkg/logger/controller_test.go b/pkg/logger/controller_test.go
new file mode 100644
index 00000000..eaa4cc25
--- /dev/null
+++ b/pkg/logger/controller_test.go
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package logger
+
+import (
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+
+       "go.uber.org/zap/zapcore"
+)
+
+func TestParseLevelAndSet(t *testing.T) {
+       cfg, _ := newDevConfigToFile(t)
+       InitLogger(cfg)
+
+       tests := []struct {
+               in       string
+               ok       bool
+               zapLevel zapcore.Level
+       }{
+               {"debug", true, zapcore.DebugLevel},
+               {"INFO", true, zapcore.InfoLevel},
+               {"Warn", true, zapcore.WarnLevel},
+               {"error", true, zapcore.ErrorLevel},
+               {"panic", true, zapcore.PanicLevel},
+               {"dpanic", true, zapcore.DPanicLevel},
+               {"fatal", true, zapcore.FatalLevel},
+               {"critical", true, zapcore.FatalLevel},
+               {"unknown", false, zapcore.InfoLevel}, // parseLevel default 
fallback to info
+               {"trace", true, zapcore.DebugLevel},
+               {"Warning", true, zapcore.WarnLevel},
+       }
+
+       for _, tt := range tests {
+               lvl := ParseLogLevel(tt.in)
+               if lvl != tt.zapLevel {
+                       t.Fatalf("SetLoggerLevel(%q) want %v", tt.in, tt.ok)
+               }
+               control.setLoggerLevel(lvl)
+
+               // assert the level is set
+               assert.Equal(t, tt.zapLevel, 
control.logger.config.Level.Level())
+
+               // assert the cfg is not changed
+               assert.Equal(t, cfg.Development, 
control.logger.config.Development)
+               assert.Equal(t, cfg.DisableCaller, 
control.logger.config.DisableCaller)
+               assert.Equal(t, cfg.DisableStacktrace, 
control.logger.config.DisableStacktrace)
+               assert.Equal(t, cfg.Sampling, control.logger.config.Sampling)
+               assert.Equal(t, cfg.Encoding, control.logger.config.Encoding)
+               assert.Equal(t, cfg.EncoderConfig.MessageKey, 
control.logger.config.EncoderConfig.MessageKey)
+               assert.Equal(t, cfg.EncoderConfig.LevelKey, 
control.logger.config.EncoderConfig.LevelKey)
+               assert.Equal(t, cfg.EncoderConfig.TimeKey, 
control.logger.config.EncoderConfig.TimeKey)
+               assert.Equal(t, cfg.EncoderConfig.NameKey, 
control.logger.config.EncoderConfig.NameKey)
+               assert.Equal(t, cfg.EncoderConfig.CallerKey, 
control.logger.config.EncoderConfig.CallerKey)
+               assert.Equal(t, cfg.EncoderConfig.FunctionKey, 
control.logger.config.EncoderConfig.FunctionKey)
+               assert.Equal(t, cfg.EncoderConfig.StacktraceKey, 
control.logger.config.EncoderConfig.StacktraceKey)
+               assert.Equal(t, cfg.EncoderConfig.SkipLineEnding, 
control.logger.config.EncoderConfig.SkipLineEnding)
+               assert.Equal(t, cfg.EncoderConfig.LineEnding, 
control.logger.config.EncoderConfig.LineEnding)
+               assert.Equal(t, cfg.EncoderConfig.ConsoleSeparator, 
control.logger.config.EncoderConfig.ConsoleSeparator)
+               assert.Equal(t, cfg.OutputPaths, 
control.logger.config.OutputPaths)
+               assert.Equal(t, cfg.ErrorOutputPaths, 
control.logger.config.ErrorOutputPaths)
+               assert.Equal(t, cfg.InitialFields, 
control.logger.config.InitialFields)
+       }
+
+       // make sure able to write
+       GetLogger().Info("still alive")
+       _ = GetLogger().Sync()
+}
+
+func TestInitLoggerNil(t *testing.T) {
+       InitLogger(nil)
+       if GetLogger() == nil || GetLogger().SugaredLogger == nil {
+               t.Fatalf("GetLogger returned nil")
+       }
+       // retrigger InitLogger(nil)
+       InitLogger(nil)
+       GetLogger().Debug("dev init ok")
+       _ = GetLogger().Sync()
+}
+
+// HotReload(nil) fallback to dev
+func TestHotReloadNil(t *testing.T) {
+       if err := HotReload(nil); err != nil {
+               t.Fatalf("HotReload(nil) unexpected error: %v", err)
+       }
+       GetLogger().Warn("after hot reload nil")
+       _ = GetLogger().Sync()
+}
+
+func TestLoggerBasicUsage(t *testing.T) {
+       cfg, _ := newDevConfigToFile(t)
+       InitLogger(cfg)
+
+       log := GetLogger()
+       log = &PixiuLogger{SugaredLogger: log.With("k", "v"), config: 
log.config}
+       log.Infow("with fields", "a", 1)
+
+       _ = log.Sync()
+}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index a1fff168..f9a4362f 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -37,20 +37,20 @@ import (
 
 var control *logController
 
-type pixiuLogger struct {
+type PixiuLogger struct {
        *zap.SugaredLogger
        config *zap.Config
 }
 
 func init() {
-       // only use in test case, so just load default config
+       // only used in test/bootstrap; keep a sane default
        if control == nil {
                control = new(logController)
                InitLogger(nil)
        }
 }
 
-// PaddedCallerEncoder is a custom caller encoder that ensures that all file 
paths are displayed at the same length
+// PaddedCallerEncoder aligns caller path to a fixed width for prettier 
console output.
 func PaddedCallerEncoder(caller zapcore.EntryCaller, enc 
zapcore.PrimitiveArrayEncoder) {
 
        callerPath := caller.TrimmedPath()
@@ -65,7 +65,21 @@ func PaddedCallerEncoder(caller zapcore.EntryCaller, enc 
zapcore.PrimitiveArrayE
        enc.AppendString(callerPath)
 }
 
-// InitLog load from config path
+// helper: build with unified pixiu options.
+// - always AddCaller + AddCallerSkip(2)
+// - AddStacktrace(Error+) only when stacktrace is not disabled in cfg
+func buildWithPixiuOptions(cfg *zap.Config) (*zap.Logger, error) {
+       opts := []zap.Option{
+               zap.AddCaller(),
+               zap.AddCallerSkip(2),
+       }
+       if !cfg.DisableStacktrace {
+               opts = append(opts, zap.AddStacktrace(zapcore.ErrorLevel))
+       }
+       return cfg.Build(opts...)
+}
+
+// InitLog loads from YAML file; falls back to development defaults when file 
is absent/invalid.
 func InitLog(logConfFile string) error {
        if logConfFile == "" {
                InitLogger(nil)
@@ -83,8 +97,7 @@ func InitLog(logConfFile string) error {
        }
 
        conf := &zap.Config{}
-       err = yaml.UnmarshalYML(confFileStream, conf)
-       if err != nil {
+       if err := yaml.UnmarshalYML(confFileStream, conf); err != nil {
                InitLogger(nil)
                return perrors.New(fmt.Sprintf("[Unmarshal]init pixiuLogger 
error: %v", err))
        }
@@ -94,10 +107,18 @@ func InitLog(logConfFile string) error {
        return nil
 }
 
+// InitLogger initializes logger. Default is development-style (console, 
debug),
+// but we force stacktrace to Error+ only, and enable caller with our custom 
encoder.
+// If a config is supplied, we respect it and only normalize caller encoder 
and stacktrace threshold.
 func InitLogger(conf *zap.Config) {
-       var zapLoggerConfig zap.Config
+       var (
+               cfg zap.Config
+       )
+
        if conf == nil {
-               zapLoggerConfig = zap.NewDevelopmentConfig()
+               // Default: development style
+               cfg = zap.NewDevelopmentConfig()
+
                zapLoggerEncoderConfig := zapcore.EncoderConfig{
                        TimeKey:        "time",
                        LevelKey:       "level",
@@ -111,28 +132,57 @@ func InitLogger(conf *zap.Config) {
                        EncodeCaller:   PaddedCallerEncoder,
                        // EncodeCaller:   zapcore.ShortCallerEncoder,
                }
-               zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
+
+               cfg.EncoderConfig = zapLoggerEncoderConfig
        } else {
-               zapLoggerConfig = *conf
-               // Set up a custom encoder directly without checking the 
original value
-               zapLoggerConfig.EncoderConfig.EncodeCaller = PaddedCallerEncoder
+               cfg = *conf
+               // Unify caller encoder regardless of YAML to keep alignment 
style
+               cfg.EncoderConfig.EncodeCaller = PaddedCallerEncoder
        }
-       zapLogger, _ := zapLoggerConfig.Build(zap.AddCallerSkip(2))
-       l := &pixiuLogger{zapLogger.Sugar(), &zapLoggerConfig}
 
+       z, err := buildWithPixiuOptions(&cfg)
+       if err != nil {
+               z = zap.NewNop()
+       }
+       l := &PixiuLogger{z.Sugar(), &cfg}
        control.updateLogger(l)
 }
 
-// SetLoggerLevel safely changes the log level in a concurrent manner.
-func SetLoggerLevel(level string) bool {
+// SetLoggerLevel changes the level at runtime without rebuilding logger.
+func SetLoggerLevel(level zapcore.Level) bool {
        return control.setLoggerLevel(level)
 }
 
+// HotReload rebuilds from a new zap.Config (e.g., re-read YAML).
 func HotReload(conf *zap.Config) error {
        InitLogger(conf)
        return nil
 }
 
-func GetLogger() *pixiuLogger {
+// GetLogger exposes the current sugared logger.
+func GetLogger() *PixiuLogger {
        return control.logger
 }
+
+// ParseLogLevel parses textual level to zapcore.Level.
+func ParseLogLevel(level string) zapcore.Level {
+       switch strings.ToLower(strings.TrimSpace(level)) {
+       case "debug", "trace":
+               return zapcore.DebugLevel
+       case "info":
+               return zapcore.InfoLevel
+       case "warn", "warning":
+               return zapcore.WarnLevel
+       case "error":
+               return zapcore.ErrorLevel
+       case "dpanic":
+               return zapcore.DPanicLevel
+       case "panic":
+               return zapcore.PanicLevel
+       case "fatal", "critical":
+               return zapcore.FatalLevel
+       default:
+               Warnf("unknown log level %q, defaulting to info", level)
+               return zapcore.InfoLevel
+       }
+}
diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go
index 561cc4d7..ce7fb21e 100644
--- a/pkg/logger/logger_test.go
+++ b/pkg/logger/logger_test.go
@@ -18,66 +18,198 @@
 package logger
 
 import (
-       "fmt"
+       "os"
        "path/filepath"
-       "runtime"
+       "strings"
        "testing"
+       "time"
 )
 
 import (
-       "github.com/stretchr/testify/assert"
+       "go.uber.org/zap"
+       "go.uber.org/zap/zapcore"
 )
 
-func TestInitLog(t *testing.T) {
-       var (
-               err  error
-               path string
+func newDevConfigToFile(t *testing.T) (*zap.Config, string) {
+       t.Helper()
+       dir := t.TempDir()
+       out := filepath.Join(dir, "zap.log")
+
+       cfg := zap.NewDevelopmentConfig()
+       // output to a temp file
+       cfg.OutputPaths = []string{out}
+       cfg.ErrorOutputPaths = []string{out}
+       cfg.EncoderConfig.StacktraceKey = "stacktrace"
+       cfg.EncoderConfig.CallerKey = "caller"
+
+       return &cfg, out
+}
+
+func readAll(t *testing.T, path string) string {
+       t.Helper()
+       b, err := os.ReadFile(path)
+       if err != nil {
+               t.Fatalf("read %s failed: %v", path, err)
+       }
+       return string(b)
+}
+
+func TestDisableStacktraceTrueNoStackEvenOnError(t *testing.T) {
+       cfg, out := newDevConfigToFile(t)
+       cfg.DisableStacktrace = true // YAML equivalent: disableStacktrace: true
+       InitLogger(cfg)
+
+       log := GetLogger()
+       log.Error("boom")
+       _ = log.Sync()
+
+       got := readAll(t, out)
+       line := ""
+       for _, l := range strings.Split(got, "\n") {
+               if strings.Contains(l, "boom") {
+                       line = l
+                       break
+               }
+       }
+       if line == "" {
+               t.Fatalf("error line not found")
+       }
+       if strings.Contains(strings.ToLower(line), "stacktrace") {
+               t.Fatalf("disableStacktrace=true: must NOT output stacktrace, 
got:\n%s", line)
+       }
+}
+
+func TestSetLoggerLevelDoesNotRebuildAndTakesEffect(t *testing.T) {
+       cfg, out := newDevConfigToFile(t)
+       InitLogger(cfg)
+
+       before := GetLogger().SugaredLogger
+
+       // dynamic set to error
+       ok := SetLoggerLevel(zapcore.ErrorLevel)
+       if !ok {
+               t.Fatalf("SetLoggerLevel returned false")
+       }
+
+       after := GetLogger().SugaredLogger
+       if before != after {
+               t.Fatalf("SetLoggerLevel should NOT rebuild logger; pointer 
changed: %p -> %p", before, after)
+       }
+
+       // write new: info should not appear, error should appear
+       log := GetLogger()
+       log.Info("info should be filtered")
+       log.Error("error should appear")
+       _ = log.Sync()
+
+       got := readAll(t, out)
+       if strings.Contains(got, "info should be filtered") {
+               t.Fatalf("info should NOT appear after level set to 
error:\n%s", got)
+       }
+       if !strings.Contains(got, "error should appear") {
+               t.Fatalf("error should appear but not found:\n%s", got)
+       }
+}
+
+func TestHotReloadRebuildsAndSwitchesSink(t *testing.T) {
+       // cfg1 -> out1
+       cfg1, out1 := newDevConfigToFile(t)
+       InitLogger(cfg1)
+       l1 := GetLogger().SugaredLogger
+
+       const (
+               H1 = "hello-1"
+               H2 = "hello-2"
        )
 
-       err = InitLog("")
-       assert.EqualError(t, err, "log configure file name is nil")
-
-       path, err = filepath.Abs("./log.xml")
-       assert.NoError(t, err)
-       err = InitLog(path)
-       assert.EqualError(t, err, "log configure file name "+path+" suffix must 
be .yml")
-
-       path, err = filepath.Abs("./logger.yml")
-       assert.NoError(t, err)
-       err = InitLog(path)
-       var errMsg string
-       if runtime.GOOS == "windows" {
-               errMsg = fmt.Sprintf("open %s: The system cannot find the file 
specified.", path)
-       } else {
-               errMsg = fmt.Sprintf("open %s: no such file or directory", path)
+       GetLogger().Info(H1)
+       _ = GetLogger().Sync()
+
+       // cfg2 -> out2(new sink)
+       cfg2, out2 := newDevConfigToFile(t)
+       // to split it, set level to info
+       HotReload(cfg2)
+       l2 := GetLogger().SugaredLogger
+
+       if l1 == l2 {
+               t.Fatalf("HotReload should rebuild logger; got same pointer 
%p", l1)
+       }
+
+       GetLogger().Info(H2)
+       _ = GetLogger().Sync()
+
+       got1 := readAll(t, out1)
+       got2 := readAll(t, out2)
+
+       if !strings.Contains(got1, H1) {
+               t.Fatalf("out1 should contain hello-1 but not found:\n%s", got1)
+       }
+       if strings.Contains(got1, H2) {
+               t.Fatalf("out1 should NOT contain hello-2 after reload:\n%s", 
got1)
+       }
+       if !strings.Contains(got2, H2) {
+               t.Fatalf("out2 should contain hello-2 but not found:\n%s", got2)
        }
-       assert.EqualError(t, err, fmt.Sprintf("os.ReadFile file:%s, error:%s", 
path, errMsg))
-
-       err = InitLog("./log.yml")
-       assert.NoError(t, err)
-
-       Debug("debug")
-       Info("info")
-       Warn("warn")
-       Error("error")
-       Debugf("%s", "debug")
-       Infof("%s", "info")
-       Warnf("%s", "warn")
-       Errorf("%s", "error")
 }
 
-func TestSetLoggerLevel(t *testing.T) {
-       assert.NotNil(t, control, "control should not be nil")
+func TestPaddedCallerEncoderFixedWidthAtLeast30(t *testing.T) {
+       caller := zapcore.EntryCaller{
+               Defined: true,
+               File:    "a/b/c.go",
+               Line:    7,
+       }
+       collector := &stringCollector{}
+       PaddedCallerEncoder(caller, collector)
+
+       if len(collector.items) == 0 {
+               t.Fatalf("collector got no items")
+       }
+       got := collector.items[0]
+       if len(got) < 30 {
+               t.Fatalf("caller not padded to >=30, got len=%d val=%q", 
len(got), got)
+       }
+}
 
-       assert.True(t, SetLoggerLevel("info"), "when pass info to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("debug"), "when pass debug to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("error"), "when pass error to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("panic"), "when pass panic to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("INFO"), "when pass INFO to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("DEbug"), "when pass DEbug to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("ErRor"), "when pass ErRor to 
SetLoggerLevel, result should be true")
-       assert.True(t, SetLoggerLevel("WaRN"), "when pass WaRN to 
SetLoggerLevel, result should be true")
+// -------------------------- helpers --------------------------
 
-       assert.False(t, SetLoggerLevel("i"), "when pass i to SetLoggerLevel, 
result should be false")
-       assert.False(t, SetLoggerLevel(""), "when pass nothing to 
SetLoggerLevel, result should be false")
+type stringCollector struct {
+       items []string
 }
+
+// mock zapcore.PrimitiveArrayEncoder
+func (s *stringCollector) AppendString(v string) { s.items = append(s.items, 
v) }
+
+func (s *stringCollector) AppendBool(bool)                      { /*mock*/ }
+func (s *stringCollector) AppendByteString([]byte)              { /*mock*/ }
+func (s *stringCollector) AppendComplex128(complex128)          { /*mock*/ }
+func (s *stringCollector) AppendComplex64(complex64)            { /*mock*/ }
+func (s *stringCollector) AppendDuration(time.Duration)         { /*mock*/ }
+func (s *stringCollector) AppendFloat64(float64)                { /*mock*/ }
+func (s *stringCollector) AppendFloat32(float32)                { /*mock*/ }
+func (s *stringCollector) AppendInt(int)                        { /*mock*/ }
+func (s *stringCollector) AppendInt64(int64)                    { /*mock*/ }
+func (s *stringCollector) AppendInt32(int32)                    { /*mock*/ }
+func (s *stringCollector) AppendInt16(int16)                    { /*mock*/ }
+func (s *stringCollector) AppendInt8(int8)                      { /*mock*/ }
+func (s *stringCollector) AppendTime(time.Time)                 { /*mock*/ }
+func (s *stringCollector) AppendUint(uint)                      { /*mock*/ }
+func (s *stringCollector) AppendUint64(uint64)                  { /*mock*/ }
+func (s *stringCollector) AppendUint32(uint32)                  { /*mock*/ }
+func (s *stringCollector) AppendUint16(uint16)                  { /*mock*/ }
+func (s *stringCollector) AppendUint8(uint8)                    { /*mock*/ }
+func (s *stringCollector) AppendUintptr(uintptr)                { /*mock*/ }
+func (s *stringCollector) AppendReflected(any)                  { /*mock*/ }
+func (s *stringCollector) AppendArray(zapcore.ArrayMarshaler)   { /*mock*/ }
+func (s *stringCollector) AppendObject(zapcore.ObjectMarshaler) { /*mock*/ }
+func (s *stringCollector) AppendBinary([]byte)                  { /*mock*/ }
+func (s *stringCollector) AppendComplex(complex128)             { /*mock*/ }
+func (s *stringCollector) AppendDurationRef(time.Duration)      { /*mock*/ }
+func (s *stringCollector) AppendTimeLayout(time.Time, string)   { /*mock*/ }
+func (s *stringCollector) AppendIP(ip any)                      { /*mock*/ }
+func (s *stringCollector) AppendIPNet(net any)                  { /*mock*/ }
+func (s *stringCollector) AppendMAC(mac any)                    { /*mock*/ }
+func (s *stringCollector) AppendHex(any)                        { /*mock*/ }
+func (s *stringCollector) AppendFloat(any)                      { /*mock*/ }
+func (s *stringCollector) Cap() int                             { return 0 }
+func (s *stringCollector) Len() int                             { return 
len(s.items) }
+func (s *stringCollector) Truncate(int)                         { /*mock*/ }

Reply via email to