github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/slog_test.go (about)

     1  //go:build go1.21
     2  
     3  package zlog
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"log"
     9  	"log/slog"
    10  	"runtime/debug"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"go.uber.org/zap"
    16  	"go.uber.org/zap/zaptest"
    17  )
    18  
    19  func getErrorWithStackTrace() error {
    20  	return &errorWithStack{
    21  		error: errors.New("test error"),
    22  		stack: debug.Stack(),
    23  	}
    24  }
    25  
    26  type errorWithStack struct {
    27  	error
    28  	stack []byte
    29  }
    30  
    31  func TestRedirectStdLog(t *testing.T) {
    32  	out := &zaptest.Buffer{}
    33  	l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out)
    34  	require.Nil(t, err)
    35  	resetFunc := redirectStdLog(l, false)
    36  	defer resetFunc()
    37  
    38  	log.Printf("[Debug] std log Printf, key1=%v", "value1")
    39  	slog.Debug("std slog Debug", "key1", "value1")
    40  
    41  	lines := out.Lines()
    42  	require.Len(t, lines, 2)
    43  	assert.Contains(t, lines[0], "DEBUG stdlog zlog/slog_test.go:")
    44  	assert.Contains(t, lines[0], "[Debug] std log Printf, key1=value1")
    45  	assert.Contains(t, lines[1], "DEBUG zlog/slog_test.go:")
    46  	assert.Contains(t, lines[1], `std slog Debug {"key1": "value1"}`)
    47  }
    48  
    49  func TestSetSlogDefault(t *testing.T) {
    50  	out := &zaptest.Buffer{}
    51  	l, p, err := NewWithOutput(&Config{Level: "warn", Format: "console"}, out)
    52  	require.Nil(t, err)
    53  	resetFunc := ReplaceGlobals(l, p)
    54  	defer resetFunc()
    55  
    56  	SetSlogDefault(NewSlogLogger())
    57  	log.Printf("[Warn] std log Printf, key1=%v", "value1")
    58  	slog.Info("std slog Info", "key1", "value1")
    59  	slog.Warn("std slog Warn", "key2", "value2")
    60  
    61  	lines := out.Lines()
    62  	require.Len(t, lines, 2)
    63  	assert.Contains(t, lines[0], "WARN stdlog zlog/slog_test.go:")
    64  	assert.Contains(t, lines[0], "[Warn] std log Printf, key1=value1")
    65  	assert.Contains(t, lines[1], "WARN zlog/slog_test.go:")
    66  	assert.Contains(t, lines[1], `std slog Warn {"key2": "value2"}`)
    67  }
    68  
    69  func TestNewSlogLogger(t *testing.T) {
    70  	r0 := NewSlogLogger()
    71  	assert.NotNil(t, r0.Handler().(*slogImpl).opts)
    72  	assert.NotNil(t, r0.Handler().(*slogImpl).l)
    73  
    74  	l := L().Named("slogTest").With(zap.String("ns", "default"))
    75  	r1 := NewSlogLogger(func(options *SlogOptions) {
    76  		options.Logger = l.Logger
    77  	})
    78  	assert.Equal(t, "slogTest", r1.Handler().(*slogImpl).name)
    79  	r1.Info("test NewSlogLogger with logger")
    80  }
    81  
    82  func TestSlogLoggerLogging(t *testing.T) {
    83  	out := &zaptest.Buffer{}
    84  	l, _, err := NewWithOutput(&Config{
    85  		Level:             "trace",
    86  		Format:            "json",
    87  		DisableTimestamp:  true,
    88  		DisableStacktrace: true,
    89  	}, out)
    90  	require.Nil(t, err)
    91  	logger := NewSlogLogger(func(options *SlogOptions) {
    92  		options.Logger = l
    93  	})
    94  
    95  	logger.Info("test info", "ns", "default", "podnum", 2)
    96  	lines := out.Lines()
    97  	assert.Contains(t, lines[0], `"level":"info"`)
    98  	assert.Contains(t, lines[0], `"caller":"zlog/slog_test.go:`)
    99  	assert.Contains(t, lines[0], `"ns":"default"`)
   100  	assert.Contains(t, lines[0], `"podnum":2`)
   101  }
   102  
   103  func TestSlogLoggerCtxHandler(t *testing.T) {
   104  	t.Run("change level", func(t *testing.T) {
   105  		out := &zaptest.Buffer{}
   106  		cfg := &Config{Level: "info", Format: "console"}
   107  		cfg.RedirectStdLog = true
   108  		cfg.CtxHandler.ChangeLevel = func(ctx context.Context) *Level {
   109  			if ctx.Value("debug") != nil {
   110  				level := DebugLevel
   111  				return &level
   112  			}
   113  			return nil
   114  		}
   115  		cfg.CtxHandler.WithCtx = func(ctx context.Context) (result CtxResult) {
   116  			if ctx.Value("debug") != nil {
   117  				level := DebugLevel
   118  				result.Level = &level
   119  			}
   120  			return
   121  		}
   122  		l, p, err := NewWithOutput(cfg, out)
   123  		require.Nil(t, err)
   124  		resetFunc := ReplaceGlobals(l, p)
   125  		defer resetFunc()
   126  
   127  		ctx := context.WithValue(context.Background(), "debug", "1")
   128  		slog.DebugContext(ctx, "a debug message")
   129  
   130  		lines := out.Lines()
   131  		require.Len(t, lines, 1)
   132  		assert.Contains(t, lines[0], "DEBUG zlog/slog_test.go:")
   133  		assert.Contains(t, lines[0], `a debug message`)
   134  	})
   135  
   136  	t.Run("ctx fields", func(t *testing.T) {
   137  		out := &zaptest.Buffer{}
   138  		cfg := &Config{Level: "info", Format: "console"}
   139  		cfg.RedirectStdLog = true
   140  		cfg.CtxHandler.WithCtx = func(ctx context.Context) (result CtxResult) {
   141  			result.Fields = append(result.Fields,
   142  				zap.String("logid", "abcde"),
   143  				zap.Int64("userID", 12345))
   144  			return
   145  		}
   146  		l, p, err := NewWithOutput(cfg, out)
   147  		require.Nil(t, err)
   148  		resetFunc := ReplaceGlobals(l, p)
   149  		defer resetFunc()
   150  
   151  		slog.InfoContext(context.Background(), "an info message")
   152  
   153  		lines := out.Lines()
   154  		require.Len(t, lines, 1)
   155  		assert.Contains(t, lines[0], "INFO zlog/slog_test.go:")
   156  		assert.Contains(t, lines[0], `an info message {"logid": "abcde", "userID": 12345}`)
   157  	})
   158  }
   159  
   160  func TestSlogOptionsReplaceAttr(t *testing.T) {
   161  	t.Run("error key", func(t *testing.T) {
   162  		out := &zaptest.Buffer{}
   163  		l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out)
   164  		require.Nil(t, err)
   165  		logger := NewSlogLogger(func(options *SlogOptions) {
   166  			options.Logger = l
   167  			options.ReplaceAttr = func(groups []string, a slog.Attr) (rr ReplaceResult) {
   168  				if a.Key == "error" || a.Key == "err" && a.Value.Kind() == slog.KindAny {
   169  					if err, ok := a.Value.Any().(error); ok {
   170  						rr.Field = zap.Error(err)
   171  						return
   172  					}
   173  				}
   174  				rr.Field = ConvertAttrToField(a)
   175  				return
   176  			}
   177  		})
   178  
   179  		logger.Error("error log 1", "err", errors.New("test error 1"))
   180  		logger.Error("error log 2", "error", errors.New("test error 2"))
   181  		logger.Error("error log 3", "notMatchErrKey", errors.New("test error 3"))
   182  
   183  		lines := out.Lines()
   184  		require.Len(t, lines, 3)
   185  		assert.Contains(t, lines[0], "ERROR zlog/slog_test.go:")
   186  		assert.Contains(t, lines[0], `error log 1 {"error": "test error 1"}`)
   187  		assert.Contains(t, lines[1], "ERROR zlog/slog_test.go:")
   188  		assert.Contains(t, lines[1], `error log 2 {"error": "test error 2"}`)
   189  		assert.Contains(t, lines[2], "ERROR zlog/slog_test.go:")
   190  		assert.Contains(t, lines[2], `error log 3 {"notMatchErrKey": "test error 3"}`)
   191  	})
   192  
   193  	t.Run("error stacktrace", func(t *testing.T) {
   194  		out := &zaptest.Buffer{}
   195  		l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out)
   196  		require.Nil(t, err)
   197  		logger := NewSlogLogger(func(options *SlogOptions) {
   198  			options.Logger = l
   199  			options.ReplaceAttr = func(groups []string, a slog.Attr) (rr ReplaceResult) {
   200  				if a.Key == "error" || a.Key == "err" && a.Value.Kind() == slog.KindAny {
   201  					if err, ok := a.Value.Any().(error); ok {
   202  						if inner, ok := err.(*errorWithStack); ok {
   203  							rr.Multi = []zap.Field{
   204  								zap.Error(err),
   205  								zap.ByteString("stacktrace", inner.stack),
   206  							}
   207  						} else {
   208  							rr.Field = zap.Error(err)
   209  						}
   210  						return
   211  					}
   212  				}
   213  				rr.Field = ConvertAttrToField(a)
   214  				return
   215  			}
   216  		})
   217  
   218  		logger.Error("error log", "err", wrapFunc(3, getErrorWithStackTrace)())
   219  
   220  		lines := out.Lines()
   221  		require.Len(t, lines, 1)
   222  		assert.Contains(t, lines[0], "ERROR zlog/slog_test.go:")
   223  		assert.Contains(t, lines[0], `error log {"error": "test error", "stacktrace": "`)
   224  		assert.Contains(t, lines[0], `zlog.getErrorWithStackTrace()\n\t`)
   225  		assert.Contains(t, lines[0], "zlog/slog_test.go:23")
   226  	})
   227  }
   228  
   229  func wrapFunc(depth int, f func() error) func() error {
   230  	for i := 0; i < depth; i++ {
   231  		copyFunc := f
   232  		newFunc := func() error {
   233  			return copyFunc()
   234  		}
   235  		f = newFunc
   236  	}
   237  	return f
   238  }