go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/logging/grpc_logger.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package logging implements a gRPC glog.Logger implementation backed
    16  // by a go.chromium.org/luci/common/logging Logger.
    17  //
    18  // The logger can be installed by calling Install.
    19  package logging
    20  
    21  import (
    22  	"os"
    23  	"runtime"
    24  	"strings"
    25  
    26  	"go.chromium.org/luci/common/logging"
    27  
    28  	"google.golang.org/grpc/grpclog"
    29  )
    30  
    31  // Suppress is a sentinel logging level that instructs the logger to suppress
    32  // all non-fatal logging output. This is NOT a valid logging.Level, and should
    33  // not be used as such.
    34  var Suppress = logging.Level(logging.Error + 1)
    35  
    36  type grpcLogger struct {
    37  	base   logging.Logger
    38  	vLevel int
    39  }
    40  
    41  // Install installs a logger as the gRPC library's logger. The installation is
    42  // not protected by a mutex, so this must be set somewhere that atomic access is
    43  // guaranteed.
    44  //
    45  // A special logging level, "Suppress", can be provided to suppress all
    46  // non-fatal logging output .
    47  //
    48  // gRPC V=level and error terminology translation is as follows:
    49  // - V=0, ERROR (low verbosity) is logged at logging.ERROR level.
    50  // - V=1, WARNING (medium verbosity) is logged at logging.WARNING level.
    51  // - V=2, INFO (high verbosity) is logged at logging.DEBUG level.
    52  func Install(base logging.Logger, level logging.Level) {
    53  	grpclog.SetLoggerV2(&grpcLogger{
    54  		base:   base,
    55  		vLevel: translateLevel(level),
    56  	})
    57  }
    58  
    59  func (gl *grpcLogger) Info(args ...any) {
    60  	if gl.V(2) {
    61  		gl.base.LogCall(logging.Debug, 2, makeArgFormatString(args), args)
    62  	}
    63  }
    64  
    65  func (gl *grpcLogger) Infof(format string, args ...any) {
    66  	if gl.V(2) {
    67  		gl.base.LogCall(logging.Debug, 2, format, args)
    68  	}
    69  }
    70  
    71  func (gl *grpcLogger) Infoln(args ...any) {
    72  	if gl.V(2) {
    73  		gl.base.LogCall(logging.Debug, 2, makeArgFormatString(args), args)
    74  	}
    75  }
    76  
    77  func (gl *grpcLogger) Warning(args ...any) {
    78  	if gl.V(1) {
    79  		gl.base.LogCall(logging.Warning, 2, makeArgFormatString(args), args)
    80  	}
    81  }
    82  
    83  func (gl *grpcLogger) Warningf(format string, args ...any) {
    84  	if gl.V(1) {
    85  		gl.base.LogCall(logging.Warning, 2, format, args)
    86  	}
    87  }
    88  
    89  func (gl *grpcLogger) Warningln(args ...any) {
    90  	if gl.V(1) {
    91  		gl.base.LogCall(logging.Warning, 2, makeArgFormatString(args), args)
    92  	}
    93  }
    94  
    95  func (gl *grpcLogger) Error(args ...any) {
    96  	if gl.V(0) {
    97  		gl.base.LogCall(logging.Error, 2, makeArgFormatString(args), args)
    98  	}
    99  }
   100  
   101  func (gl *grpcLogger) Errorf(format string, args ...any) {
   102  	if gl.V(0) {
   103  		gl.base.LogCall(logging.Error, 2, format, args)
   104  	}
   105  }
   106  
   107  func (gl *grpcLogger) Errorln(args ...any) {
   108  	if gl.V(0) {
   109  		gl.base.LogCall(logging.Error, 2, makeArgFormatString(args), args)
   110  	}
   111  }
   112  
   113  func (gl *grpcLogger) Fatal(args ...any) {
   114  	gl.base.LogCall(logging.Error, 2, makeArgFormatString(args), args)
   115  	gl.logStackTraceAndDie()
   116  }
   117  
   118  func (gl *grpcLogger) Fatalf(format string, args ...any) {
   119  	gl.base.LogCall(logging.Error, 2, format, args)
   120  	gl.logStackTraceAndDie()
   121  }
   122  
   123  func (gl *grpcLogger) Fatalln(args ...any) {
   124  	gl.base.LogCall(logging.Error, 2, makeArgFormatString(args), args)
   125  	gl.logStackTraceAndDie()
   126  }
   127  
   128  func (gl *grpcLogger) V(l int) bool {
   129  	return gl.vLevel >= l
   130  }
   131  
   132  func (gl *grpcLogger) logStackTraceAndDie() {
   133  	gl.base.LogCall(logging.Error, 3, "Stack Trace:\n%s", []any{stacks(true)})
   134  	fatalExit()
   135  }
   136  
   137  // fatalExit is an operating system exit function used by "Fatal" logs. It is a
   138  // variable here so it can be stubbed for testing, but will exit with a non-zero
   139  // return code by default.
   140  var fatalExit = func() {
   141  	os.Exit(1)
   142  }
   143  
   144  // stacks is a wrapper for runtime.Stack that attempts to recover the data for
   145  // all goroutines.
   146  //
   147  // This was copied from the glog library:
   148  // https://github.com/golang/glog @ / 23def4e6c14b4da8ac2ed8007337bc5eb5007998
   149  func stacks(all bool) []byte {
   150  	// We don't know how big the traces are, so grow a few times if they don't fit.
   151  	// Start large, though.
   152  	n := 10000
   153  	if all {
   154  		n = 100000
   155  	}
   156  	var trace []byte
   157  	for i := 0; i < 5; i++ {
   158  		trace = make([]byte, n)
   159  		nbytes := runtime.Stack(trace, all)
   160  		if nbytes < len(trace) {
   161  			return trace[:nbytes]
   162  		}
   163  		n *= 2
   164  	}
   165  	return trace
   166  }
   167  
   168  func makeArgFormatString(args []any) string {
   169  	if len(args) == 0 {
   170  		return ""
   171  	}
   172  	return strings.TrimSuffix(strings.Repeat("%v ", len(args)), " ")
   173  }
   174  
   175  // translateLevel translates a "verbose level" to a logging level.
   176  func translateLevel(l logging.Level) int {
   177  	switch l {
   178  	case Suppress:
   179  		return -1
   180  	case logging.Error:
   181  		return 0
   182  	case logging.Warning, logging.Info:
   183  		return 1
   184  	default:
   185  		return 2
   186  	}
   187  }