1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,
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,
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 = ""
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
179 expectSampled := 103
180
181 logOut := filepath.Join(t.TempDir(), "test.log")
182 cfg.OutputPaths = []string{logOut}
183 cfg.EncoderConfig.TimeKey = ""
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