log icon indicating copy to clipboard operation
log copied to clipboard

Idea: 'Helper' method for Caller

Open bboreham opened this issue 4 years ago • 13 comments

Currently, it is necessary to figure out how each log method will be called in order to specify a count of stack frames to Caller. https://github.com/go-kit/log/blob/5739c2646c7293c773ed50d2086e843e3330781c/value.go#L82-L84

This makes functions that wrap or forward to go-kit/log hard to use.

Suggestion: have a Helper method like in testing, which tells Caller to skip that function from the count.

This would be relatively expensive – a stack walk, lock and map lookup each time the helper is entered, and more map lookups when actually logging – but much more pleasant to use.

bboreham avatar Nov 02 '21 17:11 bboreham

Do you think it would be possible to implement this idea without changing the implementations of log.With and friends?

ChrisHines avatar Nov 03 '21 02:11 ChrisHines

What I had in mind would change Caller and add Helper, plus there needs to be somewhere on the logger data structure to hold the set of helpers. I can’t immediately see that this implies a change to With.

bboreham avatar Nov 03 '21 08:11 bboreham

OK. What if instead of changing Caller there was a new SlowButConvenientCaller (not the real name) and a new implementation of Logger that worked together to do what you describe? That would then be a new feature instead of changing any existing features. Maybe it could also be implemented in a separate package initially to see how it works. Is something like that possible?

ChrisHines avatar Nov 03 '21 14:11 ChrisHines

I have an idea, but I'm not yet sure it's feasible. @bboreham @ChrisHines — could you provide a small program that demonstrates the problem cases you've got in mind?

peterbourgon avatar Aug 23 '22 18:08 peterbourgon

One real case is https://github.com/grafana/dskit/blob/9956e2453a9fc2e88246578adafee7c6d7de176a/spanlogger/spanlogger.go#L80

where we want to send the same log message to tracing and to a go-kit logger; we have a lot of caller=spanlogger.go:80 in our logs.

Similar, but where we do manage to adjust Caller to avoid the problem: https://github.com/weaveworks/common/blob/c65105d60b185cfaa7ef23269a257db392da0b37/logging/gokit.go#L79

Trivial self-contained program:

package main

import (
	"os"

	"github.com/go-kit/log"
)

// Some function that wraps a logging operation, e.g. to send a trace or bump a metric.
func utility(logger log.Logger, reason string) {
	logger.Log("msg", "important "+reason)
}

func main() {
	var logger log.Logger
	logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)

	logger.Log("msg", "hello")
	utility(logger, "hello")
}

bboreham avatar Aug 24 '22 10:08 bboreham

@bboreham Thank you!

peterbourgon avatar Aug 24 '22 13:08 peterbourgon

@bboreham Do I understand correctly that you don't want the current output of

ts=2009-11-10T23:00:00Z caller=prog.go:19 msg=hello
ts=2009-11-10T23:00:00Z caller=prog.go:11 msg="important hello"

and instead want

ts=2009-11-10T23:00:00Z caller=prog.go:19 msg=hello
ts=2009-11-10T23:00:00Z caller=prog.go:20 msg="important hello"

?

peterbourgon avatar Aug 24 '22 16:08 peterbourgon

Exactly.

bboreham avatar Aug 24 '22 16:08 bboreham

@bboreham since utility is a helper, could you also do:

func utility(logger log.Logger, reason string) {
	logger.Log("msg", "important "+reason, "caller", log.Caller(4))
}

oliverpool avatar Aug 31 '22 08:08 oliverpool

How would I know 4 is the correct number, in general?

bboreham avatar Aug 31 '22 08:08 bboreham

You would need to somehow pass this around you utilities, I guess.

oliverpool avatar Aug 31 '22 09:08 oliverpool

I don't think that is realistic in a large program made up of many packages.

bboreham avatar Aug 31 '22 10:08 bboreham

I'm sharing this experiment rather prematurely in order to solicit feedback. Here's the gist. And here's the gist:

The basic idea is to embed more "smarts" into the logged value. The log.Valuer is defined as a method on a Context struct, which holds an optional call frame representing the point in the stack where SetHelper was called. The value, which is lazy-evaluated as in @bboreham's example, consults that state in order to determine which frame to return as the caller.

There are a couple of outstanding issues. At the moment, you have to call SetHelper on the Context struct, which basically doesn't solve the problem: you should be able to flag Helper on a log.Logger directly. I think this is solvable, by making the Context a log.Logger itself, and providing a func Helper(log.Logger) function in package caller, which would be able to decorate/wrap the logger with a Context that set the helper frame. There are also some issues, noted in the test, related to decorators, which I think are also solvable by stack introspection.

I'll keep poking at this, but if anyone is inspired to take the ball and run with it, please do. Or, if anyone sees a problem I haven't noticed, feedback appreciated.

peterbourgon avatar Sep 04 '22 20:09 peterbourgon