如何在go中的每个函数中自动创建span?

myzjeezk  于 2023-08-01  发布在  Go
关注(0)|答案(2)|浏览(79)

我想用Otel来记录每个请求的exec行。但是我发现如果我想计算每个函数的执行时间,我必须为它创建一个跨度。就像这样:

func ATestFunction(ctx context.Context, username string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "operation name")
    defer span.Finish()

   // do something
}

字符串
这意味着我想记录执行时间的每个函数都必须添加两行固定代码。我怎么能解决这样的问题与增加固定的代码。

0g0grzrc

0g0grzrc1#

我怎么能解决这样一个问题与添加固定代码。
你不能这样做。
如果你想对每个函数都进行检测,你必须这样做:手动或通过代码生成/重写。

l5tcr1uw

l5tcr1uw2#

这是个合理的问题插装已经是一些看起来像开销和忙碌的工作,杂乱的东西,应该是优雅的编写。
好消息是,通过从跟踪开始,您还可以(原则上)涵盖日志记录(向span添加事件)和指标(将它们绑定到span的特定事件或指标,或使用围绕跟踪构建的工具,如Honeycomb.io或Jaeger)。因此,您已经走上了通往低杂波仪器系统的良好道路。
另外,不应该丢弃StartSpanFromContext返回的第二个值。保持新的语境非常重要;这个上下文有了新的跨度!它必须传递给子函数才能正常工作。
另一个值得注意的一般观点是,添加的块 * 是有意义的。并不是你写的每个函数都值得工具化。span应该代表一个专业应用程序用户或系统管理员可能理解的函数的开始,但您不必非常熟悉源代码。
如果您想要一种零编码开销的方法来理解应用程序性能,您可能正在寻找PProf。这一个让你只包括模块,并在一个goroutine中启动一个HTTP服务器,然后你可以在命令行中连接到它以获得PProf输出。它的工作原理是以100 Hz的频率停止每个goroutine,并检查每个goroutine的调用堆栈,为找到的每一行添加1。它的开销低得惊人,并允许您创建像样的火焰图,您可以使用这些图来确定在哪里花费了时间来执行缓慢的操作,或者混合使用服务器的哪些代码是最热的。
因此,一旦你接受了开始一个span * 是 * 引入一个有用的东西-它连接代码的内部,与应用程序的“核心逻辑”/“业务逻辑”概念(也是后端服务)-那么你可以看到它不仅仅是滑稽的代码:它应该是有用的,精心挑选的词语来描述发生的事情。有时它会在那里,大多数情况下不会。您正在与运行应用程序的人员进行通信,了解发生了什么。它们不是“自我笔记”。
无论如何,围绕您正在使用的库编写一个抽象是可以的。制作一个让代码尽可能干净的 Package 器是很好的,也是应用程序体系结构的一个重要部分。例如,您可以将span变量保留在ctx中,使用:

ctx = instrumentation.StartSpan(ctx, "operation name")
defer instrumentation.EndSpan(ctx)

// something that uses the span
instrumentation.Log(ctx, "eventID", log.String("foo", "bar"))

字符串
你还剩下两行,这是不可避免的,因为你有一个赋值,deferdefer必须发生在同一个函数中。因此,虽然抽象并没有减少行数,但它确实消除了一个并不总是需要的变量,也许您可以看到这种使用context.Context的风格如何简化 * 横切关注点 *,例如配置和插装。
这两个函数看起来像这样(在instrumentation模块中):

type spanTag int
// special tag for getting the span out.  Impossible for other modules to
// do this without going through public functions in this module.
var contextSpanTag = spanTag(42)

func StartSpan(ctx context.Context) context.Context {
    span, newCtx := opentracing.StartSpanFromContext(ctx, "operation name")
    // this might be redundant; opentracing may have already put the
    // span inside `newCtx` and provided a way to get it out again.
    // If so, the internals of this code would change or maybe the
    // whole abstraction become moot.
    return newCtx.WithValue(contextSpanTag, span)
}

func EndSpan(ctx context.Context, operationName string) context.Context {
    ctx.Value(contextSpanTag).(*opentracing.Span).Finish()
}

func Span(ctx context.Context) *opentracing.Span {
    return ctx.Value(contextSpanTag)
}


一般来说,Go代码不是为了最小行数而编写的。重要的是代码易于阅读和计算。因此,最好的办法就是使用它提供给你的东西,或者如果你有改进它的想法,把它们提交给“go-nuts”邮件列表。

相关问题