stdr.go

  1/*
  2Copyright 2019 The logr Authors.
  3
  4Licensed under the Apache License, Version 2.0 (the "License");
  5you may not use this file except in compliance with the License.
  6You may obtain a copy of the License at
  7
  8    http://www.apache.org/licenses/LICENSE-2.0
  9
 10Unless required by applicable law or agreed to in writing, software
 11distributed under the License is distributed on an "AS IS" BASIS,
 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13See the License for the specific language governing permissions and
 14limitations under the License.
 15*/
 16
 17// Package stdr implements github.com/go-logr/logr.Logger in terms of
 18// Go's standard log package.
 19package stdr
 20
 21import (
 22	"log"
 23	"os"
 24
 25	"github.com/go-logr/logr"
 26	"github.com/go-logr/logr/funcr"
 27)
 28
 29// The global verbosity level.  See SetVerbosity().
 30var globalVerbosity int
 31
 32// SetVerbosity sets the global level against which all info logs will be
 33// compared.  If this is greater than or equal to the "V" of the logger, the
 34// message will be logged.  A higher value here means more logs will be written.
 35// The previous verbosity value is returned.  This is not concurrent-safe -
 36// callers must be sure to call it from only one goroutine.
 37func SetVerbosity(v int) int {
 38	old := globalVerbosity
 39	globalVerbosity = v
 40	return old
 41}
 42
 43// New returns a logr.Logger which is implemented by Go's standard log package,
 44// or something like it.  If std is nil, this will use a default logger
 45// instead.
 46//
 47// Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)))
 48func New(std StdLogger) logr.Logger {
 49	return NewWithOptions(std, Options{})
 50}
 51
 52// NewWithOptions returns a logr.Logger which is implemented by Go's standard
 53// log package, or something like it.  See New for details.
 54func NewWithOptions(std StdLogger, opts Options) logr.Logger {
 55	if std == nil {
 56		// Go's log.Default() is only available in 1.16 and higher.
 57		std = log.New(os.Stderr, "", log.LstdFlags)
 58	}
 59
 60	if opts.Depth < 0 {
 61		opts.Depth = 0
 62	}
 63
 64	fopts := funcr.Options{
 65		LogCaller: funcr.MessageClass(opts.LogCaller),
 66	}
 67
 68	sl := &logger{
 69		Formatter: funcr.NewFormatter(fopts),
 70		std:       std,
 71	}
 72
 73	// For skipping our own logger.Info/Error.
 74	sl.Formatter.AddCallDepth(1 + opts.Depth)
 75
 76	return logr.New(sl)
 77}
 78
 79// Options carries parameters which influence the way logs are generated.
 80type Options struct {
 81	// Depth biases the assumed number of call frames to the "true" caller.
 82	// This is useful when the calling code calls a function which then calls
 83	// stdr (e.g. a logging shim to another API).  Values less than zero will
 84	// be treated as zero.
 85	Depth int
 86
 87	// LogCaller tells stdr to add a "caller" key to some or all log lines.
 88	// Go's log package has options to log this natively, too.
 89	LogCaller MessageClass
 90
 91	// TODO: add an option to log the date/time
 92}
 93
 94// MessageClass indicates which category or categories of messages to consider.
 95type MessageClass int
 96
 97const (
 98	// None ignores all message classes.
 99	None MessageClass = iota
100	// All considers all message classes.
101	All
102	// Info only considers info messages.
103	Info
104	// Error only considers error messages.
105	Error
106)
107
108// StdLogger is the subset of the Go stdlib log.Logger API that is needed for
109// this adapter.
110type StdLogger interface {
111	// Output is the same as log.Output and log.Logger.Output.
112	Output(calldepth int, logline string) error
113}
114
115type logger struct {
116	funcr.Formatter
117	std StdLogger
118}
119
120var _ logr.LogSink = &logger{}
121var _ logr.CallDepthLogSink = &logger{}
122
123func (l logger) Enabled(level int) bool {
124	return globalVerbosity >= level
125}
126
127func (l logger) Info(level int, msg string, kvList ...interface{}) {
128	prefix, args := l.FormatInfo(level, msg, kvList)
129	if prefix != "" {
130		args = prefix + ": " + args
131	}
132	_ = l.std.Output(l.Formatter.GetDepth()+1, args)
133}
134
135func (l logger) Error(err error, msg string, kvList ...interface{}) {
136	prefix, args := l.FormatError(err, msg, kvList)
137	if prefix != "" {
138		args = prefix + ": " + args
139	}
140	_ = l.std.Output(l.Formatter.GetDepth()+1, args)
141}
142
143func (l logger) WithName(name string) logr.LogSink {
144	l.Formatter.AddName(name)
145	return &l
146}
147
148func (l logger) WithValues(kvList ...interface{}) logr.LogSink {
149	l.Formatter.AddValues(kvList)
150	return &l
151}
152
153func (l logger) WithCallDepth(depth int) logr.LogSink {
154	l.Formatter.AddCallDepth(depth)
155	return &l
156}
157
158// Underlier exposes access to the underlying logging implementation.  Since
159// callers only have a logr.Logger, they have to know which implementation is
160// in use, so this interface is less of an abstraction and more of way to test
161// type conversion.
162type Underlier interface {
163	GetUnderlying() StdLogger
164}
165
166// GetUnderlying returns the StdLogger underneath this logger.  Since StdLogger
167// is itself an interface, the result may or may not be a Go log.Logger.
168func (l logger) GetUnderlying() StdLogger {
169	return l.std
170}