google.golang.org/grpc@v1.72.2/grpclog/internal/loggerv2_test.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package internal 20 21 import ( 22 "bytes" 23 "encoding/json" 24 "fmt" 25 "io" 26 "os" 27 "reflect" 28 "regexp" 29 "strings" 30 "testing" 31 ) 32 33 // logFuncStr is a string used via testCheckLogContainsFuncStr to test the 34 // logger output. 35 const logFuncStr = "called-func" 36 37 func makeSprintfErr(t *testing.T) func(format string, a ...any) string { 38 return func(string, ...any) string { 39 t.Errorf("got: sprintf called on io.Discard logger, want: expensive sprintf to not be called for io.Discard") 40 return "" 41 } 42 } 43 44 func makeSprintErr(t *testing.T) func(a ...any) string { 45 return func(...any) string { 46 t.Errorf("got: sprint called on io.Discard logger, want: expensive sprint to not be called for io.Discard") 47 return "" 48 } 49 } 50 51 // checkLogContainsFuncStr checks that the logger buffer logBuf contains 52 // logFuncStr. 53 func checkLogContainsFuncStr(t *testing.T, logBuf []byte) { 54 if !bytes.Contains(logBuf, []byte(logFuncStr)) { 55 t.Errorf("got '%v', want logger func to be called and print '%v'", string(logBuf), logFuncStr) 56 } 57 } 58 59 // checkBufferWasWrittenAsExpected checks that the log buffer buf was written as expected, 60 // per the discard, logTYpe, msg, and isJSON arguments. 61 func checkBufferWasWrittenAsExpected(t *testing.T, buf *bytes.Buffer, discard bool, logType string, msg string, isJSON bool) { 62 bts, err := buf.ReadBytes('\n') 63 if discard { 64 if err == nil { 65 t.Fatalf("got '%v', want discard %v to not write", string(bts), logType) 66 } else if err != io.EOF { 67 t.Fatalf("got '%v', want discard %v buffer to be EOF", err, logType) 68 } 69 } else { 70 if err != nil { 71 t.Fatalf("got '%v', want non-discard %v to not error", err, logType) 72 } else if !bytes.Contains(bts, []byte(msg)) { 73 t.Fatalf("got '%v', want non-discard %v buffer contain message '%v'", string(bts), logType, msg) 74 } 75 if isJSON { 76 obj := map[string]string{} 77 if err := json.Unmarshal(bts, &obj); err != nil { 78 t.Fatalf("got '%v', want non-discard json %v to unmarshal", err, logType) 79 } else if _, ok := obj["severity"]; !ok { 80 t.Fatalf("got '%v', want non-discard json %v to have severity field", "missing severity", logType) 81 82 } else if jsonMsg, ok := obj["message"]; !ok { 83 t.Fatalf("got '%v', want non-discard json %v to have message field", "missing message", logType) 84 85 } else if !strings.Contains(jsonMsg, msg) { 86 t.Fatalf("got '%v', want non-discard json %v buffer contain message '%v'", string(bts), logType, msg) 87 } 88 } 89 } 90 } 91 92 // check if b is in the format of: 93 // 94 // 2017/04/07 14:55:42 WARNING: WARNING 95 func checkLogForSeverity(s int, b []byte) error { 96 expected := regexp.MustCompile(fmt.Sprintf(`^[0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} %s: %s\n$`, severityName[s], severityName[s])) 97 if m := expected.Match(b); !m { 98 return fmt.Errorf("got: %v, want string in format of: %v", string(b), severityName[s]+": 2016/10/05 17:09:26 "+severityName[s]) 99 } 100 return nil 101 } 102 103 func TestLoggerV2Severity(t *testing.T) { 104 buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)} 105 l := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{}) 106 107 l.Info(severityName[infoLog]) 108 l.Warning(severityName[warningLog]) 109 l.Error(severityName[errorLog]) 110 111 for i := 0; i < fatalLog; i++ { 112 buf := buffers[i] 113 // The content of info buffer should be something like: 114 // INFO: 2017/04/07 14:55:42 INFO 115 // WARNING: 2017/04/07 14:55:42 WARNING 116 // ERROR: 2017/04/07 14:55:42 ERROR 117 for j := i; j < fatalLog; j++ { 118 b, err := buf.ReadBytes('\n') 119 if err != nil { 120 t.Fatalf("level %d: %v", j, err) 121 } 122 if err := checkLogForSeverity(j, b); err != nil { 123 t.Fatal(err) 124 } 125 } 126 } 127 } 128 129 // TestLoggerV2PrintFuncDiscardOnlyInfo ensures that logs at the INFO level are 130 // discarded when set to io.Discard, while logs at other levels (WARN, ERROR) 131 // are still printed. It does this by using a custom error function that raises 132 // an error if the logger attempts to print at the INFO level, ensuring early 133 // return when io.Discard is used. 134 func TestLoggerV2PrintFuncDiscardOnlyInfo(t *testing.T) { 135 buffers := []*bytes.Buffer{nil, new(bytes.Buffer), new(bytes.Buffer)} 136 logger := NewLoggerV2(io.Discard, buffers[warningLog], buffers[errorLog], LoggerV2Config{}) 137 loggerTp := logger.(*loggerT) 138 139 // test that output doesn't call expensive printf funcs on an io.Discard logger 140 sprintf = makeSprintfErr(t) 141 sprint = makeSprintErr(t) 142 sprintln = makeSprintErr(t) 143 144 loggerTp.output(infoLog, "something") 145 146 sprintf = fmt.Sprintf 147 sprint = fmt.Sprint 148 sprintln = fmt.Sprintln 149 150 loggerTp.output(errorLog, logFuncStr) 151 warnB, err := buffers[warningLog].ReadBytes('\n') 152 if err != nil { 153 t.Fatalf("level %v: %v", warningLog, err) 154 } 155 checkLogContainsFuncStr(t, warnB) 156 157 errB, err := buffers[errorLog].ReadBytes('\n') 158 if err != nil { 159 t.Fatalf("level %v: %v", errorLog, err) 160 } 161 checkLogContainsFuncStr(t, errB) 162 } 163 164 func TestLoggerV2PrintFuncNoDiscard(t *testing.T) { 165 buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)} 166 logger := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{}) 167 loggerTp := logger.(*loggerT) 168 169 loggerTp.output(errorLog, logFuncStr) 170 171 infoB, err := buffers[infoLog].ReadBytes('\n') 172 if err != nil { 173 t.Fatalf("level %v: %v", infoLog, err) 174 } 175 checkLogContainsFuncStr(t, infoB) 176 177 warnB, err := buffers[warningLog].ReadBytes('\n') 178 if err != nil { 179 t.Fatalf("level %v: %v", warningLog, err) 180 } 181 checkLogContainsFuncStr(t, warnB) 182 183 errB, err := buffers[errorLog].ReadBytes('\n') 184 if err != nil { 185 t.Fatalf("level %v: %v", errorLog, err) 186 } 187 checkLogContainsFuncStr(t, errB) 188 } 189 190 // TestLoggerV2PrintFuncAllDiscard tests that discard loggers don't log. 191 func TestLoggerV2PrintFuncAllDiscard(t *testing.T) { 192 logger := NewLoggerV2(io.Discard, io.Discard, io.Discard, LoggerV2Config{}) 193 loggerTp := logger.(*loggerT) 194 195 sprintf = makeSprintfErr(t) 196 sprint = makeSprintErr(t) 197 sprintln = makeSprintErr(t) 198 199 // test that printFunc doesn't call the log func on discard loggers 200 // makeLogFuncErr will fail the test if it's called 201 loggerTp.output(infoLog, logFuncStr) 202 loggerTp.output(warningLog, logFuncStr) 203 loggerTp.output(errorLog, logFuncStr) 204 205 sprintf = fmt.Sprintf 206 sprint = fmt.Sprint 207 sprintln = fmt.Sprintln 208 } 209 210 func TestLoggerV2PrintFuncAllCombinations(t *testing.T) { 211 const ( 212 print int = iota 213 printf 214 println 215 ) 216 217 type testDiscard struct { 218 discardInf bool 219 discardWarn bool 220 discardErr bool 221 222 printType int 223 formatJSON bool 224 } 225 226 discardName := func(td testDiscard) string { 227 strs := []string{} 228 if td.discardInf { 229 strs = append(strs, "discardInfo") 230 } 231 if td.discardWarn { 232 strs = append(strs, "discardWarn") 233 } 234 if td.discardErr { 235 strs = append(strs, "discardErr") 236 } 237 if len(strs) == 0 { 238 strs = append(strs, "noDiscard") 239 } 240 return strings.Join(strs, " ") 241 } 242 var printName = []string{ 243 print: "print", 244 printf: "printf", 245 println: "println", 246 } 247 var jsonName = map[bool]string{ 248 true: "json", 249 false: "noJson", 250 } 251 252 discardTests := []testDiscard{} 253 for _, di := range []bool{true, false} { 254 for _, dw := range []bool{true, false} { 255 for _, de := range []bool{true, false} { 256 for _, pt := range []int{print, printf, println} { 257 for _, fj := range []bool{true, false} { 258 discardTests = append(discardTests, testDiscard{discardInf: di, discardWarn: dw, discardErr: de, printType: pt, formatJSON: fj}) 259 } 260 } 261 } 262 } 263 } 264 265 for _, discardTest := range discardTests { 266 testName := fmt.Sprintf("%v %v %v", jsonName[discardTest.formatJSON], printName[discardTest.printType], discardName(discardTest)) 267 t.Run(testName, func(t *testing.T) { 268 cfg := LoggerV2Config{FormatJSON: discardTest.formatJSON} 269 270 buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)} 271 writers := []io.Writer{buffers[infoLog], buffers[warningLog], buffers[errorLog]} 272 if discardTest.discardInf { 273 writers[infoLog] = io.Discard 274 } 275 if discardTest.discardWarn { 276 writers[warningLog] = io.Discard 277 } 278 if discardTest.discardErr { 279 writers[errorLog] = io.Discard 280 } 281 logger := NewLoggerV2(writers[infoLog], writers[warningLog], writers[errorLog], cfg) 282 283 msgInf := "someinfo" 284 msgWarn := "somewarn" 285 msgErr := "someerr" 286 if discardTest.printType == print { 287 logger.Info(msgInf) 288 logger.Warning(msgWarn) 289 logger.Error(msgErr) 290 } else if discardTest.printType == printf { 291 logger.Infof("%v", msgInf) 292 logger.Warningf("%v", msgWarn) 293 logger.Errorf("%v", msgErr) 294 } else if discardTest.printType == println { 295 logger.Infoln(msgInf) 296 logger.Warningln(msgWarn) 297 logger.Errorln(msgErr) 298 } 299 300 // verify the test Discard, log type, message, and json arguments were logged as-expected 301 302 checkBufferWasWrittenAsExpected(t, buffers[infoLog], discardTest.discardInf, "info", msgInf, cfg.FormatJSON) 303 checkBufferWasWrittenAsExpected(t, buffers[warningLog], discardTest.discardWarn, "warning", msgWarn, cfg.FormatJSON) 304 checkBufferWasWrittenAsExpected(t, buffers[errorLog], discardTest.discardErr, "error", msgErr, cfg.FormatJSON) 305 }) 306 } 307 } 308 309 func TestLoggerV2Fatal(t *testing.T) { 310 const ( 311 print = "print" 312 println = "println" 313 printf = "printf" 314 ) 315 printFuncs := []string{print, println, printf} 316 317 exitCalls := []int{} 318 319 if reflect.ValueOf(exit).Pointer() != reflect.ValueOf(os.Exit).Pointer() { 320 t.Error("got: exit isn't os.Exit, want exit var to be os.Exit") 321 } 322 323 exit = func(code int) { 324 exitCalls = append(exitCalls, code) 325 } 326 defer func() { 327 exit = os.Exit 328 }() 329 330 for _, printFunc := range printFuncs { 331 buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)} 332 logger := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{}) 333 switch printFunc { 334 case print: 335 logger.Fatal("fatal") 336 case println: 337 logger.Fatalln("fatalln") 338 case printf: 339 logger.Fatalf("fatalf %d", 42) 340 default: 341 t.Errorf("unknown print type '%v'", printFunc) 342 } 343 344 checkBufferWasWrittenAsExpected(t, buffers[errorLog], false, "fatal", "fatal", false) 345 if len(exitCalls) == 0 { 346 t.Error("got: no exit call, want fatal log to call exit") 347 } 348 exitCalls = []int{} 349 } 350 }