snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package internal contains functionality internal to the grpclog package.
package internal
// LoggerV2Impl is the logger used for the non-depth log functions.
var LoggerV2Impl LoggerV2
// DepthLoggerV2Impl is the logger used for the depth log functions.
var DepthLoggerV2Impl DepthLoggerV2

View File

@@ -0,0 +1,87 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package internal
// Logger mimics golang's standard Logger as an interface.
//
// Deprecated: use LoggerV2.
type Logger interface {
Fatal(args ...any)
Fatalf(format string, args ...any)
Fatalln(args ...any)
Print(args ...any)
Printf(format string, args ...any)
Println(args ...any)
}
// LoggerWrapper wraps Logger into a LoggerV2.
type LoggerWrapper struct {
Logger
}
// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
func (l *LoggerWrapper) Info(args ...any) {
l.Logger.Print(args...)
}
// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
func (l *LoggerWrapper) Infoln(args ...any) {
l.Logger.Println(args...)
}
// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
func (l *LoggerWrapper) Infof(format string, args ...any) {
l.Logger.Printf(format, args...)
}
// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
func (l *LoggerWrapper) Warning(args ...any) {
l.Logger.Print(args...)
}
// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
func (l *LoggerWrapper) Warningln(args ...any) {
l.Logger.Println(args...)
}
// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
func (l *LoggerWrapper) Warningf(format string, args ...any) {
l.Logger.Printf(format, args...)
}
// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
func (l *LoggerWrapper) Error(args ...any) {
l.Logger.Print(args...)
}
// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
func (l *LoggerWrapper) Errorln(args ...any) {
l.Logger.Println(args...)
}
// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
func (l *LoggerWrapper) Errorf(format string, args ...any) {
l.Logger.Printf(format, args...)
}
// V reports whether verbosity level l is at least the requested verbose level.
func (*LoggerWrapper) V(int) bool {
// Returns true for all verbose level.
return true
}

View File

