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  }