loggerv2.go raw

   1  /*
   2   *
   3   * Copyright 2024 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  	"encoding/json"
  23  	"fmt"
  24  	"io"
  25  	"log"
  26  	"os"
  27  )
  28  
  29  // LoggerV2 does underlying logging work for grpclog.
  30  type LoggerV2 interface {
  31  	// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
  32  	Info(args ...any)
  33  	// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
  34  	Infoln(args ...any)
  35  	// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
  36  	Infof(format string, args ...any)
  37  	// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
  38  	Warning(args ...any)
  39  	// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
  40  	Warningln(args ...any)
  41  	// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
  42  	Warningf(format string, args ...any)
  43  	// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
  44  	Error(args ...any)
  45  	// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
  46  	Errorln(args ...any)
  47  	// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
  48  	Errorf(format string, args ...any)
  49  	// Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
  50  	// gRPC ensures that all Fatal logs will exit with os.Exit(1).
  51  	// Implementations may also call os.Exit() with a non-zero exit code.
  52  	Fatal(args ...any)
  53  	// Fatalln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
  54  	// gRPC ensures that all Fatal logs will exit with os.Exit(1).
  55  	// Implementations may also call os.Exit() with a non-zero exit code.
  56  	Fatalln(args ...any)
  57  	// Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
  58  	// gRPC ensures that all Fatal logs will exit with os.Exit(1).
  59  	// Implementations may also call os.Exit() with a non-zero exit code.
  60  	Fatalf(format string, args ...any)
  61  	// V reports whether verbosity level l is at least the requested verbose level.
  62  	V(l int) bool
  63  }
  64  
  65  // DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
  66  // DepthLoggerV2, the below functions will be called with the appropriate stack
  67  // depth set for trivial functions the logger may ignore.
  68  //
  69  // # Experimental
  70  //
  71  // Notice: This type is EXPERIMENTAL and may be changed or removed in a
  72  // later release.
  73  type DepthLoggerV2 interface {
  74  	LoggerV2
  75  	// InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Println.
  76  	InfoDepth(depth int, args ...any)
  77  	// WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Println.
  78  	WarningDepth(depth int, args ...any)
  79  	// ErrorDepth logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Println.
  80  	ErrorDepth(depth int, args ...any)
  81  	// FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Println.
  82  	FatalDepth(depth int, args ...any)
  83  }
  84  
  85  const (
  86  	// infoLog indicates Info severity.
  87  	infoLog int = iota
  88  	// warningLog indicates Warning severity.
  89  	warningLog
  90  	// errorLog indicates Error severity.
  91  	errorLog
  92  	// fatalLog indicates Fatal severity.
  93  	fatalLog
  94  )
  95  
  96  // severityName contains the string representation of each severity.
  97  var severityName = []string{
  98  	infoLog:    "INFO",
  99  	warningLog: "WARNING",
 100  	errorLog:   "ERROR",
 101  	fatalLog:   "FATAL",
 102  }
 103  
 104  // sprintf is fmt.Sprintf.
 105  // These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
 106  var sprintf = fmt.Sprintf
 107  
 108  // sprint is fmt.Sprint.
 109  // These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
 110  var sprint = fmt.Sprint
 111  
 112  // sprintln is fmt.Sprintln.
 113  // These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
 114  var sprintln = fmt.Sprintln
 115  
 116  // exit is os.Exit.
 117  // This var exists to make it possible to test functions calling os.Exit.
 118  var exit = os.Exit
 119  
 120  // loggerT is the default logger used by grpclog.
 121  type loggerT struct {
 122  	m          []*log.Logger
 123  	v          int
 124  	jsonFormat bool
 125  }
 126  
 127  func (g *loggerT) output(severity int, s string) {
 128  	sevStr := severityName[severity]
 129  	if !g.jsonFormat {
 130  		g.m[severity].Output(2, sevStr+": "+s)
 131  		return
 132  	}
 133  	// TODO: we can also include the logging component, but that needs more
 134  	// (API) changes.
 135  	b, _ := json.Marshal(map[string]string{
 136  		"severity": sevStr,
 137  		"message":  s,
 138  	})
 139  	g.m[severity].Output(2, string(b))
 140  }
 141  
 142  func (g *loggerT) printf(severity int, format string, args ...any) {
 143  	// Note the discard check is duplicated in each print func, rather than in
 144  	// output, to avoid the expensive Sprint calls.
 145  	// De-duplicating this by moving to output would be a significant performance regression!
 146  	if lg := g.m[severity]; lg.Writer() == io.Discard {
 147  		return
 148  	}
 149  	g.output(severity, sprintf(format, args...))
 150  }
 151  
 152  func (g *loggerT) print(severity int, v ...any) {
 153  	if lg := g.m[severity]; lg.Writer() == io.Discard {
 154  		return
 155  	}
 156  	g.output(severity, sprint(v...))
 157  }
 158  
 159  func (g *loggerT) println(severity int, v ...any) {
 160  	if lg := g.m[severity]; lg.Writer() == io.Discard {
 161  		return
 162  	}
 163  	g.output(severity, sprintln(v...))
 164  }
 165  
 166  func (g *loggerT) Info(args ...any) {
 167  	g.print(infoLog, args...)
 168  }
 169  
 170  func (g *loggerT) Infoln(args ...any) {
 171  	g.println(infoLog, args...)
 172  }
 173  
 174  func (g *loggerT) Infof(format string, args ...any) {
 175  	g.printf(infoLog, format, args...)
 176  }
 177  
 178  func (g *loggerT) Warning(args ...any) {
 179  	g.print(warningLog, args...)
 180  }
 181  
 182  func (g *loggerT) Warningln(args ...any) {
 183  	g.println(warningLog, args...)
 184  }
 185  
 186  func (g *loggerT) Warningf(format string, args ...any) {
 187  	g.printf(warningLog, format, args...)
 188  }
 189  
 190  func (g *loggerT) Error(args ...any) {
 191  	g.print(errorLog, args...)
 192  }
 193  
 194  func (g *loggerT) Errorln(args ...any) {
 195  	g.println(errorLog, args...)
 196  }
 197  
 198  func (g *loggerT) Errorf(format string, args ...any) {
 199  	g.printf(errorLog, format, args...)
 200  }
 201  
 202  func (g *loggerT) Fatal(args ...any) {
 203  	g.print(fatalLog, args...)
 204  	exit(1)
 205  }
 206  
 207  func (g *loggerT) Fatalln(args ...any) {
 208  	g.println(fatalLog, args...)
 209  	exit(1)
 210  }
 211  
 212  func (g *loggerT) Fatalf(format string, args ...any) {
 213  	g.printf(fatalLog, format, args...)
 214  	exit(1)
 215  }
 216  
 217  func (g *loggerT) V(l int) bool {
 218  	return l <= g.v
 219  }
 220  
 221  // LoggerV2Config configures the LoggerV2 implementation.
 222  type LoggerV2Config struct {
 223  	// Verbosity sets the verbosity level of the logger.
 224  	Verbosity int
 225  	// FormatJSON controls whether the logger should output logs in JSON format.
 226  	FormatJSON bool
 227  }
 228  
 229  // combineLoggers returns a combined logger for both higher & lower severity logs,
 230  // or only one if the other is io.Discard.
 231  //
 232  // This uses io.Discard instead of io.MultiWriter when all loggers
 233  // are set to io.Discard. Both this package and the standard log package have
 234  // significant optimizations for io.Discard, which io.MultiWriter lacks (as of
 235  // this writing).
 236  func combineLoggers(lower, higher io.Writer) io.Writer {
 237  	if lower == io.Discard {
 238  		return higher
 239  	}
 240  	if higher == io.Discard {
 241  		return lower
 242  	}
 243  	return io.MultiWriter(lower, higher)
 244  }
 245  
 246  // NewLoggerV2 creates a new LoggerV2 instance with the provided configuration.
 247  // The infoW, warningW, and errorW writers are used to write log messages of
 248  // different severity levels.
 249  func NewLoggerV2(infoW, warningW, errorW io.Writer, c LoggerV2Config) LoggerV2 {
 250  	flag := log.LstdFlags
 251  	if c.FormatJSON {
 252  		flag = 0
 253  	}
 254  
 255  	warningW = combineLoggers(infoW, warningW)
 256  	errorW = combineLoggers(errorW, warningW)
 257  
 258  	fatalW := errorW
 259  
 260  	m := []*log.Logger{
 261  		log.New(infoW, "", flag),
 262  		log.New(warningW, "", flag),
 263  		log.New(errorW, "", flag),
 264  		log.New(fatalW, "", flag),
 265  	}
 266  	return &loggerT{m: m, v: c.Verbosity, jsonFormat: c.FormatJSON}
 267  }
 268