@@ -0,0 +1,267 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package internal
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
)
// LoggerV2 does underlying logging work for grpclog.
type LoggerV2 interface {
// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
Info(args ...any)
// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
Infoln(args ...any)
// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
Infof(format string, args ...any)
// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
Warning(args ...any)
// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
Warningln(args ...any)
// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
Warningf(format string, args ...any)
// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
Error(args ...any)
// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
Errorln(args ...any)
// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
Errorf(format string, args ...any)
// Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatal(args ...any)
// Fatalln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatalln(args ...any)
// Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatalf(format string, args ...any)
// V reports whether verbosity level l is at least the requested verbose level.
V(l int) bool
}
// DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
// DepthLoggerV2, the below functions will be called with the appropriate stack
// depth set for trivial functions the logger may ignore.
//
// # Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type DepthLoggerV2 interface {
LoggerV2
// InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Println.
InfoDepth(depth int, args ...any)
// WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Println.
WarningDepth(depth int, args ...any)
// ErrorDepth logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Println.
ErrorDepth(depth int, args ...any)
// FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Println.
FatalDepth(depth int, args ...any)
}
const (
// infoLog indicates Info severity.
infoLog int = iota
// warningLog indicates Warning severity.
warningLog
// errorLog indicates Error severity.
errorLog
// fatalLog indicates Fatal severity.
fatalLog
)
// severityName contains the string representation of each severity.
var severityName = []string{
infoLog: "INFO",
warningLog: "WARNING",
errorLog: "ERROR",
fatalLog: "FATAL",
}
// sprintf is fmt.Sprintf.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprintf = fmt.Sprintf
// sprint is fmt.Sprint.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprint = fmt.Sprint
// sprintln is fmt.Sprintln.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprintln = fmt.Sprintln
// exit is os.Exit.
// This var exists to make it possible to test functions calling os.Exit.
var exit = os.Exit
// loggerT is the default logger used by grpclog.
type loggerT struct {
m []*log.Logger
v int
jsonFormat bool
}
func (g *loggerT) output(severity int, s string) {
sevStr := severityName[severity]
if !g.jsonFormat {
g.m[severity].Output(2, sevStr+": "+s)
return
}
// TODO: we can also include the logging component, but that needs more
// (API) changes.
b, _ := json.Marshal(map[string]string{
"severity": sevStr,
"message": s,
})
g.m[severity].Output(2, string(b))
}
func (g *loggerT) printf(severity int, format string, args ...any) {
// Note the discard check is duplicated in each print func, rather than in
// output, to avoid the expensive Sprint calls.
// De-duplicating this by moving to output would be a significant performance regression!
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprintf(format, args...))
}
func (g *loggerT) print(severity int, v ...any) {
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprint(v...))
}
func (g *loggerT) println(severity int, v ...any) {
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprintln(v...))
}
func (g *loggerT) Info(args ...any) {
g.print(infoLog, args...)
}
func (g *loggerT) Infoln(args ...any) {
g.println(infoLog, args...)
}
func (g *loggerT) Infof(format string, args ...any) {
g.printf(infoLog, format, args...)
}
func (g *loggerT) Warning(args ...any) {
g.print(warningLog, args...)
}
func (g *loggerT) Warningln(args ...any) {
g.println(warningLog, args...)
}
func (g *loggerT) Warningf(format string, args ...any) {
g.printf(warningLog, format, args...)
}
func (g *loggerT) Error(args ...any) {
g.print(errorLog, args...)
}
func (g *loggerT) Errorln(args ...any) {
g.println(errorLog, args...)
}
func (g *loggerT) Errorf(format string, args ...any) {
g.printf(errorLog, format, args...)
}
func (g *loggerT) Fatal(args ...any) {
g.print(fatalLog, args...)
exit(1)
}
func (g *loggerT) Fatalln(args ...any) {
g.println(fatalLog, args...)
exit(1)
}
func (g *loggerT) Fatalf(format string, args ...any) {
g.printf(fatalLog, format, args...)
exit(1)
}
func (g *loggerT) V(l int) bool {
return l <= g.v
}
// LoggerV2Config configures the LoggerV2 implementation.
type LoggerV2Config struct {
// Verbosity sets the verbosity level of the logger.
Verbosity int
// FormatJSON controls whether the logger should output logs in JSON format.
FormatJSON bool
}
// combineLoggers returns a combined logger for both higher & lower severity logs,
// or only one if the other is io.Discard.
//
// This uses io.Discard instead of io.MultiWriter when all loggers
// are set to io.Discard. Both this package and the standard log package have
// significant optimizations for io.Discard, which io.MultiWriter lacks (as of
// this writing).
func combineLoggers(lower, higher io.Writer) io.Writer {
if lower == io.Discard {
return higher
}
if higher == io.Discard {
return lower
}
return io.MultiWriter(lower, higher)
}
// NewLoggerV2 creates a new LoggerV2 instance with the provided configuration.
// The infoW, warningW, and errorW writers are used to write log messages of
// different severity levels.
func NewLoggerV2(infoW, warningW, errorW io.Writer, c LoggerV2Config) LoggerV2 {
flag := log.LstdFlags
if c.FormatJSON {
flag = 0
}
warningW = combineLoggers(infoW, warningW)
errorW = combineLoggers(errorW, warningW)
fatalW := errorW
m := []*log.Logger{
log.New(infoW, "", flag),
log.New(warningW, "", flag),
log.New(errorW, "", flag),
log.New(fatalW, "", flag),
}
return &loggerT{m: m, v: c.Verbosity, jsonFormat: c.FormatJSON}
}

View File

