123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- package logx
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "log"
- "os"
- "reflect"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- )
- var (
- s = []byte("Sending #11 notification (id: 1451875113812010473) in #1 connection")
- pool = make(chan []byte, 1)
- _ Writer = (*mockWriter)(nil)
- )
- type mockWriter struct {
- lock sync.Mutex
- builder strings.Builder
- }
- func (mw *mockWriter) Alert(v any) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelAlert, v)
- }
- func (mw *mockWriter) Debug(v any, fields ...LogField) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelDebug, v, fields...)
- }
- func (mw *mockWriter) Error(v any, fields ...LogField) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelError, v, fields...)
- }
- func (mw *mockWriter) Info(v any, fields ...LogField) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelInfo, v, fields...)
- }
- func (mw *mockWriter) Severe(v any) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelSevere, v)
- }
- func (mw *mockWriter) Slow(v any, fields ...LogField) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelSlow, v, fields...)
- }
- func (mw *mockWriter) Stack(v any) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelError, v)
- }
- func (mw *mockWriter) Stat(v any, fields ...LogField) {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- output(&mw.builder, levelStat, v, fields...)
- }
- func (mw *mockWriter) Close() error {
- return nil
- }
- func (mw *mockWriter) Contains(text string) bool {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- return strings.Contains(mw.builder.String(), text)
- }
- func (mw *mockWriter) Reset() {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- mw.builder.Reset()
- }
- func (mw *mockWriter) String() string {
- mw.lock.Lock()
- defer mw.lock.Unlock()
- return mw.builder.String()
- }
- func TestField(t *testing.T) {
- tests := []struct {
- name string
- f LogField
- want map[string]any
- }{
- {
- name: "error",
- f: Field("foo", errors.New("bar")),
- want: map[string]any{
- "foo": "bar",
- },
- },
- {
- name: "errors",
- f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
- want: map[string]any{
- "foo": []any{"bar", "baz"},
- },
- },
- {
- name: "strings",
- f: Field("foo", []string{"bar", "baz"}),
- want: map[string]any{
- "foo": []any{"bar", "baz"},
- },
- },
- {
- name: "duration",
- f: Field("foo", time.Second),
- want: map[string]any{
- "foo": "1s",
- },
- },
- {
- name: "durations",
- f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
- want: map[string]any{
- "foo": []any{"1s", "2s"},
- },
- },
- {
- name: "times",
- f: Field("foo", []time.Time{
- time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
- time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
- }),
- want: map[string]any{
- "foo": []any{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
- },
- },
- {
- name: "stringer",
- f: Field("foo", ValStringer{val: "bar"}),
- want: map[string]any{
- "foo": "bar",
- },
- },
- {
- name: "stringers",
- f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
- want: map[string]any{
- "foo": []any{"bar", "baz"},
- },
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- Infow("foo", test.f)
- validateFields(t, w.String(), test.want)
- })
- }
- }
- func TestFileLineFileMode(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- file, line := getFileLine()
- Error("anything")
- assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
- file, line = getFileLine()
- Errorf("anything %s", "format")
- assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
- }
- func TestFileLineConsoleMode(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- file, line := getFileLine()
- Error("anything")
- assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
- w.Reset()
- file, line = getFileLine()
- Errorf("anything %s", "format")
- assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
- }
- func TestStructedLogAlert(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelAlert, w, func(v ...any) {
- Alert(fmt.Sprint(v...))
- })
- }
- func TestStructedLogDebug(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelDebug, w, func(v ...any) {
- Debug(v...)
- })
- }
- func TestStructedLogDebugf(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelDebug, w, func(v ...any) {
- Debugf(fmt.Sprint(v...))
- })
- }
- func TestStructedLogDebugv(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelDebug, w, func(v ...any) {
- Debugv(fmt.Sprint(v...))
- })
- }
- func TestStructedLogDebugw(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelDebug, w, func(v ...any) {
- Debugw(fmt.Sprint(v...), Field("foo", time.Second))
- })
- }
- func TestStructedLogError(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelError, w, func(v ...any) {
- Error(v...)
- })
- }
- func TestStructedLogErrorf(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelError, w, func(v ...any) {
- Errorf("%s", fmt.Sprint(v...))
- })
- }
- func TestStructedLogErrorv(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelError, w, func(v ...any) {
- Errorv(fmt.Sprint(v...))
- })
- }
- func TestStructedLogErrorw(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelError, w, func(v ...any) {
- Errorw(fmt.Sprint(v...), Field("foo", "bar"))
- })
- }
- func TestStructedLogInfo(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelInfo, w, func(v ...any) {
- Info(v...)
- })
- }
- func TestStructedLogInfof(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelInfo, w, func(v ...any) {
- Infof("%s", fmt.Sprint(v...))
- })
- }
- func TestStructedLogInfov(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelInfo, w, func(v ...any) {
- Infov(fmt.Sprint(v...))
- })
- }
- func TestStructedLogInfow(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelInfo, w, func(v ...any) {
- Infow(fmt.Sprint(v...), Field("foo", "bar"))
- })
- }
- func TestStructedLogInfoConsoleAny(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLogConsole(t, w, func(v ...any) {
- old := atomic.LoadUint32(&encoding)
- atomic.StoreUint32(&encoding, plainEncodingType)
- defer func() {
- atomic.StoreUint32(&encoding, old)
- }()
- Infov(v)
- })
- }
- func TestStructedLogInfoConsoleAnyString(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLogConsole(t, w, func(v ...any) {
- old := atomic.LoadUint32(&encoding)
- atomic.StoreUint32(&encoding, plainEncodingType)
- defer func() {
- atomic.StoreUint32(&encoding, old)
- }()
- Infov(fmt.Sprint(v...))
- })
- }
- func TestStructedLogInfoConsoleAnyError(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLogConsole(t, w, func(v ...any) {
- old := atomic.LoadUint32(&encoding)
- atomic.StoreUint32(&encoding, plainEncodingType)
- defer func() {
- atomic.StoreUint32(&encoding, old)
- }()
- Infov(errors.New(fmt.Sprint(v...)))
- })
- }
- func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLogConsole(t, w, func(v ...any) {
- old := atomic.LoadUint32(&encoding)
- atomic.StoreUint32(&encoding, plainEncodingType)
- defer func() {
- atomic.StoreUint32(&encoding, old)
- }()
- Infov(ValStringer{
- val: fmt.Sprint(v...),
- })
- })
- }
- func TestStructedLogInfoConsoleText(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLogConsole(t, w, func(v ...any) {
- old := atomic.LoadUint32(&encoding)
- atomic.StoreUint32(&encoding, plainEncodingType)
- defer func() {
- atomic.StoreUint32(&encoding, old)
- }()
- Info(fmt.Sprint(v...))
- })
- }
- func TestStructedLogSlow(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSlow, w, func(v ...any) {
- Slow(v...)
- })
- }
- func TestStructedLogSlowf(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSlow, w, func(v ...any) {
- Slowf(fmt.Sprint(v...))
- })
- }
- func TestStructedLogSlowv(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSlow, w, func(v ...any) {
- Slowv(fmt.Sprint(v...))
- })
- }
- func TestStructedLogSloww(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSlow, w, func(v ...any) {
- Sloww(fmt.Sprint(v...), Field("foo", time.Second))
- })
- }
- func TestStructedLogStat(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelStat, w, func(v ...any) {
- Stat(v...)
- })
- }
- func TestStructedLogStatf(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelStat, w, func(v ...any) {
- Statf(fmt.Sprint(v...))
- })
- }
- func TestStructedLogSevere(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSevere, w, func(v ...any) {
- Severe(v...)
- })
- }
- func TestStructedLogSeveref(t *testing.T) {
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- doTestStructedLog(t, levelSevere, w, func(v ...any) {
- Severef(fmt.Sprint(v...))
- })
- }
- func TestStructedLogWithDuration(t *testing.T) {
- const message = "hello there"
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- WithDuration(time.Second).Info(message)
- var entry map[string]any
- if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
- t.Error(err)
- }
- assert.Equal(t, levelInfo, entry[levelKey])
- assert.Equal(t, message, entry[contentKey])
- assert.Equal(t, "1000.0ms", entry[durationKey])
- }
- func TestSetLevel(t *testing.T) {
- SetLevel(ErrorLevel)
- const message = "hello there"
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- Info(message)
- assert.Equal(t, 0, w.builder.Len())
- }
- func TestSetLevelTwiceWithMode(t *testing.T) {
- testModes := []string{
- "console",
- "volumn",
- "mode",
- }
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- for _, mode := range testModes {
- testSetLevelTwiceWithMode(t, mode, w)
- }
- }
- func TestSetLevelWithDuration(t *testing.T) {
- SetLevel(ErrorLevel)
- const message = "hello there"
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- WithDuration(time.Second).Info(message)
- assert.Equal(t, 0, w.builder.Len())
- }
- func TestErrorfWithWrappedError(t *testing.T) {
- SetLevel(ErrorLevel)
- const message = "there"
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- Errorf("hello %w", errors.New(message))
- assert.True(t, strings.Contains(w.String(), "hello there"))
- }
- func TestMustNil(t *testing.T) {
- Must(nil)
- }
- func TestSetup(t *testing.T) {
- defer func() {
- SetLevel(InfoLevel)
- atomic.StoreUint32(&encoding, jsonEncodingType)
- }()
- MustSetup(LogConf{
- ServiceName: "any",
- Mode: "console",
- TimeFormat: timeFormat,
- })
- MustSetup(LogConf{
- ServiceName: "any",
- Mode: "file",
- Path: os.TempDir(),
- })
- MustSetup(LogConf{
- ServiceName: "any",
- Mode: "volume",
- Path: os.TempDir(),
- })
- MustSetup(LogConf{
- ServiceName: "any",
- Mode: "console",
- TimeFormat: timeFormat,
- })
- MustSetup(LogConf{
- ServiceName: "any",
- Mode: "console",
- Encoding: plainEncoding,
- })
- defer os.RemoveAll("CD01CB7D-2705-4F3F-889E-86219BF56F10")
- assert.NotNil(t, setupWithVolume(LogConf{}))
- assert.Nil(t, setupWithVolume(LogConf{
- ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
- }))
- assert.Nil(t, setupWithVolume(LogConf{
- ServiceName: "CD01CB7D-2705-4F3F-889E-86219BF56F10",
- Rotation: sizeRotationRule,
- }))
- assert.NotNil(t, setupWithFiles(LogConf{}))
- assert.Nil(t, setupWithFiles(LogConf{
- ServiceName: "any",
- Path: os.TempDir(),
- Compress: true,
- KeepDays: 1,
- MaxBackups: 3,
- MaxSize: 1024 * 1024,
- }))
- setupLogLevel(LogConf{
- Level: levelInfo,
- })
- setupLogLevel(LogConf{
- Level: levelError,
- })
- setupLogLevel(LogConf{
- Level: levelSevere,
- })
- _, err := createOutput("")
- assert.NotNil(t, err)
- Disable()
- SetLevel(InfoLevel)
- atomic.StoreUint32(&encoding, jsonEncodingType)
- }
- func TestDisable(t *testing.T) {
- Disable()
- var opt logOptions
- WithKeepDays(1)(&opt)
- WithGzip()(&opt)
- WithMaxBackups(1)(&opt)
- WithMaxSize(1024)(&opt)
- assert.Nil(t, Close())
- assert.Nil(t, Close())
- }
- func TestDisableStat(t *testing.T) {
- DisableStat()
- const message = "hello there"
- w := new(mockWriter)
- old := writer.Swap(w)
- defer writer.Store(old)
- Stat(message)
- assert.Equal(t, 0, w.builder.Len())
- }
- func TestSetWriter(t *testing.T) {
- atomic.StoreUint32(&disableLog, 0)
- Reset()
- SetWriter(nopWriter{})
- assert.NotNil(t, writer.Load())
- assert.True(t, writer.Load() == nopWriter{})
- mocked := new(mockWriter)
- SetWriter(mocked)
- assert.Equal(t, mocked, writer.Load())
- }
- func TestWithGzip(t *testing.T) {
- fn := WithGzip()
- var opt logOptions
- fn(&opt)
- assert.True(t, opt.gzipEnabled)
- }
- func TestWithKeepDays(t *testing.T) {
- fn := WithKeepDays(1)
- var opt logOptions
- fn(&opt)
- assert.Equal(t, 1, opt.keepDays)
- }
- func BenchmarkCopyByteSliceAppend(b *testing.B) {
- for i := 0; i < b.N; i++ {
- var buf []byte
- buf = append(buf, getTimestamp()...)
- buf = append(buf, ' ')
- buf = append(buf, s...)
- _ = buf
- }
- }
- func BenchmarkCopyByteSliceAllocExactly(b *testing.B) {
- for i := 0; i < b.N; i++ {
- now := []byte(getTimestamp())
- buf := make([]byte, len(now)+1+len(s))
- n := copy(buf, now)
- buf[n] = ' '
- copy(buf[n+1:], s)
- }
- }
- func BenchmarkCopyByteSlice(b *testing.B) {
- var buf []byte
- for i := 0; i < b.N; i++ {
- buf = make([]byte, len(s))
- copy(buf, s)
- }
- fmt.Fprint(io.Discard, buf)
- }
- func BenchmarkCopyOnWriteByteSlice(b *testing.B) {
- var buf []byte
- for i := 0; i < b.N; i++ {
- size := len(s)
- buf = s[:size:size]
- }
- fmt.Fprint(io.Discard, buf)
- }
- func BenchmarkCacheByteSlice(b *testing.B) {
- for i := 0; i < b.N; i++ {
- dup := fetch()
- copy(dup, s)
- put(dup)
- }
- }
- func BenchmarkLogs(b *testing.B) {
- b.ReportAllocs()
- log.SetOutput(io.Discard)
- for i := 0; i < b.N; i++ {
- Info(i)
- }
- }
- func fetch() []byte {
- select {
- case b := <-pool:
- return b
- default:
- }
- return make([]byte, 4096)
- }
- func getFileLine() (string, int) {
- _, file, line, _ := runtime.Caller(1)
- short := file
- for i := len(file) - 1; i > 0; i-- {
- if file[i] == '/' {
- short = file[i+1:]
- break
- }
- }
- return short, line
- }
- func put(b []byte) {
- select {
- case pool <- b:
- default:
- }
- }
- func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...any)) {
- const message = "hello there"
- write(message)
- var entry map[string]any
- if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
- t.Error(err)
- }
- assert.Equal(t, level, entry[levelKey])
- val, ok := entry[contentKey]
- assert.True(t, ok)
- assert.True(t, strings.Contains(val.(string), message))
- }
- func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...any)) {
- const message = "hello there"
- write(message)
- assert.True(t, strings.Contains(w.String(), message))
- }
- func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
- writer.Store(nil)
- SetUp(LogConf{
- Mode: mode,
- Level: "debug",
- Path: "/dev/null",
- Encoding: plainEncoding,
- Stat: false,
- TimeFormat: time.RFC3339,
- })
- SetUp(LogConf{
- Mode: mode,
- Level: "info",
- Path: "/dev/null",
- })
- const message = "hello there"
- Info(message)
- assert.Equal(t, 0, w.builder.Len())
- Infof(message)
- assert.Equal(t, 0, w.builder.Len())
- ErrorStack(message)
- assert.Equal(t, 0, w.builder.Len())
- ErrorStackf(message)
- assert.Equal(t, 0, w.builder.Len())
- }
- type ValStringer struct {
- val string
- }
- func (v ValStringer) String() string {
- return v.val
- }
- func validateFields(t *testing.T, content string, fields map[string]any) {
- var m map[string]any
- if err := json.Unmarshal([]byte(content), &m); err != nil {
- t.Error(err)
- }
- for k, v := range fields {
- if reflect.TypeOf(v).Kind() == reflect.Slice {
- assert.EqualValues(t, v, m[k])
- } else {
- assert.Equal(t, v, m[k], content)
- }
- }
- }
|