...

Source file src/go.uber.org/zap/config_test.go

Documentation: go.uber.org/zap

     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  	"os"
    25  	"path/filepath"
    26  	"sync/atomic"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  	"go.uber.org/zap/zapcore"
    32  )
    33  
    34  func TestConfig(t *testing.T) {
    35  	tests := []struct {
    36  		desc     string
    37  		cfg      Config
    38  		expectN  int64
    39  		expectRe string
    40  	}{
    41  		{
    42  			desc:    "production",
    43  			cfg:     NewProductionConfig(),
    44  			expectN: 2 + 100 + 1, // 2 from initial logs, 100 initial sampled logs, 1 from off-by-one in sampler
    45  			expectRe: `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" +
    46  				`{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n",
    47  		},
    48  		{
    49  			desc:    "development",
    50  			cfg:     NewDevelopmentConfig(),
    51  			expectN: 3 + 200, // 3 initial logs, all 200 subsequent logs
    52  			expectRe: "DEBUG\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tdebug\t" + `{"k": "v", "z": "zz"}` + "\n" +
    53  				"INFO\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tinfo\t" + `{"k": "v", "z": "zz"}` + "\n" +
    54  				"WARN\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\twarn\t" + `{"k": "v", "z": "zz"}` + "\n" +
    55  				`go.uber.org/zap.TestConfig.\w+`,
    56  		},
    57  	}
    58  
    59  	for _, tt := range tests {
    60  		t.Run(tt.desc, func(t *testing.T) {
    61  			logOut := filepath.Join(t.TempDir(), "test.log")
    62  
    63  			tt.cfg.OutputPaths = []string{logOut}
    64  			tt.cfg.EncoderConfig.TimeKey = "" // no timestamps in tests
    65  			tt.cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"}
    66  
    67  			hook, count := makeCountingHook()
    68  			logger, err := tt.cfg.Build(Hooks(hook))
    69  			require.NoError(t, err, "Unexpected error constructing logger.")
    70  
    71  			logger.Debug("debug")
    72  			logger.Info("info")
    73  			logger.Warn("warn")
    74  
    75  			byteContents, err := os.ReadFile(logOut)
    76  			require.NoError(t, err, "Couldn't read log contents from temp file.")
    77  			logs := string(byteContents)
    78  			assert.Regexp(t, tt.expectRe, logs, "Unexpected log output.")
    79  
    80  			for i := 0; i < 200; i++ {
    81  				logger.Info("sampling")
    82  			}
    83  			assert.Equal(t, tt.expectN, count.Load(), "Hook called an unexpected number of times.")
    84  		})
    85  	}
    86  }
    87  
    88  func TestConfigWithInvalidPaths(t *testing.T) {
    89  	tests := []struct {
    90  		desc      string
    91  		output    string
    92  		errOutput string
    93  	}{
    94  		{"output directory doesn't exist", "/tmp/not-there/foo.log", "stderr"},
    95  		{"error output directory doesn't exist", "stdout", "/tmp/not-there/foo-errors.log"},
    96  		{"neither output directory exists", "/tmp/not-there/foo.log", "/tmp/not-there/foo-errors.log"},
    97  	}
    98  
    99  	for _, tt := range tests {
   100  		t.Run(tt.desc, func(t *testing.T) {
   101  			cfg := NewProductionConfig()
   102  			cfg.OutputPaths = []string{tt.output}
   103  			cfg.ErrorOutputPaths = []string{tt.errOutput}
   104  			_, err := cfg.Build()
   105  			assert.Error(t, err, "Expected an error opening a non-existent directory.")
   106  		})
   107  	}
   108  }
   109  
   110  func TestConfigWithMissingAttributes(t *testing.T) {
   111  	tests := []struct {
   112  		desc      string
   113  		cfg       Config
   114  		expectErr string
   115  	}{
   116  		{
   117  			desc: "missing level",
   118  			cfg: Config{
   119  				Encoding: "json",
   120  			},
   121  			expectErr: "missing Level",
   122  		},
   123  		{
   124  			desc: "missing encoder time in encoder config",
   125  			cfg: Config{
   126  				Level:    NewAtomicLevelAt(zapcore.InfoLevel),
   127  				Encoding: "json",
   128  				EncoderConfig: zapcore.EncoderConfig{
   129  					MessageKey: "msg",
   130  					TimeKey:    "ts",
   131  				},
   132  			},
   133  			expectErr: "missing EncodeTime in EncoderConfig",
   134  		},
   135  	}
   136  
   137  	for _, tt := range tests {
   138  		t.Run(tt.desc, func(t *testing.T) {
   139  			cfg := tt.cfg
   140  			_, err := cfg.Build()
   141  			assert.EqualError(t, err, tt.expectErr)
   142  		})
   143  	}
   144  }
   145  
   146  func makeSamplerCountingHook() (h func(zapcore.Entry, zapcore.SamplingDecision),
   147  	dropped, sampled *atomic.Int64,
   148  ) {
   149  	dropped = new(atomic.Int64)
   150  	sampled = new(atomic.Int64)
   151  	h = func(_ zapcore.Entry, dec zapcore.SamplingDecision) {
   152  		if dec&zapcore.LogDropped > 0 {
   153  			dropped.Add(1)
   154  		} else if dec&zapcore.LogSampled > 0 {
   155  			sampled.Add(1)
   156  		}
   157  	}
   158  	return h, dropped, sampled
   159  }
   160  
   161  func TestConfigWithSamplingHook(t *testing.T) {
   162  	shook, dcount, scount := makeSamplerCountingHook()
   163  	cfg := Config{
   164  		Level:       NewAtomicLevelAt(InfoLevel),
   165  		Development: false,
   166  		Sampling: &SamplingConfig{
   167  			Initial:    100,
   168  			Thereafter: 100,
   169  			Hook:       shook,
   170  		},
   171  		Encoding:         "json",
   172  		EncoderConfig:    NewProductionEncoderConfig(),
   173  		OutputPaths:      []string{"stderr"},
   174  		ErrorOutputPaths: []string{"stderr"},
   175  	}
   176  	expectRe := `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" +
   177  		`{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n"
   178  	expectDropped := 99  // 200 - 100 initial - 1 thereafter
   179  	expectSampled := 103 // 2 from initial + 100 + 1 thereafter
   180  
   181  	logOut := filepath.Join(t.TempDir(), "test.log")
   182  	cfg.OutputPaths = []string{logOut}
   183  	cfg.EncoderConfig.TimeKey = "" // no timestamps in tests
   184  	cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"}
   185  
   186  	logger, err := cfg.Build()
   187  	require.NoError(t, err, "Unexpected error constructing logger.")
   188  
   189  	logger.Debug("debug")
   190  	logger.Info("info")
   191  	logger.Warn("warn")
   192  
   193  	byteContents, err := os.ReadFile(logOut)
   194  	require.NoError(t, err, "Couldn't read log contents from temp file.")
   195  	logs := string(byteContents)
   196  	assert.Regexp(t, expectRe, logs, "Unexpected log output.")
   197  
   198  	for i := 0; i < 200; i++ {
   199  		logger.Info("sampling")
   200  	}
   201  	assert.Equal(t, int64(expectDropped), dcount.Load())
   202  	assert.Equal(t, int64(expectSampled), scount.Load())
   203  }
   204  

View as plain text