Proposal Details
This proposal is for a function with a signature like func CallerString(skip int) string
.
It returns basic info about the caller in some fixed format.
Since it only returns info about a single frame and only reports it as simple data, when skip
is a reasonable constant, it could be replaced at compile time as an optimization. Otherwise, it can be implemented in terms of Callers
.
Either way, it would simplify common logging/monitoring/error annotating uses that just need a tiny bit of context about a single caller for reporting to a developer.
Comment From: gabyhelp
Related Documentation
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: ianlancetaylor
What would the string be?
I'm not sure what you mean when you suggest replacing it at compile time. It seems to me that is only possible when the caller is known. And that can only happen if the function that calls CallerString
is only called from one other location in the program. If it's called from more than one location, how can the compiler know what the value of CallerString
should be?
Comment From: jimmyfrasche
I doesn't matter precisely what the string is as long as it 1. contains enough information to identify the caller 2. is in a specified format that is human and machine readable
It could just be "path/file:lineNumber". I'm not concerned about the particulars as long as it is meets the listed criteria.
Say you had a function using it like
func F() T {
return G(runtime.CallerString(1))
}
I'm imagining some hypothetical set of optimizations where F
is inlined and the constant is decremented in kind and thus can be replaced with the information from the original call site for F
so when you call F()
you end up with a sequence of transformations: F()
to G(runtime.CallerString(0))
to G("my/file.go:10")
(or whatever format).
Comment From: seankhliao
Without skip it'd be #12876 #27329 #37620
Comment From: jimmyfrasche
It would serve a similar purpose. This is about providing a nicer interface.
The last place I used something like this was a parser for strings that would be provided in source (like how regexp.Compile
often takes a constant). Since there could be many of these in the same project, it took used runtime to grab the file/line info so it could report errors not just as an offset into a string but also the source location of that string. I just wanted a string but ended up having to read and reread a bunch of docs. I couldn't tell if it was sufficient to use runtime.Caller(1)
or if I needed to do like the example for Frames so that it worked even if things got inlined. I ended up using a modified version of the Frames example just in case but maybe that's overkill. I just wanted to make sure I got some debugging info that seemed like it should be easier to get.
Comment From: seankhliao
52751 sounds related, though that's more specialized to tests
Comment From: adonovan
The runtime package provides all the necessary primitives, and it's not obvious to me from its name what kind of string CallerString would actually return; and I can easily imagine wanting to tweak it a little. This merely convenient feature might be better provided by the internal library within your project, tailored to your specific needs.
Comment From: jimmyfrasche
The necessary primitives are not especially friendly and their generality makes the simple case overly complicated.
Getting basic info about a single frame is the common case by volume and it would be nice to have a clear, simple, robust way to get that.
Comment From: ianlancetaylor
@jimmyfrasche You say above that the string has to be machine readable, which means that it must be precisely defined. This proposal isn't going to go anywhere unless you provide a precise definition. Thanks.
Comment From: apparentlymart
From the discussion so far, it seems like the following function would meet the behavioral requirements:
func CallerString(skip int) string {
_, filename, line, ok := runtime.Caller(skip + 1)
if !ok {
return "" // is this an acceptable way to handle failure?
}
return fmt.Sprintf("%s:%d", filename, line)
}
If this were actually in package runtime
it would presumably not be able to use package fmt
as I did here. but my purpose in sharing this is to try to confirm if I understood the requirements correctly rather than to actually propose this implementation.
(I also understand that you hope that the compiler would be able to optimize this in a special way to make it cheaper to use when the argument is a constant, but for the moment I'm trying to focus only on the desired functionality and not on how the compiler optimizes it.)
Comment From: adonovan
Why does this need to go in the runtime package? Your CallerString function looks like a totally reasonable thing to add to your test or generated code or wherever you plan to use it. Presumably this function is only needed in the error case, so the cost of allocation is unlikely to matter. (The discussion about compiler optimization for constant values of skip
seems premature, especially since, as Ian points out, the optimization is only possible when the inlining depth is >= skip
.)
Comment From: apparentlymart
I think the potentially-performance-sensitive situation for this would be in logging/tracing libraries that want to summarize where a log line or trace span was created without significantly affecting the performance of the code that has been instrumented, since logging/tracing is typically used at normal runtime rather than only in tests, and is often on the happy path rather than on the error path.
With that said, I have not actually experienced source line collection as being a bottleneck, so I'm sharing this only to note that this has uses beyond test-only code or error-handling code, and not to claim that the optimization in question would necessarily be important for those situations. Others may disagree, of course!