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 }