Codebase list golang-github-go-logr-logr / 975f981
Import upstream version 1.0.0 Debian Janitor 2 years ago
16 changed file(s) with 1859 addition(s) and 415 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 two
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 implementers. 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 structures 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.Logger
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
16 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.
83 1. Dave basically proposes doing away with the notion of a logging API in favor
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
2390 function call and is logging that `error` *and not returning it*, use error
2491 logs.
2592
26 2) Verbosity-levels on info logs. This gives developers a chance to indicate
93 2. Verbosity-levels on info logs. This gives developers a chance to indicate
2794 arbitrary grades of importance for info logs, without assigning names with
28 semantic meaning such as "warning", "trace", and "debug". Superficially this
95 semantic meaning such as "warning", "trace", and "debug." Superficially this
2996 may feel very similar, but the primary difference is the lack of semantics.
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?
51
52 - **Structured logs are more easily queriable**: Since you've got
113 ## FAQ
114
115 ### Conceptual
116
117 #### Why structured logging?
118
119 - **Structured logs are more easily queryable**: Since you've got
53120 key-value pairs, it's much easier to query your structured logs for
54121 particular values by filtering on the contents of a particular key --
55122 think searching request logs for error codes, Kubernetes reconcilers for
56 the name and namespace of the reconciled object, etc
57
58 - **Structured logging makes it easier to have cross-referencable logs**:
123 the name and namespace of the reconciled object, etc.
124
125 - **Structured logging makes it easier to have cross-referenceable logs**:
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
66133 to log certain keys but not others, only log lines where a certain key
67 matches a certain value, etc, instead of just having v-levels and names
134 matches a certain value, etc., instead of just having v-levels and names
68135 to key off of.
69136
70137 - **Structured logs better represent structured data**: sometimes, the
71138 data that you want to log is inherently structured (think tuple-link
72 objects). Structured logs allow you to preserve that structure when
139 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
92159 - They're not easily searchable without resorting to fuzzy searching,
93 regular expressions, etc
160 regular expressions, etc.
94161
95162 - They don't store structured data well, since contents are flattened into
96 a string
97
98 - They're not cross-referencable
99
100 - They don't compress easily, since the message is not constant
101
102 (unless you turn positional parameters into key-value pairs with numerical
163 a string.
164
165 - They're not cross-referenceable.
166
167 - They don't compress easily, since the message is not constant.
168
169 (Unless you turn positional parameters into key-value pairs with numerical
103170 keys, at which point you've gotten key-value logging with meaningless
104 keys)
105
106 ## Practical
107
108 ## Why key-value pairs, and not a map?
171 keys.)
172
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
132199 constant messages":
133200
134 1. figure out what the error actually is, as you'd write in a TL;DR style,
135 and use that as a message
201 1. Figure out what the error actually is, as you'd write in a TL;DR style,
202 and use that as a message.
136203
137204 2. For every place you'd write a format specifier, look to the word before
138 it, and add that as a key value pair
205 it, and add that as a key value pair.
139206
140207 For instance, consider the following examples (all taken from spots in the
141208 Kubernetes codebase):
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.
162229
163230 Otherwise, you can start out with `0` as "you always want to see this",
164231 `1` as "common logging that you might *possibly* want to turn off", and
165 `10` as "I would like to performance-test your log collection stack".
232 `10` as "I would like to performance-test your log collection stack."
166233
167234 Then gradually choose levels in between as you need them, working your way
168235 down from 10 (for debug and trace style logs) and up from 1 (for chattier
169 info-type logs).
170
171 ## How do I choose my keys
172
173 - make your keys human-readable
174 - constant keys are generally a good idea
175 - be consistent across your codebase
176 - keys should naturally match parts of the message string
236 info-type logs.)
237
238 #### How do I choose my keys?
239
240 - Make your keys human-readable.
241 - Constant keys are generally a good idea.
242 - Be consistent across your codebase.
243 - Keys should naturally match parts of the message string.
177244
178245 While key names are mostly unrestricted (and spaces are acceptable),
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 }
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 // Note that Init usually takes a pointer so it can modify the receiver to save
37 // runtime info.
38 func (_ *tabLogSink) Init(info logr.RuntimeInfo) {
39 }
40
41 func (_ tabLogSink) Enabled(level int) bool {
42 return true
43 }
44
45 func (l tabLogSink) Info(level int, msg string, kvs ...interface{}) {
3846 fmt.Fprintf(l.writer, "%s\t%s\t", l.name, msg)
3947 for k, v := range l.keyValues {
4048 fmt.Fprintf(l.writer, "%s: %+v ", k, v)
4654 l.writer.Flush()
4755 }
4856
49 func (_ *TabLogger) Enabled() bool {
50 return true
57 func (l tabLogSink) Error(err error, msg string, kvs ...interface{}) {
58 kvs = append(kvs, "error", err)
59 l.Info(0, msg, kvs...)
5160 }
5261
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{
62 func (l tabLogSink) WithName(name string) logr.LogSink {
63 return &tabLogSink{
6464 name: l.name + "." + name,
6565 keyValues: l.keyValues,
6666 writer: l.writer,
6767 }
6868 }
6969
70 func (l *TabLogger) WithValues(kvs ...interface{}) logr.Logger {
70 func (l tabLogSink) WithValues(kvs ...interface{}) logr.LogSink {
7171 newMap := make(map[string]interface{}, len(l.keyValues)+len(kvs)/2)
7272 for k, v := range l.keyValues {
7373 newMap[k] = v
7575 for i := 0; i < len(kvs); i += 2 {
7676 newMap[kvs[i].(string)] = kvs[i+1]
7777 }
78 return &TabLogger{
78 return &tabLogSink{
7979 name: l.name,
8080 keyValues: newMap,
8181 writer: l.writer,
8282 }
8383 }
8484
85 // NewTabLogger is the main entry-point to this implementation. App developers
86 // call this somewhere near main() and thenceforth only deal with logr.Logger.
8587 func NewTabLogger() logr.Logger {
86 return &TabLogger{
88 sink := &tabLogSink{
8789 writer: tabwriter.NewWriter(os.Stderr, 40, 8, 2, '\t', 0),
8890 }
91 return logr.New(sink)
8992 }
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
29 "github.com/go-logr/logr"
30 )
31
32 // New returns a logr.Logger which is implemented by an arbitrary function.
33 func New(fn func(prefix, args string), opts Options) logr.Logger {
34 return logr.New(newSink(fn, opts))
35 }
36
37 func newSink(fn func(prefix, args string), opts Options) logr.LogSink {
38 return &fnlogger{
39 prefix: "",
40 values: nil,
41 depth: 0,
42 write: fn,
43 logCaller: opts.LogCaller,
44 verbosity: opts.Verbosity,
45 }
46 }
47
48 // Options carries parameters which influence the way logs are generated.
49 type Options struct {
50 // LogCaller tells funcr to add a "caller" key to some or all log lines.
51 // This has some overhead, so some users might not want it.
52 LogCaller MessageClass
53
54 // Verbosity tells funcr which V logs to be write. Higher values enable
55 // more logs.
56 Verbosity int
57 }
58
59 // MessageClass indicates which category or categories of messages to consider.
60 type MessageClass int
61
62 const (
63 None MessageClass = iota
64 All
65 Info
66 Error
67 )
68
69 type fnlogger struct {
70 prefix string
71 values []interface{}
72 depth int
73 write func(prefix, args string)
74 logCaller MessageClass
75 verbosity int
76 }
77
78 // Assert conformance to the interfaces.
79 var _ logr.LogSink = &fnlogger{}
80 var _ logr.CallDepthLogSink = &fnlogger{}
81
82 func flatten(kvList ...interface{}) string {
83 if len(kvList)%2 != 0 {
84 kvList = append(kvList, "<no-value>")
85 }
86 // Empirically bytes.Buffer is faster than strings.Builder for this.
87 buf := bytes.NewBuffer(make([]byte, 0, 1024))
88 for i := 0; i < len(kvList); i += 2 {
89 k, ok := kvList[i].(string)
90 if !ok {
91 k = fmt.Sprintf("<non-string-key-%d>", i/2)
92 }
93 v := kvList[i+1]
94
95 if i > 0 {
96 buf.WriteRune(' ')
97 }
98 buf.WriteRune('"')
99 buf.WriteString(k)
100 buf.WriteRune('"')
101 buf.WriteRune('=')
102 buf.WriteString(pretty(v))
103 }
104 return buf.String()
105 }
106
107 func pretty(value interface{}) string {
108 return prettyWithFlags(value, 0)
109 }
110
111 const (
112 flagRawString = 0x1
113 )
114
115 // TODO: This is not fast. Most of the overhead goes here.
116 func prettyWithFlags(value interface{}, flags uint32) string {
117 // Handling the most common types without reflect is a small perf win.
118 switch v := value.(type) {
119 case bool:
120 return strconv.FormatBool(v)
121 case string:
122 if flags&flagRawString > 0 {
123 return v
124 }
125 // This is empirically faster than strings.Builder.
126 return `"` + v + `"`
127 case int:
128 return strconv.FormatInt(int64(v), 10)
129 case int8:
130 return strconv.FormatInt(int64(v), 10)
131 case int16:
132 return strconv.FormatInt(int64(v), 10)
133 case int32:
134 return strconv.FormatInt(int64(v), 10)
135 case int64:
136 return strconv.FormatInt(int64(v), 10)
137 case uint:
138 return strconv.FormatUint(uint64(v), 10)
139 case uint8:
140 return strconv.FormatUint(uint64(v), 10)
141 case uint16:
142 return strconv.FormatUint(uint64(v), 10)
143 case uint32:
144 return strconv.FormatUint(uint64(v), 10)
145 case uint64:
146 return strconv.FormatUint(v, 10)
147 case uintptr:
148 return strconv.FormatUint(uint64(v), 10)
149 case float32:
150 return strconv.FormatFloat(float64(v), 'f', -1, 32)
151 case float64:
152 return strconv.FormatFloat(v, 'f', -1, 64)
153 }
154
155 buf := bytes.NewBuffer(make([]byte, 0, 256))
156 t := reflect.TypeOf(value)
157 if t == nil {
158 return "null"
159 }
160 v := reflect.ValueOf(value)
161 switch t.Kind() {
162 case reflect.Bool:
163 return strconv.FormatBool(v.Bool())
164 case reflect.String:
165 if flags&flagRawString > 0 {
166 return v.String()
167 }
168 // This is empirically faster than strings.Builder.
169 return `"` + v.String() + `"`
170 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
171 return strconv.FormatInt(int64(v.Int()), 10)
172 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
173 return strconv.FormatUint(uint64(v.Uint()), 10)
174 case reflect.Float32:
175 return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
176 case reflect.Float64:
177 return strconv.FormatFloat(v.Float(), 'f', -1, 64)
178 case reflect.Struct:
179 buf.WriteRune('{')
180 for i := 0; i < t.NumField(); i++ {
181 f := t.Field(i)
182 if f.PkgPath != "" {
183 // reflect says this field is only defined for non-exported fields.
184 continue
185 }
186 if i > 0 {
187 buf.WriteRune(',')
188 }
189 buf.WriteRune('"')
190 name := f.Name
191 if tag, found := f.Tag.Lookup("json"); found {
192 if comma := strings.Index(tag, ","); comma != -1 {
193 name = tag[:comma]
194 } else {
195 name = tag
196 }
197 }
198 buf.WriteString(name)
199 buf.WriteRune('"')
200 buf.WriteRune(':')
201 buf.WriteString(pretty(v.Field(i).Interface()))
202 }
203 buf.WriteRune('}')
204 return buf.String()
205 case reflect.Slice, reflect.Array:
206 buf.WriteRune('[')
207 for i := 0; i < v.Len(); i++ {
208 if i > 0 {
209 buf.WriteRune(',')
210 }
211 e := v.Index(i)
212 buf.WriteString(pretty(e.Interface()))
213 }
214 buf.WriteRune(']')
215 return buf.String()
216 case reflect.Map:
217 buf.WriteRune('{')
218 // This does not sort the map keys, for best perf.
219 it := v.MapRange()
220 i := 0
221 for it.Next() {
222 if i > 0 {
223 buf.WriteRune(',')
224 }
225 // JSON only does string keys.
226 buf.WriteRune('"')
227 buf.WriteString(prettyWithFlags(it.Key().Interface(), flagRawString))
228 buf.WriteRune('"')
229 buf.WriteRune(':')
230 buf.WriteString(pretty(it.Value().Interface()))
231 i++
232 }
233 buf.WriteRune('}')
234 return buf.String()
235 case reflect.Ptr, reflect.Interface:
236 return pretty(v.Elem().Interface())
237 }
238 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
239 }
240
241 type callerID struct {
242 File string `json:"file"`
243 Line int `json:"line"`
244 }
245
246 func (l fnlogger) caller() callerID {
247 // +1 for this frame, +1 for Info/Error.
248 _, file, line, ok := runtime.Caller(l.depth + 2)
249 if !ok {
250 return callerID{"<unknown>", 0}
251 }
252 return callerID{filepath.Base(file), line}
253 }
254
255 // Note that this receiver is a pointer, so depth can be saved.
256 func (l *fnlogger) Init(info logr.RuntimeInfo) {
257 l.depth += info.CallDepth
258 }
259
260 func (l fnlogger) Enabled(level int) bool {
261 return level <= l.verbosity
262 }
263
264 func (l fnlogger) Info(level int, msg string, kvList ...interface{}) {
265 args := make([]interface{}, 0, 64) // using a constant here impacts perf
266 if l.logCaller == All || l.logCaller == Info {
267 args = append(args, "caller", l.caller())
268 }
269 args = append(args, "level", level, "msg", msg)
270 args = append(args, l.values...)
271 args = append(args, kvList...)
272 argsStr := flatten(args...)
273 l.write(l.prefix, argsStr)
274 }
275
276 func (l fnlogger) Error(err error, msg string, kvList ...interface{}) {
277 args := make([]interface{}, 0, 64) // using a constant here impacts perf
278 if l.logCaller == All || l.logCaller == Error {
279 args = append(args, "caller", l.caller())
280 }
281 args = append(args, "msg", msg)
282 var loggableErr interface{}
283 if err != nil {
284 loggableErr = err.Error()
285 }
286 args = append(args, "error", loggableErr)
287 args = append(args, l.values...)
288 args = append(args, kvList...)
289 argsStr := flatten(args...)
290 l.write(l.prefix, argsStr)
291 }
292
293 // WithName returns a new Logger with the specified name appended. funcr
294 // uses '/' characters to separate name elements. Callers should not pass '/'
295 // in the provided name string, but this library does not actually enforce that.
296 func (l fnlogger) WithName(name string) logr.LogSink {
297 if len(l.prefix) > 0 {
298 l.prefix = l.prefix + "/"
299 }
300 l.prefix += name
301 return &l
302 }
303
304 func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink {
305 // Three slice args forces a copy.
306 n := len(l.values)
307 l.values = append(l.values[:n:n], kvList...)
308 return &l
309 }
310
311 func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
312 l.depth += depth
313 return &l
314 }
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 "path/filepath"
22 "runtime"
23 "testing"
24
25 "github.com/go-logr/logr"
26 )
27
28 type substr string
29
30 func ptrint(i int) *int {
31 return &i
32 }
33 func ptrstr(s string) *string {
34 return &s
35 }
36
37 func TestPretty(t *testing.T) {
38 cases := []interface{}{
39 "strval",
40 substr("substrval"),
41 true,
42 false,
43 int(93),
44 int8(93),
45 int16(93),
46 int32(93),
47 int64(93),
48 int(-93),
49 int8(-93),
50 int16(-93),
51 int32(-93),
52 int64(-93),
53 uint(93),
54 uint8(93),
55 uint16(93),
56 uint32(93),
57 uint64(93),
58 uintptr(93),
59 float32(93.76),
60 float64(93.76),
61 ptrint(93),
62 ptrstr("pstrval"),
63 []int{9, 3, 7, 6},
64 [4]int{9, 3, 7, 6},
65 struct {
66 Int int
67 String string
68 }{
69 93, "seventy-six",
70 },
71 map[string]int{
72 "nine": 3,
73 },
74 map[substr]int{
75 "nine": 3,
76 },
77 fmt.Errorf("error"),
78 struct {
79 X int `json:"x"`
80 Y int `json:"y"`
81 }{
82 93, 76,
83 },
84 struct {
85 X []int
86 Y map[int]int
87 Z struct{ P, Q int }
88 }{
89 []int{9, 3, 7, 6},
90 map[int]int{9: 3},
91 struct{ P, Q int }{9, 3},
92 },
93 []struct{ X, Y string }{
94 {"nine", "three"},
95 {"seven", "six"},
96 },
97 }
98
99 for i, tc := range cases {
100 ours := pretty(tc)
101 std, err := json.Marshal(tc)
102 if err != nil {
103 t.Errorf("[%d]: unexpected error: %v", i, err)
104 }
105 if ours != string(std) {
106 t.Errorf("[%d]: expected %q, got %q", i, std, ours)
107 }
108 }
109 }
110
111 func makeKV(args ...interface{}) []interface{} {
112 return args
113 }
114
115 func TestFlatten(t *testing.T) {
116 testCases := []struct {
117 name string
118 kv []interface{}
119 expect string
120 }{{
121 name: "nil",
122 kv: nil,
123 expect: "",
124 }, {
125 name: "empty",
126 kv: []interface{}{},
127 expect: "",
128 }, {
129 name: "primitives",
130 kv: makeKV("int", 1, "str", "ABC", "bool", true),
131 expect: `"int"=1 "str"="ABC" "bool"=true`,
132 }, {
133 name: "missing value",
134 kv: makeKV("key"),
135 expect: `"key"="<no-value>"`,
136 }, {
137 name: "non-string key",
138 kv: makeKV(123, "val"),
139 expect: `"<non-string-key-0>"="val"`,
140 }}
141
142 for _, tc := range testCases {
143 t.Run(tc.name, func(t *testing.T) {
144 r := flatten(tc.kv...)
145 if r != tc.expect {
146 t.Errorf("expected %q, got %q", tc.expect, r)
147 }
148 })
149 }
150 }
151
152 func TestEnabled(t *testing.T) {
153 t.Run("default V", func(t *testing.T) {
154 log := newSink(func(prefix, args string) {}, Options{})
155 if !log.Enabled(0) {
156 t.Errorf("expected true")
157 }
158 if log.Enabled(1) {
159 t.Errorf("expected false")
160 }
161 })
162 t.Run("V=9", func(t *testing.T) {
163 log := newSink(func(prefix, args string) {}, Options{Verbosity: 9})
164 if !log.Enabled(8) {
165 t.Errorf("expected true")
166 }
167 if !log.Enabled(9) {
168 t.Errorf("expected true")
169 }
170 if log.Enabled(10) {
171 t.Errorf("expected false")
172 }
173 })
174 }
175
176 type capture struct {
177 log string
178 }
179
180 func (c *capture) Func(prefix, args string) {
181 c.log = prefix + " " + args
182 }
183
184 func TestInfo(t *testing.T) {
185 testCases := []struct {
186 name string
187 args []interface{}
188 expect string
189 }{{
190 name: "just msg",
191 args: makeKV(),
192 expect: ` "level"=0 "msg"="msg"`,
193 }, {
194 name: "primitives",
195 args: makeKV("int", 1, "str", "ABC", "bool", true),
196 expect: ` "level"=0 "msg"="msg" "int"=1 "str"="ABC" "bool"=true`,
197 }}
198
199 for _, tc := range testCases {
200 t.Run(tc.name, func(t *testing.T) {
201 cap := &capture{}
202 sink := newSink(cap.Func, Options{})
203 sink.Info(0, "msg", tc.args...)
204 if cap.log != tc.expect {
205 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
206 }
207 })
208 }
209 }
210
211 func TestInfoWithCaller(t *testing.T) {
212 t.Run("LogCaller=All", func(t *testing.T) {
213 cap := &capture{}
214 sink := newSink(cap.Func, Options{LogCaller: All})
215 sink.Info(0, "msg")
216 _, file, line, _ := runtime.Caller(0)
217 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
218 if cap.log != expect {
219 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
220 }
221 })
222 t.Run("LogCaller=Info", func(t *testing.T) {
223 cap := &capture{}
224 sink := newSink(cap.Func, Options{LogCaller: Info})
225 sink.Info(0, "msg")
226 _, file, line, _ := runtime.Caller(0)
227 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
228 if cap.log != expect {
229 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
230 }
231 })
232 t.Run("LogCaller=Error", func(t *testing.T) {
233 cap := &capture{}
234 sink := newSink(cap.Func, Options{LogCaller: Error})
235 sink.Info(0, "msg")
236 expect := ` "level"=0 "msg"="msg"`
237 if cap.log != expect {
238 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
239 }
240 })
241 t.Run("LogCaller=None", func(t *testing.T) {
242 cap := &capture{}
243 sink := newSink(cap.Func, Options{LogCaller: None})
244 sink.Info(0, "msg")
245 expect := ` "level"=0 "msg"="msg"`
246 if cap.log != expect {
247 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
248 }
249 })
250 }
251
252 func TestError(t *testing.T) {
253 testCases := []struct {
254 name string
255 args []interface{}
256 expect string
257 }{{
258 name: "just msg",
259 args: makeKV(),
260 expect: ` "msg"="msg" "error"="err"`,
261 }, {
262 name: "primitives",
263 args: makeKV("int", 1, "str", "ABC", "bool", true),
264 expect: ` "msg"="msg" "error"="err" "int"=1 "str"="ABC" "bool"=true`,
265 }}
266
267 for _, tc := range testCases {
268 t.Run(tc.name, func(t *testing.T) {
269 cap := &capture{}
270 sink := newSink(cap.Func, Options{})
271 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
272 if cap.log != tc.expect {
273 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
274 }
275 })
276 }
277 }
278
279 func TestErrorWithCaller(t *testing.T) {
280 t.Run("LogCaller=All", func(t *testing.T) {
281 cap := &capture{}
282 sink := newSink(cap.Func, Options{LogCaller: All})
283 sink.Error(fmt.Errorf("err"), "msg")
284 _, file, line, _ := runtime.Caller(0)
285 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
286 if cap.log != expect {
287 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
288 }
289 })
290 t.Run("LogCaller=Error", func(t *testing.T) {
291 cap := &capture{}
292 sink := newSink(cap.Func, Options{LogCaller: Error})
293 sink.Error(fmt.Errorf("err"), "msg")
294 _, file, line, _ := runtime.Caller(0)
295 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
296 if cap.log != expect {
297 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
298 }
299 })
300 t.Run("LogCaller=Info", func(t *testing.T) {
301 cap := &capture{}
302 sink := newSink(cap.Func, Options{LogCaller: Info})
303 sink.Error(fmt.Errorf("err"), "msg")
304 expect := ` "msg"="msg" "error"="err"`
305 if cap.log != expect {
306 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
307 }
308 })
309 t.Run("LogCaller=None", func(t *testing.T) {
310 cap := &capture{}
311 sink := newSink(cap.Func, Options{LogCaller: None})
312 sink.Error(fmt.Errorf("err"), "msg")
313 expect := ` "msg"="msg" "error"="err"`
314 if cap.log != expect {
315 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
316 }
317 })
318 }
319
320 func TestInfoWithName(t *testing.T) {
321 testCases := []struct {
322 name string
323 names []string
324 args []interface{}
325 expect string
326 }{{
327 name: "one",
328 names: []string{"pfx1"},
329 args: makeKV("k", "v"),
330 expect: `pfx1 "level"=0 "msg"="msg" "k"="v"`,
331 }, {
332 name: "two",
333 names: []string{"pfx1", "pfx2"},
334 args: makeKV("k", "v"),
335 expect: `pfx1/pfx2 "level"=0 "msg"="msg" "k"="v"`,
336 }}
337
338 for _, tc := range testCases {
339 t.Run(tc.name, func(t *testing.T) {
340 cap := &capture{}
341 sink := newSink(cap.Func, Options{})
342 for _, n := range tc.names {
343 sink = sink.WithName(n)
344 }
345 sink.Info(0, "msg", tc.args...)
346 if cap.log != tc.expect {
347 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
348 }
349 })
350 }
351 }
352
353 func TestErrorWithName(t *testing.T) {
354 testCases := []struct {
355 name string
356 names []string
357 args []interface{}
358 expect string
359 }{{
360 name: "one",
361 names: []string{"pfx1"},
362 args: makeKV("k", "v"),
363 expect: `pfx1 "msg"="msg" "error"="err" "k"="v"`,
364 }, {
365 name: "two",
366 names: []string{"pfx1", "pfx2"},
367 args: makeKV("k", "v"),
368 expect: `pfx1/pfx2 "msg"="msg" "error"="err" "k"="v"`,
369 }}
370
371 for _, tc := range testCases {
372 t.Run(tc.name, func(t *testing.T) {
373 cap := &capture{}
374 sink := newSink(cap.Func, Options{})
375 for _, n := range tc.names {
376 sink = sink.WithName(n)
377 }
378 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
379 if cap.log != tc.expect {
380 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
381 }
382 })
383 }
384 }
385
386 func TestInfoWithValues(t *testing.T) {
387 testCases := []struct {
388 name string
389 values []interface{}
390 args []interface{}
391 expect string
392 }{{
393 name: "zero",
394 values: makeKV(),
395 args: makeKV("k", "v"),
396 expect: ` "level"=0 "msg"="msg" "k"="v"`,
397 }, {
398 name: "one",
399 values: makeKV("one", 1),
400 args: makeKV("k", "v"),
401 expect: ` "level"=0 "msg"="msg" "one"=1 "k"="v"`,
402 }, {
403 name: "two",
404 values: makeKV("one", 1, "two", 2),
405 args: makeKV("k", "v"),
406 expect: ` "level"=0 "msg"="msg" "one"=1 "two"=2 "k"="v"`,
407 }}
408
409 for _, tc := range testCases {
410 t.Run(tc.name, func(t *testing.T) {
411 cap := &capture{}
412 sink := newSink(cap.Func, Options{})
413 sink = sink.WithValues(tc.values...)
414 sink.Info(0, "msg", tc.args...)
415 if cap.log != tc.expect {
416 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
417 }
418 })
419 }
420 }
421
422 func TestErrorWithValues(t *testing.T) {
423 testCases := []struct {
424 name string
425 values []interface{}
426 args []interface{}
427 expect string
428 }{{
429 name: "zero",
430 values: makeKV(),
431 args: makeKV("k", "v"),
432 expect: ` "msg"="msg" "error"="err" "k"="v"`,
433 }, {
434 name: "one",
435 values: makeKV("one", 1),
436 args: makeKV("k", "v"),
437 expect: ` "msg"="msg" "error"="err" "one"=1 "k"="v"`,
438 }, {
439 name: "two",
440 values: makeKV("one", 1, "two", 2),
441 args: makeKV("k", "v"),
442 expect: ` "msg"="msg" "error"="err" "one"=1 "two"=2 "k"="v"`,
443 }}
444
445 for _, tc := range testCases {
446 t.Run(tc.name, func(t *testing.T) {
447 cap := &capture{}
448 sink := newSink(cap.Func, Options{})
449 sink = sink.WithValues(tc.values...)
450 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
451 if cap.log != tc.expect {
452 t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log)
453 }
454 })
455 }
456 }
457
458 func TestInfoWithCallDepth(t *testing.T) {
459 t.Run("one", func(t *testing.T) {
460 cap := &capture{}
461 sink := newSink(cap.Func, Options{LogCaller: All})
462 dSink := sink.(logr.CallDepthLogSink)
463 sink = dSink.WithCallDepth(1)
464 sink.Info(0, "msg")
465 _, file, line, _ := runtime.Caller(1)
466 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line)
467 if cap.log != expect {
468 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
469 }
470 })
471 }
472
473 func TestErrorWithCallDepth(t *testing.T) {
474 t.Run("one", func(t *testing.T) {
475 cap := &capture{}
476 sink := newSink(cap.Func, Options{LogCaller: All})
477 dSink := sink.(logr.CallDepthLogSink)
478 sink = dSink.WithCallDepth(1)
479 sink.Error(fmt.Errorf("err"), "msg")
480 _, file, line, _ := runtime.Caller(1)
481 expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line)
482 if cap.log != expect {
483 t.Errorf("\nexpected %q\n got %q", expect, cap.log)
484 }
485 })
486 }
00 module github.com/go-logr/logr
11
2 go 1.14
2 go 1.16
+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 }