github.com/GuanceCloud/cliutils@v1.1.21/logger/logger_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package logger
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  	"go.uber.org/zap/zapcore"
    21  )
    22  
    23  func BenchmarkMuitiLogs(b *testing.B) {
    24  	opt := &Option{
    25  		Path:  "/dev/null",
    26  		Level: INFO,
    27  		Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER,
    28  	}
    29  
    30  	if err := InitRoot(opt); err != nil {
    31  		b.Fatal(err)
    32  	}
    33  
    34  	for i := 0; i < b.N; i++ {
    35  		l := SLogger(fmt.Sprintf("bench-multi-%d", i))
    36  
    37  		l.Debug("debug message")
    38  		l.Info("info message")
    39  		l.Warn("warn message")
    40  
    41  		l.Debugf("debugf message: %s", "hello debug")
    42  		l.Infof("info message: %s", "hello info")
    43  		l.Warnf("warn message: %s", "hello warn")
    44  	}
    45  }
    46  
    47  func BenchmarkBasic(b *testing.B) {
    48  	opt := &Option{
    49  		Path:  "/dev/null",
    50  		Level: INFO,
    51  		Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER,
    52  	}
    53  
    54  	if err := InitRoot(opt); err != nil {
    55  		b.Fatal(err)
    56  	}
    57  
    58  	l := SLogger("bench")
    59  	for i := 0; i < b.N; i++ {
    60  		l.Debug("debug message")
    61  		l.Info("info message")
    62  		l.Warn("warn message")
    63  
    64  		l.Debugf("debugf message: %s", "hello debug")
    65  		l.Infof("info message: %s", "hello info")
    66  		l.Warnf("warn message: %s", "hello warn")
    67  	}
    68  }
    69  
    70  func TestLoggerSideEffect(t *testing.T) {
    71  	type abc struct {
    72  		i int
    73  	}
    74  
    75  	opt := &Option{
    76  		Level: INFO,
    77  		Flags: OPT_DEFAULT,
    78  	}
    79  
    80  	f := func(x *abc) string {
    81  		x.i++
    82  		return fmt.Sprintf("%d", x.i)
    83  	}
    84  
    85  	if err := InitRoot(opt); err != nil {
    86  		t.Error(err)
    87  	}
    88  
    89  	l := SLogger("TestLoggerSideEffect")
    90  
    91  	x := &abc{}
    92  	l.Debugf("%+#v", f(x)) // under info level, on debug, the f() still effected
    93  
    94  	assert.Equal(t, 1, x.i)
    95  }
    96  
    97  func TestJsonLogging(t *testing.T) {
    98  	opt := &Option{
    99  		Path:  "abc.json",
   100  		Level: DEBUG,
   101  		Flags: OPT_SHORT_CALLER | OPT_ROTATE,
   102  	}
   103  
   104  	if err := InitRoot(opt); err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	l := SLogger("json")
   109  	l.Debug("this is the json message with short path")
   110  	showLog(opt.Path)
   111  
   112  	// check json elements
   113  	j, err := os.ReadFile(opt.Path)
   114  	if err != nil {
   115  		t.Error(err)
   116  	}
   117  
   118  	var logdata map[string]string
   119  
   120  	if err := json.Unmarshal(j, &logdata); err != nil {
   121  		t.Error(err)
   122  	}
   123  
   124  	for _, k := range []string{
   125  		NameKeyMod,
   126  		NameKeyMsg,
   127  		NameKeyLevel,
   128  		NameKeyTime,
   129  		NameKeyPos,
   130  	} {
   131  		_, ok := logdata[k]
   132  		assert.True(t, ok)
   133  	}
   134  
   135  	Reset()
   136  
   137  	opt1 := &Option{
   138  		Path:  "abc.log",
   139  		Level: DEBUG,
   140  		Flags: OPT_ENC_CONSOLE | OPT_ROTATE,
   141  	}
   142  
   143  	if err := InitRoot(opt1); err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	l2 := SLogger("log")
   148  	l2.Debug("this is the raw message with full path")
   149  	showLog(opt1.Path)
   150  
   151  	os.Remove(opt.Path)
   152  	os.Remove(opt1.Path)
   153  }
   154  
   155  func TestEnvLogPath(t *testing.T) {
   156  	cases := []struct {
   157  		name    string
   158  		envPath string
   159  		msg     string
   160  		fail    bool
   161  	}{
   162  		{
   163  			name:    "stdout",
   164  			envPath: "",
   165  			msg:     "this is debug log",
   166  		},
   167  		{
   168  			name:    "windows-nul",
   169  			envPath: "nul",
   170  		},
   171  		{
   172  			name:    "unix-null",
   173  			envPath: "/dev/null",
   174  		},
   175  	}
   176  
   177  	for _, tc := range cases {
   178  		t.Run(tc.name, func(t *testing.T) {
   179  			Reset()
   180  			os.Clearenv()
   181  
   182  			if err := os.Setenv("LOGGER_PATH", tc.envPath); err != nil {
   183  				t.Fatal(err)
   184  			}
   185  
   186  			opt := &Option{Path: "" /* path not set, use env only */}
   187  
   188  			err := InitRoot(opt)
   189  			if tc.fail {
   190  				assert.True(t, err != nil)
   191  				t.Logf("expect error: %s", err)
   192  			} else {
   193  				assert.NoError(t, err)
   194  			}
   195  
   196  			l := SLogger(tc.name)
   197  			l.Debug(tc.msg)
   198  		})
   199  	}
   200  }
   201  
   202  func TestLogAppend(t *testing.T) {
   203  	Reset()
   204  
   205  	f := "TestLogAppend.log"
   206  	opt := &Option{
   207  		Path:  f,
   208  		Level: DEBUG,
   209  		Flags: OPT_DEFAULT,
   210  	}
   211  
   212  	defer os.Remove(f)
   213  
   214  	if err := InitRoot(opt); err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	l := SLogger("test1")
   219  	l.Debug("this is the first time logging")
   220  
   221  	Close()
   222  
   223  	if err := InitRoot(opt); err != nil {
   224  		t.Fatal(err)
   225  	}
   226  
   227  	l = SLogger("test1")
   228  	l.Debug("this is the second append logging")
   229  
   230  	Close()
   231  
   232  	assert.Equal(t, 2, logLines(opt.Path))
   233  	showLog(opt.Path)
   234  }
   235  
   236  func TestTotalSLoggers(t *testing.T) {
   237  	Reset()
   238  
   239  	f := "TestTotalSLoggers"
   240  	opt := &Option{
   241  		Path:  f,
   242  		Level: DEBUG,
   243  		Flags: OPT_DEFAULT,
   244  	}
   245  
   246  	defer os.Remove(f)
   247  
   248  	if err := InitRoot(opt); err != nil {
   249  		t.Fatal(err)
   250  	}
   251  
   252  	n := int64(1000)
   253  
   254  	for i := int64(0); i < n; i++ {
   255  		_ = SLogger(fmt.Sprintf("slogger-%d", i))
   256  	}
   257  
   258  	// should not create new SLogger any more
   259  	for i := int64(0); i < n; i++ {
   260  		_ = SLogger(fmt.Sprintf("slogger-%d", i))
   261  	}
   262  
   263  	total := TotalSLoggers()
   264  
   265  	assert.Equalf(t, n, total, fmt.Sprintf("%d != %d", n, total))
   266  }
   267  
   268  func TestInitRoot(t *testing.T) {
   269  	Reset()
   270  
   271  	cases := []struct {
   272  		name        string
   273  		opt         *Option
   274  		logs        [][2]string
   275  		color, fail bool
   276  	}{
   277  		{
   278  			name: "stdout-log-no-color",
   279  			opt: &Option{
   280  				Level: INFO,
   281  				Flags: (OPT_DEFAULT | OPT_STDOUT),
   282  			},
   283  			logs: [][2]string{
   284  				{INFO, "stdout info log"},
   285  				{DEBUG, "stdout debug log"},
   286  			},
   287  			fail: false,
   288  		},
   289  
   290  		{
   291  			name: "stdout-log-with-color",
   292  			opt: &Option{
   293  				Level: INFO,
   294  				Flags: (OPT_DEFAULT | OPT_STDOUT | OPT_COLOR),
   295  			},
   296  			logs: [][2]string{
   297  				{INFO, "stdout info log with color"},
   298  				{DEBUG, "stdout debug log with color"},
   299  			},
   300  			fail: false,
   301  		},
   302  
   303  		{
   304  			name: "normal case",
   305  			opt: &Option{
   306  				Path:  "0.log",
   307  				Level: DEBUG,
   308  				Flags: OPT_DEFAULT,
   309  			},
   310  			logs: [][2]string{
   311  				{DEBUG, "abc123"},
   312  				{INFO, "abc123"},
   313  				{WARN, "abc123"},
   314  			},
   315  			color: false,
   316  		},
   317  
   318  		{
   319  			name: "with color",
   320  			opt: &Option{
   321  				Path:  "1.log",
   322  				Level: DEBUG,
   323  				Flags: OPT_DEFAULT | OPT_COLOR,
   324  			},
   325  			logs: [][2]string{
   326  				{DEBUG, "abc123"},
   327  				{INFO, "abc123"},
   328  				{WARN, "abc123"},
   329  			},
   330  			color: true,
   331  		},
   332  
   333  		{
   334  			name: "stdout log with path set",
   335  			opt: &Option{
   336  				Path:  "2.log",
   337  				Level: DEBUG,
   338  				Flags: (OPT_DEFAULT | OPT_STDOUT),
   339  			},
   340  			logs: [][2]string{
   341  				{DEBUG, "abc123"},
   342  			},
   343  			fail: true,
   344  		},
   345  
   346  		{
   347  			name: "no flags",
   348  			opt: &Option{
   349  				Path:  "3.log",
   350  				Level: DEBUG,
   351  				Flags: OPT_ROTATE | OPT_ENC_CONSOLE,
   352  			},
   353  			logs: [][2]string{
   354  				{DEBUG, "abc123"},
   355  				{INFO, "abc123"},
   356  				{WARN, "abc123"},
   357  			},
   358  			color: false,
   359  		},
   360  	}
   361  
   362  	for idx, c := range cases {
   363  		t.Run(c.name, func(t *testing.T) {
   364  			err := InitRoot(c.opt)
   365  			l := SLogger(fmt.Sprintf("case-%d", idx))
   366  			if c.fail {
   367  				assert.Error(t, err)
   368  				t.Logf("[%d] expected failing", idx)
   369  				return
   370  			}
   371  
   372  			assert.NoError(t, err)
   373  
   374  			for _, arr := range c.logs {
   375  				switch arr[0] {
   376  				case DEBUG:
   377  					l.Debug(arr[1])
   378  				case INFO:
   379  					l.Info(arr[1])
   380  				case WARN:
   381  					l.Warn(arr[1])
   382  				case ERROR:
   383  					l.Error(arr[1])
   384  				default:
   385  					l.Debug(arr[1])
   386  				}
   387  			}
   388  
   389  			Reset() // reset root logger
   390  			if c.opt.Flags&OPT_STDOUT == 0 {
   391  				t.Logf("case %d on file: %s", idx, c.opt.Path)
   392  				assert.Equal(t, len(c.logs), logLines(c.opt.Path))
   393  				assert.Equal(t, c.color, colorExits(c.opt.Path))
   394  				showLog(c.opt.Path)
   395  				os.Remove(c.opt.Path)
   396  			}
   397  		})
   398  	}
   399  }
   400  
   401  func TestRotateOnDevNull(t *testing.T) {
   402  	MaxSize = 1 // set to 1MB
   403  
   404  	opt := &Option{
   405  		Path:  "/dev/null",
   406  		Level: INFO,
   407  		Flags: OPT_ENC_CONSOLE | OPT_SHORT_CALLER | OPT_ROTATE, // set rotate
   408  	}
   409  
   410  	assert.NoError(t, InitRoot(opt))
   411  
   412  	t.Logf("MaxSize: %d", MaxSize)
   413  
   414  	l := SLogger(t.Name())
   415  	logData := strings.Repeat("3.1415926x", 1024) // 10kb
   416  	i := 0
   417  	for {
   418  		l.Info(logData)
   419  		i++
   420  		if i >= 200 { // 2MB
   421  			break
   422  		}
   423  	}
   424  }
   425  
   426  type BufferSync struct {
   427  	io.ReadWriter
   428  }
   429  
   430  func (b *BufferSync) Sync() error {
   431  	return nil
   432  }
   433  
   434  func TestTrace(t *testing.T) {
   435  	tests := []struct {
   436  		name            string
   437  		enabled         bool
   438  		traceLoggerName string
   439  		spanLoggerName  string
   440  		message         string
   441  		extractTrace    ExtractTrace
   442  		expectedOutput  string
   443  	}{
   444  		{
   445  			name:            "parse trace",
   446  			enabled:         true,
   447  			traceLoggerName: "traceID",
   448  			spanLoggerName:  "span_id",
   449  			message:         "test",
   450  			extractTrace: func(ctx context.Context) Trace {
   451  				return Trace{
   452  					SpanID:  "2",
   453  					TraceID: "1",
   454  				}
   455  			},
   456  			expectedOutput: `{"level":"DEBUG","message":"test","traceID":"1","span_id":"2"}` + "\n",
   457  		},
   458  		{
   459  			name:           "not trace",
   460  			enabled:        false,
   461  			message:        "test",
   462  			extractTrace:   nil,
   463  			expectedOutput: `{"level":"DEBUG","message":"test"}` + "\n",
   464  		},
   465  		{
   466  			name:            "not parse",
   467  			enabled:         true,
   468  			traceLoggerName: "traceID",
   469  			spanLoggerName:  "span_id",
   470  			message:         "test",
   471  			extractTrace:    nil,
   472  			expectedOutput:  `{"level":"DEBUG","message":"test","traceID":"","span_id":""}` + "\n",
   473  		},
   474  	}
   475  
   476  	buf := bytes.NewBufferString("")
   477  	bsync := &BufferSync{buf}
   478  
   479  	for _, test := range tests {
   480  		t.Run(test.name, func(t *testing.T) {
   481  			ast := assert.New(t)
   482  			opt := make([]CtxOption, 0)
   483  			opt = append(opt,
   484  				WithTraceKey(test.traceLoggerName, test.spanLoggerName),
   485  				WithParseTrace(test.extractTrace),
   486  			)
   487  			if test.enabled {
   488  				opt = append(opt, EnableTrace())
   489  			}
   490  
   491  			opt = append(opt, WithZapCore(
   492  				zapcore.NewCore(
   493  					zapcore.NewJSONEncoder(zapcore.EncoderConfig{
   494  						MessageKey:  "message",
   495  						LevelKey:    "level",
   496  						EncodeLevel: zapcore.CapitalLevelEncoder,
   497  					}),
   498  					bsync,
   499  					zapcore.DebugLevel,
   500  				)))
   501  
   502  			logger := NewLoggerCtx(opt...)
   503  
   504  			ctx := context.Background()
   505  
   506  			logger.DebugfCtx(ctx, test.message)
   507  			re, err := io.ReadAll(bsync)
   508  			if err != nil {
   509  				log.Fatal(err)
   510  			}
   511  			ast.Equal(test.expectedOutput, string(re))
   512  		})
   513  	}
   514  }
   515  
   516  //nolint:forbidigo
   517  func showLog(f string) {
   518  	logdata, err := os.ReadFile(f)
   519  	if err != nil {
   520  		panic(err)
   521  	}
   522  
   523  	fmt.Printf("---------- %s ----------\n", f)
   524  	fmt.Println(string(logdata))
   525  }
   526  
   527  func colorExits(f string) bool {
   528  	logdata, err := os.ReadFile(f)
   529  	if err != nil {
   530  		panic(err)
   531  	}
   532  
   533  	// there should be `[0m` in log files if color enabled
   534  	return bytes.Contains(logdata, []byte("[0m"))
   535  }
   536  
   537  func logLines(f string) int {
   538  	logdata, err := os.ReadFile(f)
   539  	if err != nil {
   540  		panic(fmt.Sprintf("ReadFile(%s) failed: %s", f, err))
   541  	}
   542  
   543  	return len(bytes.Split(logdata, []byte("\n"))) - 1
   544  }