Go Runtime Frames
Both the go-kit/log
and rs/zerolog
loggers provide a Caller
method that returns the caller of the function that called it. This is useful for logging the function name in the log output. This functionality is immensly useful and roused my curiosity as to how it is implemented.
zerolog logger caller example
import "github.com/rs/zerolog"
import "github.com/rs/zerolog/log"
func main() {
log.Logger = log.With().Caller().Logger() // <--
log.Debug().Str("foo", "bar").Msg("This will be logged with a caller")
}
go-kit logger caller example
import "github.com/go-kit/log"
import "github.com/go-kit/log/level"
func main() {
var logger log.Logger
logger = log.NewLogfmtLogger(os.Stdout)
logger = log.With(logger, "caller", log.DefaultCaller) // <--
level.Debug(logger).Log("msg", "This will be logged with a caller", "foo", "bar")
}
These are code snippets showing how this can be achieved using the runtime
package.
import "runtime"
// Caller returns the caller of the function that called it.
func Caller() string {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return frame.Function
}
// Trace returns the file, line and function name of the caller
func Trace() (string, int, string) {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return frame.File, frame.Line, frame.Function
}
This is a more complete example that returns the frame at a specified index. This is useful when you want to log the caller of the function that called the function that called it. See this go playground example for a demonstration.
func getFrame(skipFrames int) runtime.Frame {
// We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame
targetFrameIndex := skipFrames + 2
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need
programCounters := make([]uintptr, targetFrameIndex+2)
n := runtime.Callers(0, programCounters)
frame := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(programCounters[:n])
for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ {
var frameCandidate runtime.Frame
frameCandidate, more = frames.Next()
if frameIndex == targetFrameIndex {
frame = frameCandidate
}
}
}
return frame
}
caller := getFrame(1).Function