google.golang.org/grpc@v1.72.2/grpclog/internal/loggerv2.go (about) 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 }