github.com/Laisky/zap@v1.27.0/config_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 "os" 25 "path/filepath" 26 "sync/atomic" 27 "testing" 28 29 "github.com/Laisky/zap/zapcore" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 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 `github.com/Laisky/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 }