When we add business logs to our application code, regardless of the level of logging, in addition to the information that we actively pass to Logger for it to log, it is also very important to know which function printed the line and where it is located, otherwise it is likely to be like looking for a needle in a haystack when troubleshooting.
For logging, it is important to record the function name and line number of the caller of the Logger method. Some logging libraries support this, such as Zap.
But if you want a robust development framework, you should not let yourself be strongly bound to a logging library, a better approach is to develop a logging facade, the program directly use the logging facade, and then the facade calls the logging library to complete the logging. A typical Java slf4j is this idea, where the program uses the slf4j directly, followed by a Logger that can be either logback or log4j or even any logging library implementation that satisfies the slf4j convention.
If we were to design a Log Facade with Go, we would need to get the caller’s function name and file location in the facade itself, so how do we achieve this inside Go? This requires the Caller function provided by the runtime standard library.
The function signature of runtime.Caller is as follows.
Caller function reports information about the file and line number of the function executed by the current Go program call stack. The parameter
skip is the number of stack frames to go up, 0 is the caller of
Caller (the call stack where
Caller is located), 1 is the caller of the caller who called Caller, and so on. Isn’t it a bit dizzying, here’s an example.
The return value of the function is the call stack identifier, the full filename with path, and the line number of the call in the file. If the information is not available, the return value ok will be set to false.
Get the function name of the caller
The first return value of runtime.Caller is a call stack identifier, through which we can get the function information of the call stack
*runtime.Func, and then further get the function name of the caller, which will be used in the following functions and methods.
runtime.FuncForPC function returns a
*Func indicating the call stack corresponding to the call stack identifier
pc; if the call stack identifier does not have a corresponding call stack, the function returns
Name method returns the name of the function called by the call stack. As mentioned above,
runtime.FuncForPC may return
nil, but the Name method is implemented in such a way to avoid the possibility of panic, so we can use it with confidence.
Here’s a simple example of using
runtime.FuncForPC together to get caller information.
Note: Here we demonstrate that it is relatively simple to trace up a call stack to get the caller’s information. When you really want to implement a class library like log facade, there may be several layers of encapsulation, and the caller information you want to record in the log should be the location where the business code hits the log, then the number of layers to go back up is definitely not as simple as 1. The specific number of layers to skip depends on the specific encapsulation of the implemented log facade.
Caller` back through the call stack to get information about the caller, although powerful, but frequent access to this information can also have an impact on program performance. Our business code shouldn’t depend on it for implementation, it’s more useful for some libraries that are transparent to the business and only used when logging information.