loggerv2.go

  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
 19package internal
 20
 21import (
 22	"encoding/json"
 23	"fmt"
 24	"io"
 25	"log"
 26	"os"
 27)
 28
 29// LoggerV2 does underlying logging work for grpclog.
 30type 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.
 73type 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
 85const (
 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.
 97var 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.
106var 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.
110var 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.
114var sprintln = fmt.Sprintln
115
116// exit is os.Exit.
117// This var exists to make it possible to test functions calling os.Exit.
118var exit = os.Exit
119
120// loggerT is the default logger used by grpclog.
121type loggerT struct {
122	m          []*log.Logger
123	v          int
124	jsonFormat bool
125}
126
127func (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
142func (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
152func (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
159func (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
166func (g *loggerT) Info(args ...any) {
167	g.print(infoLog, args...)
168}
169
170func (g *loggerT) Infoln(args ...any) {
171	g.println(infoLog, args...)
172}
173
174func (g *loggerT) Infof(format string, args ...any) {
175	g.printf(infoLog, format, args...)
176}
177
178func (g *loggerT) Warning(args ...any) {
179	g.print(warningLog, args...)
180}
181
182func (g *loggerT) Warningln(args ...any) {
183	g.println(warningLog, args...)
184}
185
186func (g *loggerT) Warningf(format string, args ...any) {
187	g.printf(warningLog, format, args...)
188}
189
190func (g *loggerT) Error(args ...any) {
191	g.print(errorLog, args...)
192}
193
194func (g *loggerT) Errorln(args ...any) {
195	g.println(errorLog, args...)
196}
197
198func (g *loggerT) Errorf(format string, args ...any) {
199	g.printf(errorLog, format, args...)
200}
201
202func (g *loggerT) Fatal(args ...any) {
203	g.print(fatalLog, args...)
204	exit(1)
205}
206
207func (g *loggerT) Fatalln(args ...any) {
208	g.println(fatalLog, args...)
209	exit(1)
210}
211
212func (g *loggerT) Fatalf(format string, args ...any) {
213	g.printf(fatalLog, format, args...)
214	exit(1)
215}
216
217func (g *loggerT) V(l int) bool {
218	return l <= g.v
219}
220
221// LoggerV2Config configures the LoggerV2 implementation.
222type 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).
236func 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.
249func 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}