-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support alternative way of skipping frames #60
Conversation
I'm just posting this for discussion. I still need to check whether this works as intended... |
No, it doesn't 😢 Implementing the proposed
It marks itself as helper, not its caller. Looks like enhancements in the standard library will be necessary to address this. Scary. |
This approach can be made to work by returning the underlying function that then needs to be called by the helper functions. I've pushed another commit for that and tested that it works for the testing logger. However, the resulting API is definitely not nice and naming probably should be improved (too many "helpers"...). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I patched this in and hacked on logr/testing and funcr and I can't make it work. The call to Helper() being in a func() means we're still off by one. Will look more later perhaps.
I've pushed further commits to make this work in the testing logger. With those changes I now get the expected result:
|
funcr/funcr.go
Outdated
prefix: "", | ||
values: nil, | ||
depth: 0, | ||
write: fn, | ||
logCaller: opts.LogCaller, | ||
verbosity: opts.Verbosity, | ||
helper: opts.Helper, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gofmt should have lined these up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for that. My gofmt integration in emacs sometimes breaks down without me noticing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks OK overall.
-
Please squash and make sure the commit and PR messages are clean and helpful.
-
Can you please add the following file as funcr/example/main.go:
/*
Copyright 2021 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
type E struct {
str string
}
func (e E) Error() string {
return e.str
}
func Helper(log logr.Logger, msg string) {
helper2(log, msg)
}
func helper2(log logr.Logger, msg string) {
log.WithCallDepth(2).Info(msg)
}
func main() {
log := funcr.New(
func(pfx, args string) { fmt.Println(pfx, args) },
funcr.Options{LogCaller: funcr.All})
example(log.WithValues("module", "example"))
}
// If this were in another package, all it would depend on in logr, not glogr.
func example(log logr.Logger) {
log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1})
log.V(1).Info("you should see this")
log.V(1).V(1).Info("you should NOT see this")
log.Error(nil, "uh oh", "trouble", true, "reasons", []float64{0.1, 0.11, 3.14})
log.Error(E{"an error occurred"}, "goodbye", "code", -1)
Helper(log, "thru a helper")
}
logr.go
Outdated
@@ -188,6 +191,13 @@ type Logger struct { | |||
level int | |||
sink LogSink | |||
withCallDepth CallDepthLogSink | |||
|
|||
// withHelper is set in Init if the log sink supports the interface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to be super nit-picky to avoid followups :)
// withHelper is set during init if the log sink supports the HelperLogSink
// interface.
//
// We only need the function returned by withHelper.GetHelper(), but
// cannot store it here because that makes Logger non-comparable,
// which is something we want to guarantee.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
logr.go
Outdated
@@ -261,6 +277,10 @@ func (l Logger) WithName(name string) Logger { | |||
// If the underlying log implementation supports a WithCallDepth(int) method, | |||
// it will be called and the result returned. If the implementation does not | |||
// support CallDepthLogSink, the original Logger will be returned. | |||
// | |||
// Helper() should be used instead of WithCallDepth(1) because Helper() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super nit - the ()
should be removed unless you are describing an actual call (e.g. WithCallDepth(1)
is ok)
logr.go
Outdated
@@ -269,6 +289,32 @@ func (l Logger) WithCallDepth(depth int) Logger { | |||
return l | |||
} | |||
|
|||
// Helper returns a Logger instance that skips the direct caller when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: returns a new Logger instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
logr.go
Outdated
// specified number of frames when logging call site information. If depth | ||
// is 0 the attribution should be to the direct caller of this method. If | ||
// depth is 1 the attribution should skip 1 call frame, and so on. | ||
// WithCallDepth returns a log sink that will offset the call stack by the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/log sink/LogSink/
logr.go
Outdated
// Successive calls to this are additive. | ||
WithCallDepth(depth int) LogSink | ||
} | ||
|
||
// HelperLogSink represents a Logger that knows how to climb the call stack | ||
// to identify the original call site and can skip intermediate helper functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard to tell but these lines look like thy might be > 80 columns? I have tried to keep comments wrapped cleanly
logr.go
Outdated
// intermediate helper functions. | ||
// | ||
// This is an optional interface and implementations are not required to | ||
// support it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add "Implementations that choose to support this must not simply implement it as WithCallDepth(1), because Logger.Helper() will call both methods if they are present. This should only be implemented for LogSinks that actually need it, as with testing.T." or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger.Helper()
or Logger.Helper
? My chance for nit picking... 😀
I've added it with Logger.Helper
.
funcr/funcr.go
Outdated
@@ -55,6 +62,10 @@ type Options struct { | |||
// Verbosity tells funcr which V logs to be write. Higher values enable | |||
// more logs. | |||
Verbosity int | |||
|
|||
// Helper is an optional function that funcr must call to mark its own |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/must call/will call/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
So we are really doing this, despite the awkward API? Okay.
Will do.
Okay, just one question about this:
|
The comment is a cut-paste error and can be removed, thanks
…On Thu, Aug 5, 2021, 12:27 AM Patrick Ohly ***@***.***> wrote:
Looks OK overall.
So we are really doing this, despite the awkward API? Okay.
Please squash and make sure the commit and PR messages are clean and
helpful.
Will do.
Can you please add the following file as funcr/example/main.go:
Okay, just one question about this:
// If this were in another package, all it would depend on in logr, not glogr.
func example(log logr.Logger) {
log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1})
log.V(1).Info("you should see this")
...
I don't understand the comment. There's no reference to glogr in the file. Looks like a cut-and-paste problem, I'll simply drop the comment unless I hear otherwise.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#60 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABKWAVE6MBKQXS4KWEFODNLT3I4PHANCNFSM5BQPPGHA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email>
.
|
I don't love it, but given how Before we merge this, I'll let it sit for a bit and see if anyone else has creative alternatives. I am doubtful, but maybe... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force-pushed.
funcr/funcr.go
Outdated
prefix: "", | ||
values: nil, | ||
depth: 0, | ||
write: fn, | ||
logCaller: opts.LogCaller, | ||
verbosity: opts.Verbosity, | ||
helper: opts.Helper, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
funcr/funcr.go
Outdated
@@ -55,6 +62,10 @@ type Options struct { | |||
// Verbosity tells funcr which V logs to be write. Higher values enable | |||
// more logs. | |||
Verbosity int | |||
|
|||
// Helper is an optional function that funcr must call to mark its own |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
logr.go
Outdated
@@ -188,6 +191,13 @@ type Logger struct { | |||
level int | |||
sink LogSink | |||
withCallDepth CallDepthLogSink | |||
|
|||
// withHelper is set in Init if the log sink supports the interface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
logr.go
Outdated
@@ -269,6 +289,32 @@ func (l Logger) WithCallDepth(depth int) Logger { | |||
return l | |||
} | |||
|
|||
// Helper returns a Logger instance that skips the direct caller when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
logr.go
Outdated
// intermediate helper functions. | ||
// | ||
// This is an optional interface and implementations are not required to | ||
// support it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger.Helper()
or Logger.Helper
? My chance for nit picking... 😀
I've added it with Logger.Helper
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Great comments. Please squash to 1 commit and make sure commit and PR messages are complete.
We'll let this sit for a bit to see if anyone comes up with an obviously better answer.
@DirectXMan12 @wojas FYI
and if we merge this, I guess we'll want a 1.1.0 tag. |
I think that's too aggressive. 40bd6cd and 5bd8e58 are both stand-alone changes. We might even merge them already now, independently of the Helper API change. I'll put them into separate PRs so that we don't forget about them if we close this one here without merging. Implementing the API and using it in testing can be squashed. I tend to prefer to keep that separate because it sometimes makes cherry-picking simpler, but that's subjective. Let's wait for more review feedback before I touch this PR again. |
Fine, then :) Squash to the smallest set of whole commits that makes sense and make sure the comments are good ;) |
func myWrapper(logger logr.Logger, msg string) {
helper, logger := logger.Helper()
helper()
logger.Info(msg)
} As an API this is horrible, but it does not look like we have much choice given how the Go testing package works. Perhaps at least make the name on the logr side clearer. Given that this is a hack specifically added for the testing package, perhaps use something like This would change the example as follows: func myWrapper(logger logr.Logger, msg string) {
helper, logger := logger.WithTestingHelper()
helper()
logger.Info(msg)
} Would this be used in any other context? Do I understand correctly that when not using the testing log sink, would you still need to use If this can be make more generic and completely replace the |
I agree that naming in the new API is sub-optimal (too much "helpers"). I just tried to get something working to get a feeling for how horrible it would be. I like One concern I have is that users might get the impression that they should only use this when using
Okay.
I don't know of one. OTOH, that doesn't mean that there is no other logging backend that uses the same approach.
No. A wrapper function must call either
There's still a need for |
OK, given that it calls I would then argue that calling something We should then update the Given the fragility of the API, perhaps add some extra checks there. It is too easy to forget to call the helper function and someone may find out that it 'still works' if you just ignore that return value with |
Correct. So replace
That's already in the current PR 😄 ("Helper should be used instead of WithCallDepth(1) because ...")
That would be nice, but cannot be implemented because the returned function intentionally doesn't go through the logger because |
That would be my preferred name choice. @thockin?
Ah, perfect, missed that. 😄
Hmm, that's annoying. We could check it for situations where no helper was defined by the log sink and we return our own function, which could set some internal variable to false. But I don't like this inconsistency, because then you will get situations where code panics in production, even though it 'worked fine' in tests. Perhaps just depend on Go's unused variable check then. |
Yes. Hopefully programmers will think before discarding the helper function return value with |
I'm not 100% sure on names:
We're proposing that "best practice" be to call
vs
Which is more likely to be correct more often? The Thoughts? |
You mean I agree that |
The main issue with Compare func myWrapper(logger logr.Logger, msg string) {
helper, logger := logger.WithCallDepthHelper()
helper()
logger.Info(msg)
} with func myWrapper(logger logr.Logger, msg string) {
logger.Helper()()
logger.WithCallDepth(1).Info(msg)
} And as @pohly mentioned, the Go compiler will also not help you if you just call |
OK. I had missed that, of course, we can't just call So let's talk about 2 things:
x, y := log.AsHelper() x, y := log.AsHelperFunc() x, y :=log.IsHelper() x, y := log.ThisIsAHelperFunc() x, y := log.GetHelperFunc() more welcome... |
I don't have a strong opinion either way. At the end, both is arbitrary. Regarding names: My understand is that in All of other examples might look okay at first glance when called without using the return values although such usage is incorrect. Therefore I still prefer |
WithOneMoreCallDepth really doesn't "feel good" to me. I'm not really looking to rush this, so I'll let it percolate for a bit. |
Perhaps
To me
Feels like a check if this is a helper. I would expect a
Suggests that this actual call marks it as a helper func. I would also not expect any return value here.
This would be fine is it would just return a helper func. I think something starting with @pohly is right that the calling function is the helper in the testing naming, but I think the function can still be seen as a helper to determine that call stack depth to subtract. I still have a preference for Come to think of it, this is a pretty common pattern: ctx, cancel := context.WithCancel(ctx)
defer cancel() This does not look too weird next to that: logger, helper := logger.WithCallDepthHelper()
helper() |
I guess I buy that reasoning for WithCallDepthHelper() WithCallStackHelper() WithCallerHelper() WithHelper() |
One more: WithCallDepthMarkerFunc() |
rebase needed :) |
Done |
5bd8e58
to
0ebd759
Compare
0ebd759
to
742e83f
Compare
logr.go
Outdated
type CallStackHelperLogSink interface { | ||
// GetHelper returns a function that must be called to mark the direct caller | ||
// as helper function when logging call site information. | ||
GetHelper() func() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be GetHelperFunc()
or GetCallStackHelper()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure. If we want a longer name, then I prefer GetCallStackHelper
because it is more consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed a version with GetCallStackHelper
.
This is needed for log sinks where the underlying stack unwinding doesn't support an explicit call depth, for example testing.T.Log. Users should use the new logger.Helper method whenever possible by calling it in each log helper function because it also works with LogSinks that support either the CallDepthLogSink interface or the new HelperLogSink interface, whereas Logger.WithCallDepth only works with CallDepthLogSink. A logger might implement both if it supports two different ways of finding source code. For example, testing/test.go optionally unwinds itself and then also testing.T adds source code information.
The funcr log sink must implement the new CallStackHelperLogSink interface (otherwise the wrapper function wouldn't be handled correctly) and also mark its own wrapper as a helper.
742e83f
to
e16cd87
Compare
LGTM |
This is needed for log sinks where the underlying stack unwinding
doesn't support an explicit call depth, for example testing.T.Log.
One such log sink is currently pending in kubernetes/klog#240.
It currently incorrectly logs logr.go as call site.