go捕获代码调用栈帧-runtime.Caller()

runtime.Caller()

需求:

想知道某个rpc方法在哪里被调用(获取代码调用栈的位置信息)

思路:

想起平常使用 zap 包的 Error 方法时,会打印堆栈的信息,说明 Error 方法源码里面肯定有获取代码调用栈信息的方式。

func (log *Logger) Error(msg string, fields ...Field) {
	if ce := log.check(ErrorLevel, msg); ce != nil {
		ce.Write(fields...)
	}
}
// 看 check 方法
func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
	// ...
  
	// Adding the caller or stack trace requires capturing the callers of
	// this function. We'll share information between these two.
    // 译文:添加调用者或堆栈跟踪需要捕获此函数的调用者。我们将在这两者之间共享信息。
	stackDepth := stacktraceFirst
	if addStack {
		stackDepth = stacktraceFull
	}
	stack := captureStacktrace(log.callerSkip+callerSkipOffset, stackDepth)// <--
	defer stack.Free()
	// ...
}
// 在captureStacktrace方法中,使用了 runtime 包的 Callers 方法(还有 Caller 方法供我们使用)
// captureStacktrace captures a stack trace of the specified depth, skipping
// the provided number of frames. skip=0 identifies the caller of
// captureStacktrace.
// 译文:captureStacktrace捕获指定深度的堆栈跟踪,跳过提供的帧数skip;
// skip = 0 表示captureStacktrace的调用者。
// The caller must call Free on the returned stacktrace after using it.
func captureStacktrace(skip int, depth stacktraceDepth) *stacktrace {
	stack := _stacktracePool.Get().(*stacktrace)// 涉及了池化管理
	// 这里根据 depth 参数的值来决定如何分配存储空间。如果深度为 stacktraceFirst,则只保留一个栈帧的空间;
    // 如果深度为 stacktraceFull,则使用整个 stack.storage。
	switch depth {
	case stacktraceFirst: // const stacktraceFirst stacktraceDepth = iota
		stack.pcs = stack.storage[:1]
	case stacktraceFull:  // const stacktraceFull stacktraceDepth
		stack.pcs = stack.storage
	}

	// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers
	// itself. +2 to skip captureStacktrace and runtime.Callers.
	numFrames := runtime.Callers(// <--
		skip+2,// 假设skip=0,则直接跳过了runtime.Callers(0层)和captureStacktrace(1层),
    			 // 所以捕获到的栈帧是从调check方法开始的
		stack.pcs,
	)

	// runtime.Callers truncates the recorded stacktrace if there is no
	// room in the provided slice. For the full stack trace, keep expanding
	// storage until there are fewer frames than there is room.
    // 如果深度为 stacktraceFull,则会检查捕获到的栈帧数量是否与存储空间大小相同,如果相同则会扩展存储空间,直到足够放下所有的栈帧信息。
    // 如果深度不是 stacktraceFull,则只保留捕获到的栈帧数。
	if depth == stacktraceFull {
		pcs := stack.pcs
		for numFrames == len(pcs) {// 说明满了
			pcs = make([]uintptr, len(pcs)*2)
			numFrames = runtime.Callers(skip+2, pcs)
		}

		// Discard old storage instead of returning it to the pool.
		// This will adjust the pool size over time if stack traces are
		// consistently very deep.
		stack.storage = pcs
		stack.pcs = pcs[:numFrames]// 保留numFrames个帧
	} else {
		stack.pcs = stack.pcs[:numFrames]
	}

	stack.frames = runtime.CallersFrames(stack.pcs)
	return stack
}

结果:

  • 通过 runtime.Caller()获取当前代码的文件路径、行数
    • runtime.Caller(1) 表示获取调用栈的上一层,即调用 runtime.Caller 函数的位置的信息。
    • runtime.Caller(0),那么就是获取调用 runtime.Caller 函数的位置的信息。

仅供参考:我们的rpc项目中,impl.xxxclient.RPCfunc()【我们想捕获的】 -> getxxxclient()【可忽略】 -> xxxclient.RPCfunc()【在此处使用 runtime.Caller】-> grpc Invoke

示例:

在每个rpc 调用xxxclient.RPCfunc()内,invoke 前去调用runtime.Caller(1),就可以捕获到impl.xxxclient.RPCfunc()的栈帧。

这个是 protoc-gen-grpc-go 插件生成的rpc方法代码

func (c *ServiceClient) RPCfunc(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
	// 打印日志
  _, file, line, _ := runtime.Caller(1)// <-- 捕获到impl.xxxclient.RPCfunc()的栈帧
	grpclog.Infof(fmt.Sprintf("func: %s:%s"),file,line)
	out := new(QueryServicePeriodResponse)
	err := c.cc.Invoke(ctx, _func, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值