@@ -0,0 +1,350 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package internal
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strings"
"testing"
)
// logFuncStr is a string used via testCheckLogContainsFuncStr to test the
// logger output.
const logFuncStr = "called-func"
func makeSprintfErr(t *testing.T) func(format string, a ...any) string {
return func(string, ...any) string {
t.Errorf("got: sprintf called on io.Discard logger, want: expensive sprintf to not be called for io.Discard")
return ""
}
}
func makeSprintErr(t *testing.T) func(a ...any) string {
return func(...any) string {
t.Errorf("got: sprint called on io.Discard logger, want: expensive sprint to not be called for io.Discard")
return ""
}
}
// checkLogContainsFuncStr checks that the logger buffer logBuf contains
// logFuncStr.
func checkLogContainsFuncStr(t *testing.T, logBuf []byte) {
if !bytes.Contains(logBuf, []byte(logFuncStr)) {
t.Errorf("got '%v', want logger func to be called and print '%v'", string(logBuf), logFuncStr)
}
}
// checkBufferWasWrittenAsExpected checks that the log buffer buf was written as expected,
// per the discard, logTYpe, msg, and isJSON arguments.
func checkBufferWasWrittenAsExpected(t *testing.T, buf *bytes.Buffer, discard bool, logType string, msg string, isJSON bool) {
bts, err := buf.ReadBytes('\n')
if discard {
if err == nil {
t.Fatalf("got '%v', want discard %v to not write", string(bts), logType)
} else if err != io.EOF {
t.Fatalf("got '%v', want discard %v buffer to be EOF", err, logType)
}
} else {
if err != nil {
t.Fatalf("got '%v', want non-discard %v to not error", err, logType)
} else if !bytes.Contains(bts, []byte(msg)) {
t.Fatalf("got '%v', want non-discard %v buffer contain message '%v'", string(bts), logType, msg)
}
if isJSON {
obj := map[string]string{}
if err := json.Unmarshal(bts, &obj); err != nil {
t.Fatalf("got '%v', want non-discard json %v to unmarshal", err, logType)
} else if _, ok := obj["severity"]; !ok {
t.Fatalf("got '%v', want non-discard json %v to have severity field", "missing severity", logType)
} else if jsonMsg, ok := obj["message"]; !ok {
t.Fatalf("got '%v', want non-discard json %v to have message field", "missing message", logType)
} else if !strings.Contains(jsonMsg, msg) {
t.Fatalf("got '%v', want non-discard json %v buffer contain message '%v'", string(bts), logType, msg)
}
}
}
}
// check if b is in the format of:
//
// 2017/04/07 14:55:42 WARNING: WARNING
func checkLogForSeverity(s int, b []byte) error {
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]))
if m := expected.Match(b); !m {
return fmt.Errorf("got: %v, want string in format of: %v", string(b), severityName[s]+": 2016/10/05 17:09:26 "+severityName[s])
}
return nil
}
func TestLoggerV2Severity(t *testing.T) {
buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)}
l := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{})
l.Info(severityName[infoLog])
l.Warning(severityName[warningLog])
l.Error(severityName[errorLog])
for i := 0; i < fatalLog; i++ {
buf := buffers[i]
// The content of info buffer should be something like:
// INFO: 2017/04/07 14:55:42 INFO
// WARNING: 2017/04/07 14:55:42 WARNING
// ERROR: 2017/04/07 14:55:42 ERROR
for j := i; j < fatalLog; j++ {
b, err := buf.ReadBytes('\n')
if err != nil {
t.Fatalf("level %d: %v", j, err)
}
if err := checkLogForSeverity(j, b); err != nil {
t.Fatal(err)
}
}
}
}
// TestLoggerV2PrintFuncDiscardOnlyInfo ensures that logs at the INFO level are
// discarded when set to io.Discard, while logs at other levels (WARN, ERROR)
// are still printed. It does this by using a custom error function that raises
// an error if the logger attempts to print at the INFO level, ensuring early
// return when io.Discard is used.
func TestLoggerV2PrintFuncDiscardOnlyInfo(t *testing.T) {
buffers := []*bytes.Buffer{nil, new(bytes.Buffer), new(bytes.Buffer)}
logger := NewLoggerV2(io.Discard, buffers[warningLog], buffers[errorLog], LoggerV2Config{})
loggerTp := logger.(*loggerT)
// test that output doesn't call expensive printf funcs on an io.Discard logger
sprintf = makeSprintfErr(t)
sprint = makeSprintErr(t)
sprintln = makeSprintErr(t)
loggerTp.output(infoLog, "something")
sprintf = fmt.Sprintf
sprint = fmt.Sprint
sprintln = fmt.Sprintln
loggerTp.output(errorLog, logFuncStr)
warnB, err := buffers[warningLog].ReadBytes('\n')
if err != nil {
t.Fatalf("level %v: %v", warningLog, err)
}
checkLogContainsFuncStr(t, warnB)
errB, err := buffers[errorLog].ReadBytes('\n')
if err != nil {
t.Fatalf("level %v: %v", errorLog, err)
}
checkLogContainsFuncStr(t, errB)
}
func TestLoggerV2PrintFuncNoDiscard(t *testing.T) {
buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)}
logger := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{})
loggerTp := logger.(*loggerT)
loggerTp.output(errorLog, logFuncStr)
infoB, err := buffers[infoLog].ReadBytes('\n')
if err != nil {
t.Fatalf("level %v: %v", infoLog, err)
}
checkLogContainsFuncStr(t, infoB)
warnB, err := buffers[warningLog].ReadBytes('\n')
if err != nil {
t.Fatalf("level %v: %v", warningLog, err)
}
checkLogContainsFuncStr(t, warnB)
errB, err := buffers[errorLog].ReadBytes('\n')
if err != nil {
t.Fatalf("level %v: %v", errorLog, err)
}
checkLogContainsFuncStr(t, errB)
}
// TestLoggerV2PrintFuncAllDiscard tests that discard loggers don't log.
func TestLoggerV2PrintFuncAllDiscard(t *testing.T) {
logger := NewLoggerV2(io.Discard, io.Discard, io.Discard, LoggerV2Config{})
loggerTp := logger.(*loggerT)
sprintf = makeSprintfErr(t)
sprint = makeSprintErr(t)
sprintln = makeSprintErr(t)
// test that printFunc doesn't call the log func on discard loggers
// makeLogFuncErr will fail the test if it's called
loggerTp.output(infoLog, logFuncStr)
loggerTp.output(warningLog, logFuncStr)
loggerTp.output(errorLog, logFuncStr)
sprintf = fmt.Sprintf
sprint = fmt.Sprint
sprintln = fmt.Sprintln
}
func TestLoggerV2PrintFuncAllCombinations(t *testing.T) {
const (
print int = iota
printf
println
)
type testDiscard struct {
discardInf bool
discardWarn bool
discardErr bool
printType int
formatJSON bool
}
discardName := func(td testDiscard) string {
strs := []string{}
if td.discardInf {
strs = append(strs, "discardInfo")
}
if td.discardWarn {
strs = append(strs, "discardWarn")
}
if td.discardErr {
strs = append(strs, "discardErr")
}
if len(strs) == 0 {
strs = append(strs, "noDiscard")
}
return strings.Join(strs, " ")
}
var printName = []string{
print: "print",
printf: "printf",
println: "println",
}
var jsonName = map[bool]string{
true: "json",
false: "noJson",
}
discardTests := []testDiscard{}
for _, di := range []bool{true, false} {
for _, dw := range []bool{true, false} {
for _, de := range []bool{true, false} {
for _, pt := range []int{print, printf, println} {
for _, fj := range []bool{true, false} {
discardTests = append(discardTests, testDiscard{discardInf: di, discardWarn: dw, discardErr: de, printType: pt, formatJSON: fj})
}
}
}
}
}
for _, discardTest := range discardTests {
testName := fmt.Sprintf("%v %v %v", jsonName[discardTest.formatJSON], printName[discardTest.printType], discardName(discardTest))
t.Run(testName, func(t *testing.T) {
cfg := LoggerV2Config{FormatJSON: discardTest.formatJSON}
buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)}
writers := []io.Writer{buffers[infoLog], buffers[warningLog], buffers[errorLog]}
if discardTest.discardInf {
writers[infoLog] = io.Discard
}
if discardTest.discardWarn {
writers[warningLog] = io.Discard
}
if discardTest.discardErr {
writers[errorLog] = io.Discard
}
logger := NewLoggerV2(writers[infoLog], writers[warningLog], writers[errorLog], cfg)
msgInf := "someinfo"
msgWarn := "somewarn"
msgErr := "someerr"
if discardTest.printType == print {
logger.Info(msgInf)
logger.Warning(msgWarn)
logger.Error(msgErr)
} else if discardTest.printType == printf {
logger.Infof("%v", msgInf)
logger.Warningf("%v", msgWarn)
logger.Errorf("%v", msgErr)
} else if discardTest.printType == println {
logger.Infoln(msgInf)
logger.Warningln(msgWarn)
logger.Errorln(msgErr)
}
// verify the test Discard, log type, message, and json arguments were logged as-expected
checkBufferWasWrittenAsExpected(t, buffers[infoLog], discardTest.discardInf, "info", msgInf, cfg.FormatJSON)
checkBufferWasWrittenAsExpected(t, buffers[warningLog], discardTest.discardWarn, "warning", msgWarn, cfg.FormatJSON)
checkBufferWasWrittenAsExpected(t, buffers[errorLog], discardTest.discardErr, "error", msgErr, cfg.FormatJSON)
})
}
}
func TestLoggerV2Fatal(t *testing.T) {
const (
print = "print"
println = "println"
printf = "printf"
)
printFuncs := []string{print, println, printf}
exitCalls := []int{}
if reflect.ValueOf(exit).Pointer() != reflect.ValueOf(os.Exit).Pointer() {
t.Error("got: exit isn't os.Exit, want exit var to be os.Exit")
}
exit = func(code int) {
exitCalls = append(exitCalls, code)
}
defer func() {
exit = os.Exit
}()
for _, printFunc := range printFuncs {
buffers := []*bytes.Buffer{new(bytes.Buffer), new(bytes.Buffer), new(bytes.Buffer)}
logger := NewLoggerV2(buffers[infoLog], buffers[warningLog], buffers[errorLog], LoggerV2Config{})
switch printFunc {
case print:
logger.Fatal("fatal")
case println:
logger.Fatalln("fatalln")
case printf:
logger.Fatalf("fatalf %d", 42)
default:
t.Errorf("unknown print type '%v'", printFunc)
}
checkBufferWasWrittenAsExpected(t, buffers[errorLog], false, "fatal", "fatal", false)
if len(exitCalls) == 0 {
t.Error("got: no exit call, want fatal log to call exit")
}
exitCalls = []int{}
}
}