github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/xlog_test.go (about)

     1  package xlog
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	randv2 "math/rand/v2"
     7  	"os"
     8  	"path/filepath"
     9  	"sync"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/zap"
    14  	"go.uber.org/zap/zapcore"
    15  
    16  	"github.com/benz9527/xboot/lib/id"
    17  	"github.com/benz9527/xboot/lib/infra"
    18  )
    19  
    20  func TestLogLevelString(t *testing.T) {
    21  	require.Equal(t, "DEBUG", LogLevelDebug.String())
    22  	require.Equal(t, "INFO", LogLevelInfo.String())
    23  	require.Equal(t, "WARN", LogLevelWarn.String())
    24  	require.Equal(t, "ERROR", LogLevelError.String())
    25  	require.Equal(t, zapcore.DebugLevel, LogLevelDebug.zapLevel())
    26  	require.Equal(t, zapcore.InfoLevel, LogLevelInfo.zapLevel())
    27  	require.Equal(t, zapcore.WarnLevel, LogLevelWarn.zapLevel())
    28  	require.Equal(t, zapcore.ErrorLevel, LogLevelError.zapLevel())
    29  }
    30  
    31  type testBanner struct{}
    32  
    33  func (b testBanner) JSON() string {
    34  	return "{\"app\":\"xboot\"}"
    35  }
    36  
    37  func (b *testBanner) PlainText() string {
    38  	return `
    39  ██╗  ██╗██████╗  ██████╗  ██████╗ ████████╗
    40  ╚██╗██╔╝██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝
    41   ╚███╔╝ ██████╔╝██║   ██║██║   ██║   ██║   
    42   ██╔██╗ ██╔══██╗██║   ██║██║   ██║   ██║   
    43  ██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝   ██║   
    44  ╚═╝  ╚═╝╚═════╝  ╚═════╝  ╚═════╝    ╚═╝   
    45  `
    46  }
    47  
    48  type testMemOutWriter struct {
    49  	data []byte
    50  }
    51  
    52  func (w *testMemOutWriter) Write(p []byte) (n int, err error) {
    53  	w.data = append(w.data, p...)
    54  	return len(p), nil
    55  }
    56  
    57  func (w *testMemOutWriter) Reset() {
    58  	w.data = make([]byte, 0, 4096)
    59  }
    60  
    61  func TestLoggerPrintBanner(t *testing.T) {
    62  	w := &testMemOutWriter{data: make([]byte, 0, 4096)}
    63  	err := writerMap.Put(testMemAsOut, zapcore.AddSync(w))
    64  	require.NoError(t, err)
    65  
    66  	level := zapcore.DebugLevel
    67  	cfg := zap.NewDevelopmentEncoderConfig()
    68  	core := zapcore.NewCore(
    69  		zapcore.NewJSONEncoder(cfg),
    70  		zapcore.Lock(os.Stdout),
    71  		level,
    72  	)
    73  	l := zap.New(
    74  		zapcore.NewTee(core),
    75  		zap.AddCaller(),
    76  		zap.AddStacktrace(zap.ErrorLevel),
    77  		zap.AddCallerSkip(1), // Use caller filename as service
    78  	)
    79  
    80  	logger := &xLogger{
    81  		writer: testMemAsOut,
    82  	}
    83  	logger.logger.Store(l)
    84  	logger.Banner(&testBanner{})
    85  	require.Equal(t, "{\"banner\":\"{\\\"app\\\":\\\"xboot\\\"}\"}\n", string(w.data))
    86  	w.Reset()
    87  
    88  	printBanner = sync.Once{}
    89  	core = zapcore.NewCore(
    90  		zapcore.NewConsoleEncoder(cfg),
    91  		zapcore.Lock(os.Stdout),
    92  		level,
    93  	)
    94  	l = zap.New(
    95  		zapcore.NewTee(core),
    96  		zap.AddCaller(),
    97  		zap.AddStacktrace(zap.ErrorLevel),
    98  		zap.AddCallerSkip(1), // Use caller filename as service
    99  	)
   100  	logger = &xLogger{
   101  		writer:  testMemAsOut,
   102  		encoder: PlainText,
   103  	}
   104  	logger.logger.Store(l)
   105  	logger.Banner(&testBanner{})
   106  	require.Equal(t, `
   107  ██╗  ██╗██████╗  ██████╗  ██████╗ ████████╗
   108  ╚██╗██╔╝██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝
   109   ╚███╔╝ ██████╔╝██║   ██║██║   ██║   ██║   
   110   ██╔██╗ ██╔══██╗██║   ██║██║   ██║   ██║   
   111  ██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝   ██║   
   112  ╚═╝  ╚═╝╚═════╝  ╚═════╝  ╚═════╝    ╚═╝   
   113  
   114  `, string(w.data))
   115  	w.Reset()
   116  }
   117  
   118  type testObj1 struct {
   119  	name string
   120  	arr  []testObj2
   121  	obj3 testObj3
   122  }
   123  
   124  type testObj2 struct {
   125  	age int
   126  }
   127  
   128  type testObj3 struct {
   129  	o float32
   130  }
   131  
   132  func TestXLogger_Zap_AllAPIs(t *testing.T) {
   133  	testcases := []struct {
   134  		name          string
   135  		encoder       logEncoderType
   136  		writer        logOutWriterType
   137  		defaultLogger bool
   138  		ctxM          map[string]string
   139  		clean         func()
   140  	}{
   141  		{
   142  			name:    "console json",
   143  			encoder: JSON,
   144  			writer:  StdOut,
   145  			ctxM: map[string]string{
   146  				"traceId": "TraceID",
   147  				"service": "Svc",
   148  			},
   149  		},
   150  		{
   151  			name:    "file json",
   152  			encoder: JSON,
   153  			writer:  File,
   154  			ctxM: map[string]string{
   155  				"traceId": "TraceID",
   156  				"service": "Svc",
   157  			},
   158  		},
   159  		{
   160  			name:    "console plaintext",
   161  			encoder: PlainText,
   162  			writer:  StdOut,
   163  			ctxM: map[string]string{
   164  				"traceId": "traceID",
   165  				"service": "svc",
   166  				"abc":     "",
   167  			},
   168  		},
   169  		{
   170  			name:          "console default json",
   171  			defaultLogger: true,
   172  		},
   173  		{
   174  			name:          "console default json2",
   175  			defaultLogger: true,
   176  			ctxM: map[string]string{
   177  				"traceId": "",
   178  				"service": "",
   179  				"":        "",
   180  				"abc":     "_",
   181  			},
   182  		},
   183  	}
   184  	for _, tc := range testcases {
   185  		t.Run(tc.name, func(t *testing.T) {
   186  			var opts []XLoggerOption
   187  			if tc.defaultLogger {
   188  				opts = []XLoggerOption{}
   189  			} else {
   190  				opts = append(opts,
   191  					WithXLoggerContext(context.TODO()),
   192  					WithXLoggerLevel(LogLevelDebug),
   193  					WithXLoggerEncoder(tc.encoder),
   194  					WithXLoggerTimeEncoder(zapcore.ISO8601TimeEncoder),
   195  					WithXLoggerLevelEncoder(zapcore.CapitalLevelEncoder),
   196  					func() XLoggerOption {
   197  						switch tc.writer {
   198  						case StdOut:
   199  							return WithXLoggerStdOutWriter()
   200  						case File:
   201  							nano, err := id.ClassicNanoID(6)
   202  							require.NoError(t, err)
   203  							rngLogSuffix := "_" + nano() + "_xlog"
   204  							cfg := &FileCoreConfig{
   205  								FilePath:                os.TempDir(),
   206  								Filename:                filepath.Base(os.Args[0]) + rngLogSuffix + ".log",
   207  								FileCompressible:        false,
   208  								FileMaxBackups:          4,
   209  								FileMaxAge:              "3day",
   210  								FileMaxSize:             "1KB",
   211  								FileRotateEnable:        true,
   212  								FileBufferSize:          "1KB",
   213  								FileBufferFlushInterval: 500,
   214  							}
   215  							tc.clean = func() {
   216  								removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log")
   217  								require.GreaterOrEqual(t, removed, cfg.FileMaxBackups+1)
   218  							}
   219  							return WithXLoggerFileWriter(cfg)
   220  						default:
   221  						}
   222  						return nil
   223  					}(),
   224  				)
   225  			}
   226  			if tc.ctxM != nil {
   227  				for k, v := range tc.ctxM {
   228  					opts = append(opts, WithXLoggerContextFieldExtract(k, v))
   229  				}
   230  			}
   231  			logger := NewXLogger(opts...)
   232  
   233  			ctx := context.TODO()
   234  			ctx = context.WithValue(ctx, "traceId", "1234567890")
   235  			ctx = context.WithValue(ctx, "service", "xboot")
   236  
   237  			logger.Debug("debug message 1")
   238  			logger.Log(zapcore.DebugLevel, "debug message 1-1")
   239  			logger.DebugContext(ctx, "debug message 2")
   240  			logger.Info("info message 1")
   241  			logger.InfoContext(ctx, "info message 2")
   242  			logger.Warn("warn message 1")
   243  			logger.WarnContext(ctx, "warn message 2")
   244  			err1 := infra.WrapErrorStack(errors.New("error 1"))
   245  			logger.Error(err1, "error message 1")
   246  			logger.ErrorContext(ctx, err1, "error message 2")
   247  			logger.ErrorStack(err1, "error message 1")
   248  			logger.ErrorStackContext(ctx, err1, "error message 2")
   249  
   250  			obj1 := testObj1{
   251  				name: "testObj1",
   252  				arr: []testObj2{
   253  					{age: 1},
   254  					{age: 2},
   255  				},
   256  				obj3: testObj3{o: 3.14},
   257  			}
   258  			field := zap.Object("testObj1", zapcore.ObjectMarshalerFunc(
   259  				func(oe zapcore.ObjectEncoder) error {
   260  					oe.AddString("name", obj1.name)
   261  					if err := oe.AddArray("arr", zapcore.ArrayMarshalerFunc(
   262  						func(ae zapcore.ArrayEncoder) error {
   263  							for _, v := range obj1.arr {
   264  								if err := ae.AppendObject(zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error {
   265  									enc.AddInt("age", v.age)
   266  									return nil
   267  								})); err != nil {
   268  									return err
   269  								}
   270  							}
   271  							return nil
   272  						})); err != nil {
   273  						return err
   274  					}
   275  					if err := oe.AddObject("obj3", zapcore.ObjectMarshalerFunc(
   276  						func(oe zapcore.ObjectEncoder) error {
   277  							oe.AddFloat32("o", obj1.obj3.o)
   278  							return nil
   279  						})); err != nil {
   280  						return err
   281  					}
   282  					return nil
   283  				}))
   284  			logger.Info("info message 3", field)
   285  			logger.InfoContext(ctx, "info message 4", field)
   286  
   287  			logger.IncreaseLogLevel(zapcore.WarnLevel)
   288  			require.Equal(t, zapcore.WarnLevel.String(), logger.Level())
   289  			logger.Logf(getLogLevelOrDefault(""), "unprintable debug message 3")
   290  			logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "unprintable debug message 4")
   291  			logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "unprintable info message 5")
   292  			logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "printable warn message 3")
   293  			logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "printable error message 3")
   294  			logger.ErrorStackf(err1, "error message 4")
   295  
   296  			logger.IncreaseLogLevel(zapcore.DebugLevel)
   297  			require.Equal(t, zapcore.DebugLevel.String(), logger.Level())
   298  			logger.Logf(getLogLevelOrDefault(""), "dynamic printable debug message 4")
   299  			logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "dynamic printable debug message 5")
   300  			logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "dynamic printable info message 6")
   301  			logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "dynamic printable warn message 4")
   302  			logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "dynamic printable error message 4")
   303  			logger.ErrorStackf(err1, "error message 5")
   304  
   305  			logger.IncreaseLogLevel(zapcore.WarnLevel)
   306  			require.Equal(t, zapcore.WarnLevel.String(), logger.Level())
   307  			logger.Logf(getLogLevelOrDefault(""), "unprintable debug message 5")
   308  			logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "unprintable debug message 6")
   309  			logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "unprintable info message 7")
   310  			logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "printable warn message 5")
   311  			logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "printable error message 5")
   312  			logger.ErrorStackf(err1, "error message 6")
   313  
   314  			err := logger.Sync()
   315  			if err != nil {
   316  				t.Log(err)
   317  			}
   318  			logger.Close()
   319  			if tc.clean != nil {
   320  				tc.clean()
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  func TestXLogger_Zap_DataRace(t *testing.T) {
   327  	logger := NewXLogger()
   328  	lvls := []zapcore.Level{
   329  		zapcore.DebugLevel,
   330  		zapcore.InfoLevel,
   331  		zapcore.WarnLevel,
   332  		zapcore.ErrorLevel,
   333  	}
   334  	n := int32(len(lvls))
   335  	var wg sync.WaitGroup
   336  	total := 10
   337  	wg.Add(total)
   338  	for i := 0; i < total; i++ {
   339  		go func(i int) {
   340  			for j := 0; j < 100; j++ {
   341  				rng := randv2.Int32N(n)
   342  				if i*total+j == 666 {
   343  					logger.IncreaseLogLevel(lvls[rng])
   344  				}
   345  				logger.Logf(lvls[rng], "message i: %d; j: %d", i, j)
   346  			}
   347  			wg.Done()
   348  		}(i)
   349  	}
   350  	wg.Wait()
   351  	_ = logger.Sync()
   352  }
   353  
   354  func BenchmarkXLogger_Zap(b *testing.B) {
   355  	logger := NewXLogger()
   356  	b.ResetTimer()
   357  	for i := 0; i < b.N; i++ {
   358  		logger.Info("message")
   359  	}
   360  	b.ReportAllocs()
   361  }