| /* |
| * |
| * 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} |
| } |