github.com/Laisky/zap@v1.27.0/global_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package zap
    22  
    23  import (
    24  	"log"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/Laisky/zap/internal/exit"
    31  	"github.com/Laisky/zap/internal/ztest"
    32  
    33  	"github.com/Laisky/zap/zapcore"
    34  	"github.com/Laisky/zap/zaptest/observer"
    35  
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  func TestReplaceGlobals(t *testing.T) {
    41  	initialL := *L()
    42  	initialS := *S()
    43  
    44  	withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) {
    45  		L().Info("no-op")
    46  		S().Info("no-op")
    47  		assert.Equal(t, 0, logs.Len(), "Expected initial logs to go to default no-op global.")
    48  
    49  		defer ReplaceGlobals(l)()
    50  
    51  		L().Info("captured")
    52  		S().Info("captured")
    53  		expected := observer.LoggedEntry{
    54  			Entry:   zapcore.Entry{Message: "captured"},
    55  			Context: []Field{},
    56  		}
    57  		assert.Equal(
    58  			t,
    59  			[]observer.LoggedEntry{expected, expected},
    60  			logs.AllUntimed(),
    61  			"Unexpected global log output.",
    62  		)
    63  	})
    64  
    65  	assert.Equal(t, initialL, *L(), "Expected func returned from ReplaceGlobals to restore initial L.")
    66  	assert.Equal(t, initialS, *S(), "Expected func returned from ReplaceGlobals to restore initial S.")
    67  }
    68  
    69  func TestGlobalsConcurrentUse(t *testing.T) {
    70  	var (
    71  		stop atomic.Bool
    72  		wg   sync.WaitGroup
    73  	)
    74  
    75  	for i := 0; i < 100; i++ {
    76  		wg.Add(2)
    77  		go func() {
    78  			for !stop.Load() {
    79  				ReplaceGlobals(NewNop())
    80  			}
    81  			wg.Done()
    82  		}()
    83  		go func() {
    84  			for !stop.Load() {
    85  				L().With(Int("foo", 42)).Named("main").WithOptions(Development()).Info("")
    86  				S().Info("")
    87  			}
    88  			wg.Done()
    89  		}()
    90  	}
    91  
    92  	ztest.Sleep(100 * time.Millisecond)
    93  	// CAS loop to toggle the current value.
    94  	for old := stop.Load(); !stop.CompareAndSwap(old, !old); {
    95  		old = stop.Load()
    96  	}
    97  	wg.Wait()
    98  }
    99  
   100  func TestNewStdLog(t *testing.T) {
   101  	withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   102  		std := NewStdLog(l)
   103  		std.Print("redirected")
   104  		checkStdLogMessage(t, "redirected", logs)
   105  	})
   106  }
   107  
   108  func TestNewStdLogAt(t *testing.T) {
   109  	// include DPanicLevel here, but do not include Development in options
   110  	levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel}
   111  	for _, level := range levels {
   112  		withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   113  			std, err := NewStdLogAt(l, level)
   114  			require.NoError(t, err, "Unexpected error.")
   115  			std.Print("redirected")
   116  			checkStdLogMessage(t, "redirected", logs)
   117  		})
   118  	}
   119  }
   120  
   121  func TestNewStdLogAtPanics(t *testing.T) {
   122  	// include DPanicLevel here and enable Development in options
   123  	levels := []zapcore.Level{DPanicLevel, PanicLevel}
   124  	for _, level := range levels {
   125  		withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) {
   126  			std, err := NewStdLogAt(l, level)
   127  			require.NoError(t, err, "Unexpected error")
   128  			assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.")
   129  			checkStdLogMessage(t, "redirected", logs)
   130  		})
   131  	}
   132  }
   133  
   134  func TestNewStdLogAtFatal(t *testing.T) {
   135  	withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   136  		stub := exit.WithStub(func() {
   137  			std, err := NewStdLogAt(l, FatalLevel)
   138  			require.NoError(t, err, "Unexpected error.")
   139  			std.Print("redirected")
   140  			checkStdLogMessage(t, "redirected", logs)
   141  		})
   142  		assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.")
   143  		stub.Unstub()
   144  	})
   145  }
   146  
   147  func TestNewStdLogAtInvalid(t *testing.T) {
   148  	_, err := NewStdLogAt(NewNop(), zapcore.Level(99))
   149  	assert.ErrorContains(t, err, "99", "Expected level code in error message")
   150  }
   151  
   152  func TestRedirectStdLog(t *testing.T) {
   153  	initialFlags := log.Flags()
   154  	initialPrefix := log.Prefix()
   155  
   156  	withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) {
   157  		defer RedirectStdLog(l)()
   158  		log.Print("redirected")
   159  
   160  		assert.Equal(t, []observer.LoggedEntry{{
   161  			Entry:   zapcore.Entry{Message: "redirected"},
   162  			Context: []Field{},
   163  		}}, logs.AllUntimed(), "Unexpected global log output.")
   164  	})
   165  
   166  	assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.")
   167  	assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.")
   168  }
   169  
   170  func TestRedirectStdLogCaller(t *testing.T) {
   171  	withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   172  		defer RedirectStdLog(l)()
   173  		log.Print("redirected")
   174  		entries := logs.All()
   175  		require.Len(t, entries, 1, "Unexpected number of logs.")
   176  		assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.")
   177  	})
   178  }
   179  
   180  func TestRedirectStdLogAt(t *testing.T) {
   181  	initialFlags := log.Flags()
   182  	initialPrefix := log.Prefix()
   183  
   184  	// include DPanicLevel here, but do not include Development in options
   185  	levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel}
   186  	for _, level := range levels {
   187  		withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) {
   188  			restore, err := RedirectStdLogAt(l, level)
   189  			require.NoError(t, err, "Unexpected error.")
   190  			defer restore()
   191  			log.Print("redirected")
   192  
   193  			assert.Equal(t, []observer.LoggedEntry{{
   194  				Entry:   zapcore.Entry{Level: level, Message: "redirected"},
   195  				Context: []Field{},
   196  			}}, logs.AllUntimed(), "Unexpected global log output.")
   197  		})
   198  	}
   199  
   200  	assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.")
   201  	assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.")
   202  }
   203  
   204  func TestRedirectStdLogAtCaller(t *testing.T) {
   205  	// include DPanicLevel here, but do not include Development in options
   206  	levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel}
   207  	for _, level := range levels {
   208  		withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   209  			restore, err := RedirectStdLogAt(l, level)
   210  			require.NoError(t, err, "Unexpected error.")
   211  			defer restore()
   212  			log.Print("redirected")
   213  			entries := logs.All()
   214  			require.Len(t, entries, 1, "Unexpected number of logs.")
   215  			assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.")
   216  		})
   217  	}
   218  }
   219  
   220  func TestRedirectStdLogAtPanics(t *testing.T) {
   221  	initialFlags := log.Flags()
   222  	initialPrefix := log.Prefix()
   223  
   224  	// include DPanicLevel here and enable Development in options
   225  	levels := []zapcore.Level{DPanicLevel, PanicLevel}
   226  	for _, level := range levels {
   227  		withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) {
   228  			restore, err := RedirectStdLogAt(l, level)
   229  			require.NoError(t, err, "Unexpected error.")
   230  			defer restore()
   231  			assert.Panics(t, func() { log.Print("redirected") }, "Expected log to panic.")
   232  			checkStdLogMessage(t, "redirected", logs)
   233  		})
   234  	}
   235  
   236  	assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.")
   237  	assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.")
   238  }
   239  
   240  func TestRedirectStdLogAtFatal(t *testing.T) {
   241  	initialFlags := log.Flags()
   242  	initialPrefix := log.Prefix()
   243  
   244  	withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) {
   245  		stub := exit.WithStub(func() {
   246  			restore, err := RedirectStdLogAt(l, FatalLevel)
   247  			require.NoError(t, err, "Unexpected error.")
   248  			defer restore()
   249  			log.Print("redirected")
   250  			checkStdLogMessage(t, "redirected", logs)
   251  		})
   252  		assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.")
   253  		stub.Unstub()
   254  	})
   255  
   256  	assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.")
   257  	assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.")
   258  }
   259  
   260  func TestRedirectStdLogAtInvalid(t *testing.T) {
   261  	restore, err := RedirectStdLogAt(NewNop(), zapcore.Level(99))
   262  	defer func() {
   263  		if restore != nil {
   264  			restore()
   265  		}
   266  	}()
   267  	assert.ErrorContains(t, err, "99", "Expected level code in error message")
   268  }
   269  
   270  func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) {
   271  	require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged")
   272  	entry := logs.AllUntimed()[0]
   273  	assert.Equal(t, []Field{}, entry.Context, "Unexpected entry context.")
   274  	assert.Equal(t, "redirected", entry.Message, "Unexpected entry message.")
   275  	assert.Regexp(
   276  		t,
   277  		`/global_test.go:\d+$`,
   278  		entry.Caller.String(),
   279  		"Unexpected caller annotation.",
   280  	)
   281  }