Codebase list golang-github-go-logr-logr / bf30a3e
New upstream release. Debian Janitor 2 years ago
16 changed file(s) with 1484 addition(s) and 384 deletion(s). Raw diff Collapse all Expand all
0 # CHANGELOG
1
2 ## v1.0.0-rc1
3
4 This is the first logged release. Major changes (including breaking changes)
5 have occurred since earlier tags.
0 # Contributing
1
2 Logr is open to pull-requests, provided they fit within the intended scope of
3 the project. Specifically, this library aims to be VERY small and minimalist,
4 with no external dependencies.
5
6 ## Compatibility
7
8 This project intends to follow [semantic versioning](http://semver.org) and
9 is very strict about compatibility. Any proposed changes MUST follow those
10 rules.
11
12 ## Performance
13
14 As a logging library, logr must be as light-weight as possible. Any proposed
15 code change must include results of running the [benchmark](./benchmark)
16 before and after the change.
0 # A more minimal logging API for Go
0 # A minimal logging API for Go
1
2 logr offers an(other) opinion on how Go programs and libraries can do logging
3 without becoming coupled to a particular logging implementation, This is not
4 an implementation of logging - it is an API. In fact it is two APIs with twop
5 different sets of users.
6
7 The `Logger` type is intended for application and library authors. It provides
8 a relatively small API which can be used everywhere you want to emit logs. It
9 defers the actual act of writing logs (to files, to stdout, or whatever) to the
10 `LogSink` interface.
11
12 The `LogSink` interface is intended for logging library implementors. It is a
13 pure interface which can be implemented by to provide the actual logging
14 functionality.
15
16 This decoupling allows application and library developers to write code in
17 terms of `logr.Logger` (which has very low dependency fan-out) while the
18 implementation of logging is managed "up stack" (e.g. in or near `main()`).
19 Application developers can then switch out implementations as necessary.
20
21 Many people assert that libraries should not be logging, and as such efforts
22 like this are pointless. Those people are welcome to convince the authors of
23 the tens-of-thousands of libraries that *DO* write logs that they are all
24 wrong. In the meantime, logr takes a more practical approach.
25
26 ## Typical usage
27
28 Somewhere, early in an application's life, it will make a decision about which
29 logging library (implementation) it actually wants to use. Something like:
30
31 ```
32 func main() {
33 // ... other setup code ...
34
35 // Create the "root" logger. We have chosen the "logimpl" implementation,
36 // which takes some initial parameters and returns a logr.Logger.
37 logger := logimpl.New(param1, param2)
38
39 // ... other setup code ...
40 ```
41
42 Most apps will call into other libraries, create stuctures to govern the flow,
43 etc. The `logr.Logger` object can be passed to these other libraries, stored
44 in structs, or even used as a package-global variable, if needed. For example:
45
46 ```
47 app := createTheAppObject(logger)
48 app.Run()
49 ```
50
51 Outside of this early setup, no other packages need to know about the choice of
52 implementation. They write logs in terms of the `logr.Logger` that they
53 received:
54
55 ```
56 type appObject struct {
57 // ... other fields ...
58 logger logr.Logr
59 // ... other fields ...
60 }
61
62 func (app *appObject) Run() {
63 app.logger.Info("starting up", "timestamp", time.Now())
64
65 // ... app code ...
66 ```
67
68 ## Background
69
70 If the Go standard library had defined an interface for logging, this project
71 probably would not be needed. Alas, here we are.
72
73 ### Inspiration
174
275 Before you consider this package, please read [this blog post by the
3 inimitable Dave Cheney][warning-makes-no-sense]. I really appreciate what
4 he has to say, and it largely aligns with my own experiences. Too many
5 choices of levels means inconsistent logs.
6
7 This package offers a purely abstract interface, based on these ideas but with
8 a few twists. Code can depend on just this interface and have the actual
9 logging implementation be injected from callers. Ideally only `main()` knows
10 what logging implementation is being used.
11
12 # Differences from Dave's ideas
76 inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what
77 he has to say, and it largely aligns with our own experiences.
78
79 ### Differences from Dave's ideas
1380
1481 The main differences are:
1582
1683 1) Dave basically proposes doing away with the notion of a logging API in favor
17 of `fmt.Printf()`. I disagree, especially when you consider things like output
18 locations, timestamps, file and line decorations, and structured logging. I
19 restrict the API to just 2 types of logs: info and error.
84 of `fmt.Printf()`. We disagree, especially when you consider things like output
85 locations, timestamps, file and line decorations, and structured logging. This
86 package restricts the logging API to just 2 types of logs: info and error.
2087
2188 Info logs are things you want to tell the user which are not errors. Error
2289 logs are, well, errors. If your code receives an `error` from a subordinate
3097 Because verbosity is a numerical value, it's safe to assume that an app running
3198 with higher verbosity means more (and less important) logs will be generated.
3299
33 This is a BETA grade API.
100 ## Implementations (non-exhaustive)
34101
35102 There are implementations for the following logging libraries:
36103
104 - **a function**: [funcr](https://github.com/go-logr/logr/funcr)
37105 - **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
38106 - **k8s.io/klog**: [klogr](https://git.k8s.io/klog/klogr)
39107 - **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
40 - **log** (the Go standard library logger):
41 [stdr](https://github.com/go-logr/stdr)
108 - **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
42109 - **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
43110 - **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
44111 - **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
45112
46 # FAQ
47
48 ## Conceptual
49
50 ## Why structured logging?
113 ## FAQ
114
115 ### Conceptual
116
117 #### Why structured logging?
51118
52119 - **Structured logs are more easily queriable**: Since you've got
53120 key-value pairs, it's much easier to query your structured logs for
59126 Similarly to searchability, if you maintain conventions around your
60127 keys, it becomes easy to gather all log lines related to a particular
61128 concept.
62
129
63130 - **Structured logs allow better dimensions of filtering**: if you have
64131 structure to your logs, you've got more precise control over how much
65132 information is logged -- you might choose in a particular configuration
72139 objects). Structured logs allow you to preserve that structure when
73140 outputting.
74141
75 ## Why V-levels?
142 #### Why V-levels?
76143
77144 **V-levels give operators an easy way to control the chattiness of log
78145 operations**. V-levels provide a way for a given package to distinguish
79146 the relative importance or verbosity of a given log message. Then, if
80147 a particular logger or package is logging too many messages, the user
81 of the package can simply change the v-levels for that library.
82
83 ## Why not more named levels, like Warning?
148 of the package can simply change the v-levels for that library.
149
150 #### Why not named levels, like Info/Warning/Error?
84151
85152 Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
86153 from Dave's ideas](#differences-from-daves-ideas).
87154
88 ## Why not allow format strings, too?
155 #### Why not allow format strings, too?
89156
90157 **Format strings negate many of the benefits of structured logs**:
91158
95162 - They don't store structured data well, since contents are flattened into
96163 a string
97164
98 - They're not cross-referencable
165 - They're not cross-referenceable
99166
100167 - They don't compress easily, since the message is not constant
101168
103170 keys, at which point you've gotten key-value logging with meaningless
104171 keys)
105172
106 ## Practical
107
108 ## Why key-value pairs, and not a map?
173 ### Practical
174
175 #### Why key-value pairs, and not a map?
109176
110177 Key-value pairs are *much* easier to optimize, especially around
111178 allocations. Zap (a structured logger that inspired logr's interface) has
116183 potentially better performance, plus avoid making users type
117184 `map[string]string{}` every time they want to log.
118185
119 ## What if my V-levels differ between libraries?
186 #### What if my V-levels differ between libraries?
120187
121188 That's fine. Control your V-levels on a per-logger basis, and use the
122 `WithName` function to pass different loggers to different libraries.
189 `WithName` method to pass different loggers to different libraries.
123190
124191 Generally, you should take care to ensure that you have relatively
125192 consistent V-levels within a given logger, however, as this makes deciding
126193 on what verbosity of logs to request easier.
127194
128 ## But I *really* want to use a format string!
195 #### But I really want to use a format string!
129196
130197 That's not actually a question. Assuming your question is "how do
131198 I convert my mental model of logging with format strings to logging with
149216 response when requesting url", "attempt", retries, "after
150217 seconds", seconds, "url", url)`
151218
152 If you *really* must use a format string, place it as a key value, and
153 call `fmt.Sprintf` yourself -- for instance, `log.Printf("unable to
219 If you *really* must use a format string, use it in a key's value, and
220 call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to
154221 reflect over type %T")` becomes `logger.Info("unable to reflect over
155222 type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
156223 this is necessary should be few and far between.
157224
158 ## How do I choose my V-levels?
225 #### How do I choose my V-levels?
159226
160227 This is basically the only hard constraint: increase V-levels to denote
161228 more verbose or more debug-y logs.
168235 down from 10 (for debug and trace style logs) and up from 1 (for chattier
169236 info-type logs).
170237
171 ## How do I choose my keys
238 #### How do I choose my keys?
172239
173240 - make your keys human-readable
174241 - constant keys are generally a good idea
179246 it's generally a good idea to stick to printable ascii characters, or at
180247 least match the general character set of your log lines.
181248
249 #### Why should keys be constant values?
250
251 The point of structured logging is to make later log processing easier. Your
252 keys are, effectively, the schema of each log message. If you use different
253 keys across instances of the same log-line, you will make your structured logs
254 much harder to use. `Sprintf()` is for values, not for keys!
255
256 #### Why is this not a pure interface?
257
258 The Logger type is implemented as a struct in order to allow the Go compiler to
259 optimize things like high-V `Info` logs that are not triggered. Not all of
260 these implementations are implemented yet, but this structure was suggested as
261 a way to ensure they *can* be implemented. All of the real work is behind the
262 `LogSink` interface.
263
182264 [warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging
0 # Benchmarking logr
1
2 Any major changes to the logr library must be benchmarked before and after the
3 change.
4
5 ## Running the benchmark
6
7 ```
8 $ go test -bench='.' ./benchmark/
9 ```
10
11 ## Fixing the benchmark
12
13 If you think this benchmark can be improved, you are probably correct! PRs are
14 very welcome.
0 /*
1 Copyright 2021 The logr 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
16 package logr
17
18 import (
19 "fmt"
20 "testing"
21
22 "github.com/go-logr/logr"
23 "github.com/go-logr/logr/funcr"
24 )
25
26 func noop(prefix, args string) {
27 }
28
29 //go:noinline
30 func doInfoOneArg(b *testing.B, log logr.Logger) {
31 for i := 0; i < b.N; i++ {
32 log.Info("this is", "a", "string")
33 }
34 }
35
36 //go:noinline
37 func doInfoSeveralArgs(b *testing.B, log logr.Logger) {
38 for i := 0; i < b.N; i++ {
39 log.Info("multi",
40 "bool", true, "string", "str", "int", 42,
41 "float", 3.14, "struct", struct{ X, Y int }{93, 76})
42 }
43 }
44
45 //go:noinline
46 func doV0Info(b *testing.B, log logr.Logger) {
47 for i := 0; i < b.N; i++ {
48 log.V(0).Info("multi",
49 "bool", true, "string", "str", "int", 42,
50 "float", 3.14, "struct", struct{ X, Y int }{93, 76})
51 }
52 }
53
54 //go:noinline
55 func doV9Info(b *testing.B, log logr.Logger) {
56 for i := 0; i < b.N; i++ {
57 log.V(9).Info("multi",
58 "bool", true, "string", "str", "int", 42,
59 "float", 3.14, "struct", struct{ X, Y int }{93, 76})
60 }
61 }
62
63 //go:noinline
64 func doError(b *testing.B, log logr.Logger) {
65 err := fmt.Errorf("error message")
66 for i := 0; i < b.N; i++ {
67 log.Error(err, "multi",
68 "bool", true, "string", "str", "int", 42,
69 "float", 3.14, "struct", struct{ X, Y int }{93, 76})
70 }
71 }
72
73 //go:noinline
74 func doWithValues(b *testing.B, log logr.Logger) {
75 for i := 0; i < b.N; i++ {
76 l := log.WithValues("k1", "v1", "k2", "v2")
77 _ = l
78 }
79 }
80
81 //go:noinline
82 func doWithName(b *testing.B, log logr.Logger) {
83 for i := 0; i < b.N; i++ {
84 l := log.WithName("name")
85 _ = l
86 }
87 }
88
89 func BenchmarkDiscardInfoOneArg(b *testing.B) {
90 var log logr.Logger = logr.Discard()
91 doInfoOneArg(b, log)
92 }
93
94 func BenchmarkDiscardInfoSeveralArgs(b *testing.B) {
95 var log logr.Logger = logr.Discard()
96 doInfoSeveralArgs(b, log)
97 }
98
99 func BenchmarkDiscardV0Info(b *testing.B) {
100 var log logr.Logger = logr.Discard()
101 doV0Info(b, log)
102 }
103
104 func BenchmarkDiscardV9Info(b *testing.B) {
105 var log logr.Logger = logr.Discard()
106 doV9Info(b, log)
107 }
108
109 func BenchmarkDiscardError(b *testing.B) {
110 var log logr.Logger = logr.Discard()
111 doError(b, log)
112 }
113
114 func BenchmarkDiscardWithValues(b *testing.B) {
115 var log logr.Logger = logr.Discard()
116 doWithValues(b, log)
117 }
118
119 func BenchmarkDiscardWithName(b *testing.B) {
120 var log logr.Logger = logr.Discard()
121 doWithName(b, log)
122 }
123
124 func BenchmarkFuncrInfoOneArg(b *testing.B) {
125 var log logr.Logger = funcr.New(noop, funcr.Options{})
126 doInfoOneArg(b, log)
127 }
128
129 func BenchmarkFuncrInfoSeveralArgs(b *testing.B) {
130 var log logr.Logger = funcr.New(noop, funcr.Options{})
131 doInfoSeveralArgs(b, log)
132 }
133
134 func BenchmarkFuncrV0Info(b *testing.B) {
135 var log logr.Logger = funcr.New(noop, funcr.Options{})
136 doV0Info(b, log)
137 }
138
139 func BenchmarkFuncrV9Info(b *testing.B) {
140 var log logr.Logger = funcr.New(noop, funcr.Options{})
141 doV9Info(b, log)
142 }
143
144 func BenchmarkFuncrError(b *testing.B) {
145 var log logr.Logger = funcr.New(noop, funcr.Options{})
146 doError(b, log)
147 }
148
149 func BenchmarkFuncrWithValues(b *testing.B) {
150 var log logr.Logger = funcr.New(noop, funcr.Options{})
151 doWithValues(b, log)
152 }
153
154 func BenchmarkFuncrWithName(b *testing.B) {
155 var log logr.Logger = funcr.New(noop, funcr.Options{})
156 doWithName(b, log)
157 }
0 golang-github-go-logr-logr (1.0.0~rc1-1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Debian Janitor <janitor@jelmer.uk> Sat, 29 May 2021 19:57:10 -0000
5
06 golang-github-go-logr-logr (0.4.0-1) unstable; urgency=medium
17
28 * Team upload
1515
1616 package logr
1717
18 // Discard returns a valid Logger that discards all messages logged to it.
19 // It can be used whenever the caller is not interested in the logs.
18 // Discard returns a Logger that discards all messages logged to it. It can be
19 // used whenever the caller is not interested in the logs. Logger instances
20 // produced by this function always compare as equal.
2021 func Discard() Logger {
21 return DiscardLogger{}
22 return Logger{
23 level: 0,
24 sink: discardLogSink{},
25 }
2226 }
2327
24 // DiscardLogger is a Logger that discards all messages.
25 type DiscardLogger struct{}
28 // discardLogSink is a LogSink that discards all messages.
29 type discardLogSink struct{}
2630
27 func (l DiscardLogger) Enabled() bool {
31 // Verify that it actually implements the interface
32 var _ LogSink = discardLogSink{}
33
34 func (l discardLogSink) Init(RuntimeInfo) {
35 }
36
37 func (l discardLogSink) Enabled(int) bool {
2838 return false
2939 }
3040
31 func (l DiscardLogger) Info(msg string, keysAndValues ...interface{}) {
41 func (l discardLogSink) Info(int, string, ...interface{}) {
3242 }
3343
34 func (l DiscardLogger) Error(err error, msg string, keysAndValues ...interface{}) {
44 func (l discardLogSink) Error(error, string, ...interface{}) {
3545 }
3646
37 func (l DiscardLogger) V(level int) Logger {
47 func (l discardLogSink) WithValues(...interface{}) LogSink {
3848 return l
3949 }
4050
41 func (l DiscardLogger) WithValues(keysAndValues ...interface{}) Logger {
51 func (l discardLogSink) WithName(string) LogSink {
4252 return l
4353 }
44
45 func (l DiscardLogger) WithName(name string) Logger {
46 return l
47 }
48
49 // Verify that it actually implements the interface
50 var _ Logger = DiscardLogger{}
1717
1818 import (
1919 "errors"
20 "reflect"
2021 "testing"
2122 )
2223
2324 func TestDiscard(t *testing.T) {
2425 l := Discard()
25 if _, ok := l.(DiscardLogger); !ok {
26 if _, ok := l.sink.(discardLogSink); !ok {
2627 t.Error("did not return the expected underlying type")
2728 }
2829 // Verify that none of the methods panic, there is not more we can test.
3031 l.Info("Hello world", "x", 1, "y", 2)
3132 l.V(1).Error(errors.New("foo"), "a", 123)
3233 if l.Enabled() {
33 t.Error("discard logger must always say it is disabled")
34 t.Error("discardLogSink must always say it is disabled")
3435 }
3536 }
37
38 func TestComparable(t *testing.T) {
39 a := Discard()
40 if !reflect.TypeOf(a).Comparable() {
41 t.Fatal("discardLogSink must be comparable")
42 }
43
44 b := Discard()
45 if a != b {
46 t.Fatal("any two discardLogSink must be equal")
47 }
48 }
2323 "github.com/go-logr/logr"
2424 )
2525
26 // TabLogger is a sample logr.Logger that logs to stderr.
27 // It's terribly inefficient, and is *only* a basic example.
28 type TabLogger struct {
26 // tabLogSink is a sample logr.LogSink that logs to stderr.
27 // It's terribly inefficient, and is only a basic example.
28 type tabLogSink struct {
2929 name string
3030 keyValues map[string]interface{}
31
32 writer *tabwriter.Writer
31 writer *tabwriter.Writer
3332 }
3433
35 var _ logr.Logger = &TabLogger{}
34 var _ logr.LogSink = tabLogSink{}
3635
37 func (l *TabLogger) Info(msg string, kvs ...interface{}) {
36 func (_ tabLogSink) Init(info logr.RuntimeInfo) {
37 }
38
39 func (_ tabLogSink) Enabled(level int) bool {
40 return true
41 }
42
43 func (l tabLogSink) Info(level int, msg string, kvs ...interface{}) {
3844 fmt.Fprintf(l.writer, "%s\t%s\t", l.name, msg)
3945 for k, v := range l.keyValues {
4046 fmt.Fprintf(l.writer, "%s: %+v ", k, v)
4652 l.writer.Flush()
4753 }
4854
49 func (_ *TabLogger) Enabled() bool {
50 return true
55 func (l tabLogSink) Error(err error, msg string, kvs ...interface{}) {
56 kvs = append(kvs, "error", err)
57 l.Info(0, msg, kvs...)
5158 }
5259
53 func (l *TabLogger) Error(err error, msg string, kvs ...interface{}) {
54 kvs = append(kvs, "error", err)
55 l.Info(msg, kvs...)
56 }
57
58 func (l *TabLogger) V(_ int) logr.Logger {
59 return l
60 }
61
62 func (l *TabLogger) WithName(name string) logr.Logger {
63 return &TabLogger{
60 func (l tabLogSink) WithName(name string) logr.LogSink {
61 return tabLogSink{
6462 name: l.name + "." + name,
6563 keyValues: l.keyValues,
6664 writer: l.writer,
6765 }
6866 }
6967
70 func (l *TabLogger) WithValues(kvs ...interface{}) logr.Logger {
68 func (l tabLogSink) WithValues(kvs ...interface{}) logr.LogSink {
7169 newMap := make(map[string]interface{}, len(l.keyValues)+len(kvs)/2)
7270 for k, v := range l.keyValues {
7371 newMap[k] = v
7573 for i := 0; i < len(kvs); i += 2 {
7674 newMap[kvs[i].(string)] = kvs[i+1]
7775 }
78 return &TabLogger{
76 return tabLogSink{
7977 name: l.name,
8078 keyValues: newMap,
8179 writer: l.writer,
8280 }
8381 }
8482
83 // NewTabLogger is the main entry-point to this implementation. App developers
84 // call this somewhere near main() and thenceforth only deal with logr.Logger.
8585 func NewTabLogger() logr.Logger {
86 return &TabLogger{
86 sink := tabLogSink{
8787 writer: tabwriter.NewWriter(os.Stderr, 40, 8, 2, '\t', 0),
8888 }
89 return logr.New(sink)
8990 }
0 /*
1 Copyright 2021 The logr 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
16 // Package funcr implements github.com/go-logr/logr.Logger in terms of
17 // an arbitrary "write" function.
18 package funcr
19
20 import (
21 "bytes"
22 "fmt"
23 "path/filepath"
24 "reflect"
25 "runtime"
26 "strconv"
27 "strings"
28 "sync/atomic"
29
30 "github.com/go-logr/logr"
31 )
32
33 // New returns a logr.Logger which is implemented by an arbitrary function.
34 func New(fn func(prefix, args string), opts Options) logr.Logger {
35 fnl := &fnlogger{
36 prefix: "",
37 values: nil,
38 depth: 0,
39 write: fn,
40 logCaller: opts.LogCaller,
41 verbosity: opts.Verbosity,
42 }
43 return logr.New(fnl)
44 }
45
46 // Options carries parameters which influence the way logs are generated.
47 type Options struct {
48 // LogCaller tells funcr to add a "caller" key to some or all log lines.
49 // This has some overhead, so some users might not want it.
50 LogCaller MessageClass
51
52 // Verbosity tells funcr which V logs to be write. Higher values enable
53 // more logs.
54 Verbosity int
55 }
56
57 // MessageClass indicates which category or categories of messages to consider.
58 type MessageClass int
59
60 const (
61 None MessageClass = iota
62 All
63 Info
64 Error
65 )
66
67 type fnlogger struct {
68 prefix string
69 values []interface{}
70 depth int
71 write func(prefix, args string)
72 logCaller MessageClass
73 verbosity int
74 }
75
76 // Assert conformance to the interfaces.
77 var _ logr.LogSink = &fnlogger{}
78 var _ logr.CallDepthLogSink = &fnlogger{}
79
80 // Magic string for intermediate frames that we should ignore.
81 const autogeneratedFrameName = "<autogenerated>"
82
83 // Cached depth of this interface's log functions.
84 var framesAtomic int32 // atomic
85
86 // Discover how many frames we need to climb to find the caller. This approach
87 // was suggested by Ian Lance Taylor of the Go team, so it *should* be safe
88 // enough (famous last words) and should survive changes in Go's optimizer.
89 //
90 // This assumes that all logging paths are the same depth from the caller,
91 // which should be a reasonable assumption since they are part of the same
92 // interface.
93 func framesToCaller() int {
94 // Figuring out the current depth is somewhat expensive. Saving the value
95 // amortizes most of that runtime cost.
96 if atomic.LoadInt32(&framesAtomic) != 0 {
97 return int(framesAtomic)
98 }
99 // 1 is the immediate caller. 3 should be too many.
100 for i := 1; i < 3; i++ {
101 _, file, _, _ := runtime.Caller(i + 1) // +1 for this function's frame
102 if file != autogeneratedFrameName {
103 atomic.StoreInt32(&framesAtomic, int32(i))
104 return i
105 }
106 }
107 return 1 // something went wrong, this is safe
108 }
109
110 func flatten(kvList ...interface{}) string {
111 if len(kvList)%2 != 0 {
112 kvList = append(kvList, "<no-value>")
113 }
114 // Empirically bytes.Buffer is faster than strings.Builder for this.
115 buf := bytes.NewBuffer(make([]byte, 0, 1024))
116 for i := 0; i < len(kvList); i += 2 {
117 k, ok := kvList[i].(string)
118 if !ok {
119 k = fmt.Sprintf("<non-string-key-%d>", i/2)
120 }
121 v := kvList[i+1]
122
123 if i > 0 {
124 buf.WriteRune(' ')
125 }
126 buf.WriteRune('"')
127 buf.WriteString(k)
128 buf.WriteRune('"')
129 buf.WriteRune('=')
130 buf.WriteString(pretty(v))
131 }
132 return buf.String()
133 }
134
135 func pretty(value interface{}) string {
136 return prettyWithFlags(value, 0)
137 }
138
139 const (
140 flagRawString = 0x1
141 )
142
143 // TODO: This is not fast. Most of the overhead goes here.
144 func prettyWithFlags(value interface{}, flags uint32) string {
145 // Handling the most common types without reflect is a small perf win.
146 switch v := value.(type) {
147 case bool:
148 return strconv.FormatBool(v)
149 case string:
150 if flags&flagRawString > 0 {
151 return v
152 }
153 // This is empirically faster than strings.Builder.
154 return `"` + v + `"`
155 case int:
156 return strconv.FormatInt(int64(v), 10)
157 case int8:
158 return strconv.FormatInt(int64(v), 10)
159 case int16:
160 return strconv.FormatInt(int64(v), 10)
161 case int32:
162 return strconv.FormatInt(int64(v), 10)
163 case int64:
164 return strconv.FormatInt(int64(v), 10)
165 case uint:
166 return strconv.FormatUint(uint64(v), 10)
167 case uint8:
168 return strconv.FormatUint(uint64(v), 10)
169 case uint16:
170 return strconv.FormatUint(uint64(v), 10)
171 case uint32:
172 return strconv.FormatUint(uint64(v), 10)
173 case uint64:
174 return strconv.FormatUint(v, 10)
175 case uintptr:
176 return strconv.FormatUint(uint64(v), 10)
177 case float32:
178 return strconv.FormatFloat(float64(v), 'f', -1, 32)
179 case float64:
180 return strconv.FormatFloat(v, 'f', -1, 64)
181 }
182
183 buf := bytes.NewBuffer(make([]byte, 0, 256))
184 t := reflect.TypeOf(value)
185 if t == nil {
186 return "null"
187 }
188 v := reflect.ValueOf(value)
189 switch t.Kind() {
190 case reflect.Bool:
191 return strconv.FormatBool(v.Bool())
192 case reflect.String:
193 if flags&flagRawString > 0 {
194 return v.String()
195 }
196 // This is empirically faster than strings.Builder.
197 return `"` + v.String() + `"`
198 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
199 return strconv.FormatInt(int64(v.Int()), 10)
200 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
201 return strconv.FormatUint(uint64(v.Uint()), 10)
202 case reflect.Float32:
203 return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
204 case reflect.Float64:
205 return strconv.FormatFloat(v.Float(), 'f', -1, 64)
206 case reflect.Struct:
207 buf.WriteRune('{')
208 for i := 0; i < t.NumField(); i++ {
209 f := t.Field(i)
210 if f.PkgPath != "" {
211 // reflect says this field is only defined for non-exported fields.
212 continue
213 }
214 if i > 0 {
215 buf.WriteRune(',')
216 }
217 buf.WriteRune('"')
218 name := f.Name
219 if tag, found := f.Tag.Lookup("json"); found {
220 if comma := strings.Index(tag, ","); comma != -1 {
221 name = tag[:comma]
222 } else {
223 name = tag
224 }
225 }
226 buf.WriteString(name)
227 buf.WriteRune('"')
228 buf.WriteRune(':')
229 buf.WriteString(pretty(v.Field(i).Interface()))
230 }
231 buf.WriteRune('}')
232 return buf.String()
233 case reflect.Slice, reflect.Array:
234 buf.WriteRune('[')
235 for i := 0; i < v.Len(); i++ {
236 if i > 0 {
237 buf.WriteRune(',')
238 }
239 e := v.Index(i)
240 buf.WriteString(pretty(e.Interface()))
241 }
242 buf.WriteRune(']')
243 return buf.String()
244 case reflect.Map:
245 buf.WriteRune('{')
246 // This does not sort the map keys, for best perf.
247 it := v.MapRange()
248 i := 0
249 for it.Next() {
250 if i > 0 {
251 buf.WriteRune(',')
252 }
253 // JSON only does string keys.
254 buf.WriteRune('"')
255 buf.WriteString(prettyWithFlags(it.Key().Interface(), flagRawString))
256 buf.WriteRune('"')
257 buf.WriteRune(':')
258 buf.WriteString(pretty(it.Value().Interface()))
259 i++
260 }
261 buf.WriteRune('}')
262 return buf.String()
263 case reflect.Ptr, reflect.Interface:
264 return pretty(v.Elem().Interface())
265 }
266 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
267 }
268
269 type callerID struct {
270 File string `json:"file"`
271 Line int `json:"line"`
272 }
273
274 func (l fnlogger) caller() callerID {
275 // +1 for this frame.
276 _, file, line, ok := runtime.Caller(framesToCaller() + l.depth + 1)
277 if !ok {
278 return callerID{"<unknown>", 0}
279 }
280 return callerID{filepath.Base(file), line}
281 }
282
283 func (l *fnlogger) Init(info logr.RuntimeInfo) {
284 l.depth += info.CallDepth
285 }
286
287 func (l fnlogger) Enabled(level int) bool {
288 return level <= l.verbosity
289 }
290
291 func (l fnlogger) Info(level int, msg string, kvList ...interface{}) {
292 args := make([]interface{}, 0, 64) // using a constant here impacts perf
293 if l.logCaller == All || l.logCaller == Info {
294 args = append(args, "caller", l.caller())
295 }
296 args = append(args, "level", level, "msg", msg)
297 args = append(args, l.values...)
298 args = append(args, kvList...)
299 argsStr := flatten(args...)
300 l.write(l.prefix, argsStr)
301 }
302
303 func (l fnlogger) Error(err error, msg string, kvList ...interface{}) {
304 args := make([]interface{}, 0, 64) // using a constant here impacts perf
305 if l.logCaller == All || l.logCaller == Error {
306 args = append(args, "caller", l.caller())
307 }
308 args = append(args, "msg", msg)
309 var loggableErr interface{}
310 if err != nil {
311 loggableErr = err.Error()
312 }
313 args = append(args, "error", loggableErr)
314 args = append(args, l.values...)
315 args = append(args, kvList...)
316 argsStr := flatten(args...)
317 l.write(l.prefix, argsStr)
318 }
319
320 // WithName returns a new Logger with the specified name appended. funcr
321 // uses '/' characters to separate name elements. Callers should not pass '/'
322 // in the provided name string, but this library does not actually enforce that.
323 func (l *fnlogger) WithName(name string) logr.LogSink {
324 l2 := &fnlogger{}
325 *l2 = *l
326 if len(l2.prefix) > 0 {
327 l.prefix = l2.prefix + "/"
328 }
329 l2.prefix += name
330 return l
331 }
332
333 func (l *fnlogger) WithValues(kvList ...interface{}) logr.LogSink {
334 l2 := &fnlogger{}
335 *l2 = *l
336 // Three slice args forces a copy.
337 n := len(l.values)
338 l2.values = append(l2.values[:n:n], kvList...)
339 return l2
340 }
341
342 func (l *fnlogger) WithCallDepth(depth int) logr.LogSink {
343 l2 := &fnlogger{}
344 *l2 = *l
345 l2.depth += depth
346 return l2
347 }
0 /*
1 Copyright 2021 The logr 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
16 package funcr
17
18 import (
19 "encoding/json"
20 "fmt"
21 "testing"
22 )
23
24 type substr string
25
26 func ptrint(i int) *int {
27 return &i
28 }
29 func ptrstr(s string) *string {
30 return &s
31 }
32
33 func TestPretty(t *testing.T) {
34 cases := []interface{}{
35 "strval",
36 substr("substrval"),
37 true,
38 false,
39 int(93),
40 int8(93),
41 int16(93),
42 int32(93),
43 int64(93),
44 int(-93),
45 int8(-93),
46 int16(-93),
47 int32(-93),
48 int64(-93),
49 uint(93),
50 uint8(93),
51 uint16(93),
52 uint32(93),
53 uint64(93),
54 uintptr(93),
55 float32(93.76),
56 float64(93.76),
57 ptrint(93),
58 ptrstr("pstrval"),
59 []int{9, 3, 7, 6},
60 [4]int{9, 3, 7, 6},
61 struct {
62 Int int
63 String string
64 }{
65 93, "seventy-six",
66 },
67 map[string]int{
68 "nine": 3,
69 },
70 map[substr]int{
71 "nine": 3,
72 },
73 fmt.Errorf("error"),
74 struct {
75 X int `json:"x"`
76 Y int `json:"y"`
77 }{
78 93, 76,
79 },
80 struct {
81 X []int
82 Y map[int]int
83 Z struct{ P, Q int }
84 }{
85 []int{9, 3, 7, 6},
86 map[int]int{9: 3},
87 struct{ P, Q int }{9, 3},
88 },
89 []struct{ X, Y string }{
90 {"nine", "three"},
91 {"seven", "six"},
92 },
93 }
94
95 for i, tc := range cases {
96 ours := pretty(tc)
97 std, err := json.Marshal(tc)
98 if err != nil {
99 t.Errorf("[%d]: unexpected error: %v", i, err)
100 }
101 if ours != string(std) {
102 t.Errorf("[%d]: expected %q, got %q", i, std, ours)
103 }
104 }
105 }
+274
-166
logr.go less more
1515
1616 // This design derives from Dave Cheney's blog:
1717 // http://dave.cheney.net/2015/11/05/lets-talk-about-logging
18 //
19 // This is a BETA grade API. Until there is a significant 2nd implementation,
20 // I don't really know how it will change.
21
22 // Package logr defines abstract interfaces for logging. Packages can depend on
23 // these interfaces and callers can implement logging in whatever way is
24 // appropriate.
18
19 // Package logr defines a general-purpose logging API and abstract interfaces
20 // to back that API. Packages in the Go ecosystem can depend on this package,
21 // while callers can implement logging with whatever backend is appropriate.
2522 //
2623 // Usage
27 //
28 // Logging is done using a Logger. Loggers can have name prefixes and named
29 // values attached, so that all log messages logged with that Logger have some
30 // base context associated.
31 //
32 // The term "key" is used to refer to the name associated with a particular
33 // value, to disambiguate it from the general Logger name.
34 //
35 // For instance, suppose we're trying to reconcile the state of an object, and
36 // we want to log that we've made some decision.
37 //
38 // With the traditional log package, we might write:
24 // -----
25 //
26 // Logging is done using a Logger instance. Logger is a concrete type with
27 // methods, which defers the actual logging to a LogSink interface. The main
28 // methods of Logger are Info() and Error(). Arguments to Info() and Error()
29 // are key/value pairs rather than printf-style formatted strings, emphasizing
30 // "structured logging".
31 //
32 // With Go's standard log package, we might write:
33 // log.Printf("setting target value %s", targetValue)
34 //
35 // With logr's structured logging, we'd write:
36 // logger.Info("setting target", "value", targetValue)
37 //
38 // Errors are much the same. Instead of:
39 // log.Printf("failed to open the pod bay door for user %s: %v", user, err)
40 //
41 // We'd write:
42 // logger.Error(err, "failed to open the pod bay door", "user", user)
43 //
44 // Info() and Error() are very similar, but they are separate methods so that
45 // LogSink implementations can choose to do things like attach additional
46 // information (such as stack traces) on calls to Error().
47 //
48 // Verbosity
49 // ---------
50 //
51 // Often we want to log information only when the application in "verbose
52 // mode". To write log-lines that are more verbose, Logger has a V() method.
53 // The higher the V-level of a log-line, the less critical it is considered.
54 // Log-lines with V-levels that are not enabled (as per the LogSink) will not
55 // be written. Level V(0) is the default, and logger.V(0).Info() has the same
56 // meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
57 //
58 // Where we might have written:
59 // if flVerbose >= 2 {
60 // log.Printf("an unusual thing happened")
61 // }
62 //
63 // We can write:
64 // logger.V(2).Info("an unusual thing happened")
65 //
66 // Logger Names
67 // ------------
68 //
69 // Logger instances can have name strings so that all messages logged through
70 // that instance have additional context. For example, you might want to add
71 // a subsystem name:
72 //
73 // logger.WithName("compactor").Info("started", "time", time.Now())
74 //
75 // The WithName() method returns a new Logger, which can be passed to
76 // constructors or other functions for further use. Repeated use of WithName()
77 // will accumulate name "segments". These name segments will be joined in some
78 // way by the LogSink implementation. It is strongly recommended that name
79 // segments contain simple identifiers (letters, digits, and hyphen), and do
80 // not contain characters that could muddle the log output or confuse the
81 // joining operation (e.g. whitespace, commas, periods, slashes, brackets,
82 // quotes, etc).
83 //
84 // Saved Values
85 // ------------
86 //
87 // Logger instances can store any number of key/value pairs, which will be
88 // logged alongside all messages logged through that instance. For example,
89 // you might want to create a Logger instance per managed object:
90 //
91 // With the standard log package, we might write:
3992 // log.Printf("decided to set field foo to value %q for object %s/%s",
4093 // targetValue, object.Namespace, object.Name)
4194 //
42 // With logr's structured logging, we'd write:
43 // // elsewhere in the file, set up the logger to log with the prefix of
44 // // "reconcilers", and the named value target-type=Foo, for extra context.
45 // log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
95 // With logr's we'd write:
96 // // Elsewhere: set up the logger to log the object name.
97 // obj.logger = mainLogger.WithValues(
98 // "name", obj.name, "namespace", obj.namespace)
4699 //
47100 // // later on...
48 // log.Info("setting foo on object", "value", targetValue, "object", object)
49 //
50 // Depending on our logging implementation, we could then make logging decisions
51 // based on field values (like only logging such events for objects in a certain
52 // namespace), or copy the structured information into a structured log store.
53 //
54 // For logging errors, Logger has a method called Error. Suppose we wanted to
55 // log an error while reconciling. With the traditional log package, we might
56 // write:
57 // log.Errorf("unable to reconcile object %s/%s: %v", object.Namespace, object.Name, err)
58 //
59 // With logr, we'd instead write:
60 // // assuming the above setup for log
61 // log.Error(err, "unable to reconcile object", "object", object)
62 //
63 // This functions similarly to:
64 // log.Info("unable to reconcile object", "error", err, "object", object)
65 //
66 // However, it ensures that a standard key for the error value ("error") is used
67 // across all error logging. Furthermore, certain implementations may choose to
68 // attach additional information (such as stack traces) on calls to Error, so
69 // it's preferred to use Error to log errors.
70 //
71 // Parts of a log line
72 //
73 // Each log message from a Logger has four types of context:
74 // logger name, log verbosity, log message, and the named values.
75 //
76 // The Logger name consists of a series of name "segments" added by successive
77 // calls to WithName. These name segments will be joined in some way by the
78 // underlying implementation. It is strongly recommended that name segments
79 // contain simple identifiers (letters, digits, and hyphen), and do not contain
80 // characters that could muddle the log output or confuse the joining operation
81 // (e.g. whitespace, commas, periods, slashes, brackets, quotes, etc).
82 //
83 // Log verbosity represents how little a log matters. Level zero, the default,
84 // matters most. Increasing levels matter less and less. Try to avoid lots of
85 // different verbosity levels, and instead provide useful keys, logger names,
86 // and log messages for users to filter on. It's illegal to pass a log level
87 // below zero.
101 // obj.logger.Info("setting foo", "value", targetValue)
102 //
103 // Best Practices
104 // --------------
105 //
106 // Logger has very few hard rules, with the goal that LogSink implementations
107 // might have a lot of freedom to differentiate. There are, however, some
108 // things to consider.
88109 //
89110 // The log message consists of a constant message attached to the log line.
90111 // This should generally be a simple description of what's occurring, and should
91 // never be a format string.
92 //
93 // Variable information can then be attached using named values (key/value
94 // pairs). Keys are arbitrary strings, while values may be any Go value.
112 // never be a format string. Variable information can then be attached using
113 // named values.
114 //
115 // Keys are arbitrary strings, but should generally be constant values. Values
116 // may be any Go value, but how the value is formatted is determined by the
117 // LogSink implementation.
95118 //
96119 // Key Naming Conventions
120 // ----------------------
97121 //
98122 // Keys are not strictly required to conform to any specification or regex, but
99123 // it is recommended that they:
123147 // above concepts, when necessary (for example, in a pure-JSON output form, it
124148 // would be necessary to represent at least message and timestamp as ordinary
125149 // named values).
150 //
151 // Break Glass
152 // -----------
126153 //
127154 // Implementations may choose to give callers access to the underlying
128155 // logging implementation. The recommended pattern for this is:
139166 "context"
140167 )
141168
142 // TODO: consider adding back in format strings if they're really needed
143 // TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects)
144 // TODO: consider other bits of glog functionality like Flush, OutputStats
145
146 // Logger represents the ability to log messages, both errors and not.
147 type Logger interface {
148 // Enabled tests whether this Logger is enabled. For example, commandline
149 // flags might be used to set the logging verbosity and disable some info
150 // logs.
151 Enabled() bool
152
153 // Info logs a non-error message with the given key/value pairs as context.
154 //
155 // The msg argument should be used to add some constant description to
156 // the log line. The key/value pairs can then be used to add additional
157 // variable information. The key/value pairs should alternate string
158 // keys and arbitrary values.
159 Info(msg string, keysAndValues ...interface{})
160
161 // Error logs an error, with the given message and key/value pairs as context.
162 // It functions similarly to calling Info with the "error" named value, but may
163 // have unique behavior, and should be preferred for logging errors (see the
164 // package documentations for more information).
165 //
166 // The msg field should be used to add context to any underlying error,
167 // while the err field should be used to attach the actual error that
168 // triggered this log line, if present.
169 Error(err error, msg string, keysAndValues ...interface{})
170
171 // V returns an Logger value for a specific verbosity level, relative to
172 // this Logger. In other words, V values are additive. V higher verbosity
173 // level means a log message is less important. It's illegal to pass a log
174 // level less than zero.
175 V(level int) Logger
176
177 // WithValues adds some key-value pairs of context to a logger.
178 // See Info for documentation on how key/value pairs work.
179 WithValues(keysAndValues ...interface{}) Logger
180
181 // WithName adds a new element to the logger's name.
182 // Successive calls with WithName continue to append
183 // suffixes to the logger's name. It's strongly recommended
184 // that name segments contain only letters, digits, and hyphens
185 // (see the package documentation for more information).
186 WithName(name string) Logger
187 }
188
189 // InfoLogger provides compatibility with code that relies on the v0.1.0
190 // interface.
191 //
192 // Deprecated: InfoLogger is an artifact of early versions of this API. New
193 // users should never use it and existing users should use Logger instead. This
194 // will be removed in a future release.
195 type InfoLogger = Logger
196
169 // New returns a new Logger instance. This is primarily used by libraries
170 // implementing LogSink, rather than end users.
171 func New(sink LogSink) Logger {
172 logger := Logger{
173 sink: sink,
174 }
175 if withCallDepth, ok := sink.(CallDepthLogSink); ok {
176 logger.withCallDepth = withCallDepth
177 }
178 sink.Init(runtimeInfo)
179 return logger
180 }
181
182 // Logger is an interface to an abstract logging implementation. This is a
183 // concrete type for performance reasons, but all the real work is passed on
184 // to a LogSink. Implementations of LogSink should provide their own
185 // constructors that return Logger, not LogSink.
186 type Logger struct {
187 level int
188 sink LogSink
189 withCallDepth CallDepthLogSink
190 }
191
192 // Enabled tests whether this Logger is enabled. For example, commandline
193 // flags might be used to set the logging verbosity and disable some info
194 // logs.
195 func (l Logger) Enabled() bool {
196 return l.sink.Enabled(l.level)
197 }
198
199 // Info logs a non-error message with the given key/value pairs as context.
200 //
201 // The msg argument should be used to add some constant description to
202 // the log line. The key/value pairs can then be used to add additional
203 // variable information. The key/value pairs must alternate string
204 // keys and arbitrary values.
205 func (l Logger) Info(msg string, keysAndValues ...interface{}) {
206 if l.Enabled() {
207 l.sink.Info(l.level, msg, keysAndValues...)
208 }
209 }
210
211 // Error logs an error, with the given message and key/value pairs as context.
212 // It functions similarly to Info, but may have unique behavior, and should be
213 // preferred for logging errors (see the package documentations for more
214 // information).
215 //
216 // The msg argument should be used to add context to any underlying error,
217 // while the err argument should be used to attach the actual error that
218 // triggered this log line, if present.
219 func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
220 l.sink.Error(err, msg, keysAndValues...)
221 }
222
223 // V returns a new Logger instance for a specific verbosity level, relative to
224 // this Logger. In other words, V-levels are additive. A higher verbosity
225 // level means a log message is less important. Negative V-levels are treated
226 // as 0.
227 func (l Logger) V(level int) Logger {
228 if level < 0 {
229 level = 0
230 }
231 l.level += level
232 return l
233 }
234
235 // WithValues returns a new Logger instance with additional key/value pairs.
236 // See Info for documentation on how key/value pairs work.
237 func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
238 l.sink = l.sink.WithValues(keysAndValues...)
239 return l
240 }
241
242 // WithName returns a new Logger instance with the specified name element added
243 // to the Logger's name. Successive calls with WithName append additional
244 // suffixes to the Logger's name. It's strongly recommended that name segments
245 // contain only letters, digits, and hyphens (see the package documentation for
246 // more information).
247 func (l Logger) WithName(name string) Logger {
248 l.sink = l.sink.WithName(name)
249 return l
250 }
251
252 // WithCallDepth returns a Logger instance that offsets the call stack by the
253 // specified number of frames when logging call site information, if possible.
254 // This is useful for users who have helper functions between the "real" call
255 // site and the actual calls to Logger methods. If depth is 0 the attribution
256 // should be to the direct caller of this function. If depth is 1 the
257 // attribution should skip 1 call frame, and so on. Successive calls to this
258 // are additive.
259 //
260 // If the underlying log implementation supports a WithCallDepth(int) method,
261 // it will be called and the result returned. If the implementation does not
262 // support CallDepthLogSink, the original Logger will be returned.
263 func (l Logger) WithCallDepth(depth int) Logger {
264 if l.withCallDepth == nil {
265 return l
266 }
267 l.sink = l.withCallDepth.WithCallDepth(depth)
268 return l
269 }
270
271 // contextKey is how we find Loggers in a context.Context.
197272 type contextKey struct{}
198273
199 // FromContext returns a Logger constructed from ctx or nil if no
200 // logger details are found.
201 func FromContext(ctx context.Context) Logger {
274 // FromContext returns a Logger from ctx or an error if no Logger is found.
275 func FromContext(ctx context.Context) (Logger, error) {
202276 if v, ok := ctx.Value(contextKey{}).(Logger); ok {
203 return v
204 }
205
206 return nil
207 }
208
209 // FromContextOrDiscard returns a Logger constructed from ctx or a Logger
210 // that discards all messages if no logger details are found.
277 return v, nil
278 }
279
280 return Logger{}, notFoundError{}
281 }
282
283 // notFoundError exists to carry an IsNotFound method.
284 type notFoundError struct{}
285
286 func (notFoundError) Error() string {
287 return "no logr.Logger was present"
288 }
289
290 func (notFoundError) IsNotFound() bool {
291 return true
292 }
293
294 // FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
295 // returns a Logger that discards all log messages.
211296 func FromContextOrDiscard(ctx context.Context) Logger {
212297 if v, ok := ctx.Value(contextKey{}).(Logger); ok {
213298 return v
216301 return Discard()
217302 }
218303
219 // NewContext returns a new context derived from ctx that embeds the Logger.
220 func NewContext(ctx context.Context, l Logger) context.Context {
221 return context.WithValue(ctx, contextKey{}, l)
222 }
223
224 // CallDepthLogger represents a Logger that knows how to climb the call stack
304 // NewContext returns a new Context, derived from ctx, which carries the
305 // provided Logger.
306 func NewContext(ctx context.Context, logger Logger) context.Context {
307 return context.WithValue(ctx, contextKey{}, logger)
308 }
309
310 // RuntimeInfo holds information that the logr "core" library knows which
311 // LogSinks might want to know.
312 type RuntimeInfo struct {
313 // CallDepth is the number of call frames the logr library adds between the
314 // end-user and the LogSink. LogSink implementations which choose to print
315 // the original logging site (e.g. file & line) should climb this many
316 // additional frames to find it.
317 CallDepth int
318 }
319
320 // runtimeInfo is a static global. It must not be changed at run time.
321 var runtimeInfo = RuntimeInfo{
322 CallDepth: 1,
323 }
324
325 // LogSink represents a logging implementation. End-users will generally not
326 // interact with this type.
327 type LogSink interface {
328 // Init receives optional information about the logr library for LogSink
329 // implementations that need it.
330 Init(info RuntimeInfo)
331
332 // Enabled tests whether this LogSink is enabled at the specified V-level.
333 // For example, commandline flags might be used to set the logging
334 // verbosity and disable some info logs.
335 Enabled(level int) bool
336
337 // Info logs a non-error message with the given key/value pairs as context.
338 // The level argument is provided for optional logging. This method will
339 // only be called when Enabled(level) is true. See Logger.Info for more
340 // details.
341 Info(level int, msg string, keysAndValues ...interface{})
342
343 // Error logs an error, with the given message and key/value pairs as
344 // context. See Logger.Error for more details.
345 Error(err error, msg string, keysAndValues ...interface{})
346
347 // WithValues returns a new LogSink with additional key/value pairs. See
348 // Logger.WithValues for more details.
349 WithValues(keysAndValues ...interface{}) LogSink
350
351 // WithName returns a new LogSink with the specified name appended. See
352 // Logger.WithName for more details.
353 WithName(name string) LogSink
354 }
355
356 // CallDepthLogSink represents a Logger that knows how to climb the call stack
225357 // to identify the original call site and can offset the depth by a specified
226358 // number of frames. This is useful for users who have helper functions
227359 // between the "real" call site and the actual calls to Logger methods.
231363 //
232364 // This is an optional interface and implementations are not required to
233365 // support it.
234 type CallDepthLogger interface {
235 Logger
236
366 type CallDepthLogSink interface {
237367 // WithCallDepth returns a Logger that will offset the call stack by the
238368 // specified number of frames when logging call site information. If depth
239369 // is 0 the attribution should be to the direct caller of this method. If
240370 // depth is 1 the attribution should skip 1 call frame, and so on.
241371 // Successive calls to this are additive.
242 WithCallDepth(depth int) Logger
243 }
244
245 // WithCallDepth returns a Logger that will offset the call stack by the
246 // specified number of frames when logging call site information, if possible.
247 // This is useful for users who have helper functions between the "real" call
248 // site and the actual calls to Logger methods. If depth is 0 the attribution
249 // should be to the direct caller of this function. If depth is 1 the
250 // attribution should skip 1 call frame, and so on. Successive calls to this
251 // are additive.
252 //
253 // If the underlying log implementation supports the CallDepthLogger interface,
254 // the WithCallDepth method will be called and the result returned. If the
255 // implementation does not support CallDepthLogger, the original Logger will be
256 // returned.
257 //
258 // Callers which care about whether this was supported or not should test for
259 // CallDepthLogger support themselves.
260 func WithCallDepth(logger Logger, depth int) Logger {
261 if decorator, ok := logger.(CallDepthLogger); ok {
262 return decorator.WithCallDepth(depth)
263 }
264 return logger
265 }
372 WithCallDepth(depth int) LogSink
373 }
1717
1818 import (
1919 "context"
20 "fmt"
21 "reflect"
2022 "testing"
2123 )
2224
23 // testLogger is a Logger just for testing that does nothing.
24 type testLogger struct{}
25
26 func (l *testLogger) Enabled() bool {
25 // testLogSink is a Logger just for testing that calls optional hooks on each method.
26 type testLogSink struct {
27 fnInit func(ri RuntimeInfo)
28 fnEnabled func(lvl int) bool
29 fnInfo func(lvl int, msg string, kv ...interface{})
30 fnError func(err error, msg string, kv ...interface{})
31 fnWithValues func(kv ...interface{})
32 fnWithName func(name string)
33 }
34
35 var _ LogSink = &testLogSink{}
36
37 func (l *testLogSink) Init(ri RuntimeInfo) {
38 if l.fnInit != nil {
39 l.fnInit(ri)
40 }
41 }
42
43 func (l *testLogSink) Enabled(lvl int) bool {
44 if l.fnEnabled != nil {
45 return l.fnEnabled(lvl)
46 }
2747 return false
2848 }
2949
30 func (l *testLogger) Info(msg string, keysAndValues ...interface{}) {
31 }
32
33 func (l *testLogger) Error(err error, msg string, keysAndValues ...interface{}) {
34 }
35
36 func (l *testLogger) V(level int) Logger {
37 return l
38 }
39
40 func (l *testLogger) WithValues(keysAndValues ...interface{}) Logger {
41 return l
42 }
43
44 func (l *testLogger) WithName(name string) Logger {
45 return l
46 }
47
48 // Verify that it actually implements the interface
49 var _ Logger = &testLogger{}
50 func (l *testLogSink) Info(lvl int, msg string, kv ...interface{}) {
51 if l.fnInfo != nil {
52 l.fnInfo(lvl, msg, kv...)
53 }
54 }
55
56 func (l *testLogSink) Error(err error, msg string, kv ...interface{}) {
57 if l.fnError != nil {
58 l.fnError(err, msg, kv...)
59 }
60 }
61
62 func (l *testLogSink) WithValues(kv ...interface{}) LogSink {
63 if l.fnWithValues != nil {
64 l.fnWithValues(kv...)
65 }
66 out := *l
67 return &out
68 }
69
70 func (l *testLogSink) WithName(name string) LogSink {
71 if l.fnWithName != nil {
72 l.fnWithName(name)
73 }
74 out := *l
75 return &out
76 }
77
78 type testCallDepthLogSink struct {
79 testLogSink
80 fnWithCallDepth func(depth int)
81 }
82
83 var _ CallDepthLogSink = &testCallDepthLogSink{}
84
85 func (l *testCallDepthLogSink) WithCallDepth(depth int) LogSink {
86 if l.fnWithCallDepth != nil {
87 l.fnWithCallDepth(depth)
88 }
89 out := *l
90 return &out
91 }
92
93 func TestNew(t *testing.T) {
94 calledInit := 0
95
96 sink := &testLogSink{}
97 sink.fnInit = func(ri RuntimeInfo) {
98 if ri.CallDepth != 1 {
99 t.Errorf("expected runtimeInfo.CallDepth = 1, got %d", ri.CallDepth)
100 }
101 calledInit++
102 }
103 logger := New(sink)
104
105 if logger.sink == nil {
106 t.Errorf("expected sink to be set, got %v", logger.sink)
107 }
108 if calledInit != 1 {
109 t.Errorf("expected sink.Init() to be called once, got %d", calledInit)
110 }
111 if logger.withCallDepth != nil {
112 t.Errorf("expected withCallDepth to be nil, got %v", logger.withCallDepth)
113 }
114 }
115
116 func TestNewCachesCallDepthInterface(t *testing.T) {
117 sink := &testCallDepthLogSink{}
118 logger := New(sink)
119
120 if logger.withCallDepth == nil {
121 t.Errorf("expected withCallDepth to be set")
122 }
123 }
124
125 func TestEnabled(t *testing.T) {
126 calledEnabled := 0
127
128 sink := &testLogSink{}
129 sink.fnEnabled = func(lvl int) bool {
130 calledEnabled++
131 return true
132 }
133 logger := New(sink)
134
135 if en := logger.Enabled(); en != true {
136 t.Errorf("expected true")
137 }
138 if calledEnabled != 1 {
139 t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
140 }
141 }
142
143 func TestError(t *testing.T) {
144 calledError := 0
145 errInput := fmt.Errorf("error")
146 msgInput := "msg"
147 kvInput := []interface{}{0, 1, 2}
148
149 sink := &testLogSink{}
150 sink.fnError = func(err error, msg string, kv ...interface{}) {
151 calledError++
152 if err != errInput {
153 t.Errorf("unexpected err input, got %v", err)
154 }
155 if msg != msgInput {
156 t.Errorf("unexpected msg input, got %q", msg)
157 }
158 if !reflect.DeepEqual(kv, kvInput) {
159 t.Errorf("unexpected kv input, got %v", kv)
160 }
161 }
162 logger := New(sink)
163
164 logger.Error(errInput, msgInput, kvInput...)
165 if calledError != 1 {
166 t.Errorf("expected sink.Error() to be called once, got %d", calledError)
167 }
168 }
169
170 func TestV(t *testing.T) {
171 sink := &testLogSink{}
172 logger := New(sink)
173
174 if l := logger.V(0); l.level != 0 {
175 t.Errorf("expected level 0, got %d", l.level)
176 }
177 if l := logger.V(93); l.level != 93 {
178 t.Errorf("expected level 93, got %d", l.level)
179 }
180 if l := logger.V(70).V(6); l.level != 76 {
181 t.Errorf("expected level 76, got %d", l.level)
182 }
183 if l := logger.V(-1); l.level != 0 {
184 t.Errorf("expected level 0, got %d", l.level)
185 }
186 if l := logger.V(1).V(-1); l.level != 1 {
187 t.Errorf("expected level 1, got %d", l.level)
188 }
189 }
190
191 func TestInfo(t *testing.T) {
192 calledEnabled := 0
193 calledInfo := 0
194 lvlInput := 0
195 msgInput := "msg"
196 kvInput := []interface{}{0, 1, 2}
197
198 sink := &testLogSink{}
199 sink.fnEnabled = func(lvl int) bool {
200 calledEnabled++
201 return lvl < 100
202 }
203 sink.fnInfo = func(lvl int, msg string, kv ...interface{}) {
204 calledInfo++
205 if lvl != lvlInput {
206 t.Errorf("unexpected lvl input, got %v", lvl)
207 }
208 if msg != msgInput {
209 t.Errorf("unexpected msg input, got %q", msg)
210 }
211 if !reflect.DeepEqual(kv, kvInput) {
212 t.Errorf("unexpected kv input, got %v", kv)
213 }
214 }
215 logger := New(sink)
216
217 calledEnabled = 0
218 calledInfo = 0
219 lvlInput = 0
220 logger.Info(msgInput, kvInput...)
221 if calledEnabled != 1 {
222 t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
223 }
224 if calledInfo != 1 {
225 t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
226 }
227
228 calledEnabled = 0
229 calledInfo = 0
230 lvlInput = 0
231 logger.V(0).Info(msgInput, kvInput...)
232 if calledEnabled != 1 {
233 t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
234 }
235 if calledInfo != 1 {
236 t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
237 }
238
239 calledEnabled = 0
240 calledInfo = 0
241 lvlInput = 93
242 logger.V(93).Info(msgInput, kvInput...)
243 if calledEnabled != 1 {
244 t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
245 }
246 if calledInfo != 1 {
247 t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
248 }
249
250 calledEnabled = 0
251 calledInfo = 0
252 lvlInput = 100
253 logger.V(100).Info(msgInput, kvInput...)
254 if calledEnabled != 1 {
255 t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
256 }
257 if calledInfo != 0 {
258 t.Errorf("expected sink.Info() to not be called, got %d", calledInfo)
259 }
260 }
261
262 func TestWithValues(t *testing.T) {
263 calledWithValues := 0
264 kvInput := []interface{}{"zero", 0, "one", 1, "two", 2}
265
266 sink := &testLogSink{}
267 sink.fnWithValues = func(kv ...interface{}) {
268 calledWithValues++
269 if !reflect.DeepEqual(kv, kvInput) {
270 t.Errorf("unexpected kv input, got %v", kv)
271 }
272 }
273 logger := New(sink)
274
275 out := logger.WithValues(kvInput...)
276 if calledWithValues != 1 {
277 t.Errorf("expected sink.WithValues() to be called once, got %d", calledWithValues)
278 }
279 if p := out.sink.(*testLogSink); p == sink {
280 t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
281 }
282 }
283
284 func TestWithName(t *testing.T) {
285 calledWithName := 0
286 nameInput := "name"
287
288 sink := &testLogSink{}
289 sink.fnWithName = func(name string) {
290 calledWithName++
291 if name != nameInput {
292 t.Errorf("unexpected name input, got %q", name)
293 }
294 }
295 logger := New(sink)
296
297 out := logger.WithName(nameInput)
298 if calledWithName != 1 {
299 t.Errorf("expected sink.WithName() to be called once, got %d", calledWithName)
300 }
301 if p := out.sink.(*testLogSink); p == sink {
302 t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
303 }
304 }
305
306 func TestWithCallDepthNotImplemented(t *testing.T) {
307 depthInput := 7
308
309 sink := &testLogSink{}
310 logger := New(sink)
311
312 out := logger.WithCallDepth(depthInput)
313 if p := out.sink.(*testLogSink); p != sink {
314 t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
315 }
316 }
317
318 func TestWithCallDepthImplemented(t *testing.T) {
319 calledWithCallDepth := 0
320 depthInput := 7
321
322 sink := &testCallDepthLogSink{}
323 sink.fnWithCallDepth = func(depth int) {
324 calledWithCallDepth++
325 if depth != depthInput {
326 t.Errorf("unexpected depth input, got %d", depth)
327 }
328 }
329 logger := New(sink)
330
331 out := logger.WithCallDepth(depthInput)
332 if calledWithCallDepth != 1 {
333 t.Errorf("expected sink.WithCallDepth() to be called once, got %d", calledWithCallDepth)
334 }
335 if p := out.sink.(*testCallDepthLogSink); p == sink {
336 t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
337 }
338 }
50339
51340 func TestContext(t *testing.T) {
52341 ctx := context.TODO()
53342
54 if out := FromContext(ctx); out != nil {
55 t.Errorf("expected nil logger, got %#v", out)
56 }
57 if out := FromContextOrDiscard(ctx); out == nil {
58 t.Errorf("expected non-nil logger")
59 } else if _, ok := out.(DiscardLogger); !ok {
60 t.Errorf("expected a DiscardLogger, got %#v", out)
61 }
62
63 logger := &testLogger{}
343 if out, err := FromContext(ctx); err == nil {
344 t.Errorf("expected error, got %#v", out)
345 } else if _, ok := err.(notFoundError); !ok {
346 t.Errorf("expected a notFoundError, got %#v", err)
347 }
348
349 out := FromContextOrDiscard(ctx)
350 if _, ok := out.sink.(discardLogSink); !ok {
351 t.Errorf("expected a discardLogSink, got %#v", out)
352 }
353
354 sink := &testLogSink{}
355 logger := New(sink)
64356 lctx := NewContext(ctx, logger)
65 if out := FromContext(lctx); out == nil {
66 t.Errorf("expected non-nil logger")
67 } else if out.(*testLogger) != logger {
68 t.Errorf("expected output to be the same as input: got in=%p, out=%p", logger, out)
69 }
70 if out := FromContextOrDiscard(lctx); out == nil {
71 t.Errorf("expected non-nil logger")
72 } else if out.(*testLogger) != logger {
73 t.Errorf("expected output to be the same as input: got in=%p, out=%p", logger, out)
74 }
75 }
76
77 // testCallDepthLogger is a Logger just for testing that does nothing.
78 type testCallDepthLogger struct {
79 *testLogger
80 depth int
81 }
82
83 func (l *testCallDepthLogger) WithCallDepth(depth int) Logger {
84 return &testCallDepthLogger{l.testLogger, l.depth + depth}
85 }
86
87 // Verify that it actually implements the interface
88 var _ CallDepthLogger = &testCallDepthLogger{}
89
90 func TestWithCallDepth(t *testing.T) {
91 // Test an impl that does not support it.
92 t.Run("not supported", func(t *testing.T) {
93 in := &testLogger{}
94 out := WithCallDepth(in, 42)
95 if out.(*testLogger) != in {
96 t.Errorf("expected output to be the same as input: got in=%p, out=%p", in, out)
97 }
98 })
99
100 // Test an impl that does support it.
101 t.Run("supported", func(t *testing.T) {
102 in := &testCallDepthLogger{&testLogger{}, 0}
103 out := WithCallDepth(in, 42)
104 if out.(*testCallDepthLogger) == in {
105 t.Errorf("expected output to be different than input: got in=out=%p", in)
106 }
107 if cdl := out.(*testCallDepthLogger); cdl.depth != 42 {
108 t.Errorf("expected depth=42, got %d", cdl.depth)
109 }
110 })
111 }
357 if out, err := FromContext(lctx); err != nil {
358 t.Errorf("unexpected error: %v", err)
359 } else if p := out.sink.(*testLogSink); p != sink {
360 t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
361 }
362 out = FromContextOrDiscard(lctx)
363 if p := out.sink.(*testLogSink); p != sink {
364 t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
365 }
366 }
+0
-26
testing/null.go less more
0 /*
1 Copyright 2019 The logr 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
16 package testing
17
18 import "github.com/go-logr/logr"
19
20 // NullLogger is a logr.Logger that does nothing.
21 //
22 // Deprecated: NullLogger is idenitcal to logr.DiscardLogger. It is retained
23 // for backwards compatibility, but new users should use logr.DiscardLogger
24 // instead.
25 type NullLogger = logr.DiscardLogger
1919 "testing"
2020
2121 "github.com/go-logr/logr"
22 "github.com/go-logr/logr/funcr"
2223 )
2324
24 // TestLogger is a logr.Logger that prints through a testing.T object.
25 // Only error logs will have any effect.
26 type TestLogger struct {
27 T *testing.T
25 // NewTestLogger returns a logr.Logger that prints through a testing.T object.
26 // Info logs are only enabled at V(0).
27 func NewTestLogger(t *testing.T) logr.Logger {
28 fn := func(prefix, args string) {
29 t.Logf("%s: %s", prefix, args)
30 }
31 return funcr.New(fn, funcr.Options{})
2832 }
29
30 var _ logr.Logger = TestLogger{}
31
32 func (_ TestLogger) Info(_ string, _ ...interface{}) {
33 // Do nothing.
34 }
35
36 func (_ TestLogger) Enabled() bool {
37 return false
38 }
39
40 func (log TestLogger) Error(err error, msg string, args ...interface{}) {
41 log.T.Logf("%s: %v -- %v", msg, err, args)
42 }
43
44 func (log TestLogger) V(v int) logr.Logger {
45 return log
46 }
47
48 func (log TestLogger) WithName(_ string) logr.Logger {
49 return log
50 }
51
52 func (log TestLogger) WithValues(_ ...interface{}) logr.Logger {
53 return log
54 }
0 /*
1 Copyright 2021 The logr 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
16 package testing
17
18 import (
19 "fmt"
20 "testing"
21 )
22
23 func TestTestLogger(t *testing.T) {
24 logger := NewTestLogger(t)
25 logger.Info("info")
26 logger.V(0).Info("V(0).info")
27 logger.V(1).Info("v(1).info")
28 logger.Error(fmt.Errorf("error"), "error")
29 }