自定义wrapWithTracing Directive执行顺序异常问题咨询
问题描述
我自定义了一个用于链路追踪的指令wrapWithTracing,代码如下:
object CustomDirectives extends Directives { def wrapWithTracing(name: String)(innerRoute: => Route)(implicit req: RequestContext): Route = { //start span // Apply the inner route and handle completion ctx => Try { innerRoute // Execute the inner route } match { case Success(routeResult) => //End span routeResult case Failure(ex) => // End span with error on failure throw ex } } }
之后我在/get接口中按如下方式使用该指令,期望调用接口时先启动span,再执行auth()校验和execute()业务逻辑,最后根据结果结束span:
def get: Route = { path("/get") { mapResponse(wrapToJson) { implicit ctx => { CustomDirectives.wrapWithTracing("/get") { auth() { (param) => complete { execute(param) } } } }.apply(ctx) // Manually apply the context here } } }
但实际运行时,auth()的逻辑会先执行,之后才进入wrapWithTracing启动span。原接口代码(未加追踪时)如下,auth的定义也附在下方:
// 原端点代码 def get: Route = { path("/get") { mapResponse(wrapToJson) { implicit ctx => { auth() { (param) => complete { execute(param) } } }.apply(ctx) // Manually apply the context here } } } // auth方法定义 def authReq()(f: Option[X] => Route)(implicit req: RequestContext): Route = {...}
现有代码已充分测试,无法大幅修改,请问我哪里操作有误?
问题原因
问题出在Scala传名参数的求值时机和手动传递RequestContext的方式上:
wrapWithTracing的innerRoute是传名参数(=> Route),但你在定义innerRoute时直接写了auth() { ... }——而auth()需要隐式的RequestContext,外部作用域的implicit ctx已经可用,导致auth()会立即求值执行,而非等到wrapWithTracing内部启动span后再执行。- 你手动调用
.apply(ctx)的方式,相当于先把wrapWithTracing(...) { auth(...) }整个表达式求值完成,再把结果(一个Route)应用到ctx上,这就导致auth()逻辑在wrapWithTracing启动span之前就跑完了。
解决方案
要让auth()延迟到wrapWithTracing启动span后执行,需确保innerRoute的求值完全包裹在wrapWithTracing的上下文里,同时避免提前触发auth()的执行,以下是最小改动方案:
1. 微调wrapWithTracing实现
把启动span的逻辑移到ctx =>闭包内部,确保先启动span再执行innerRoute:
object CustomDirectives extends Directives { def wrapWithTracing(name: String)(innerRoute: => Route)(implicit req: RequestContext): Route = { ctx => // 先启动span,确保逻辑顺序正确 val span = startSpan(name) Try { innerRoute // 此时innerRoute会在span启动后才求值执行 } match { case Success(routeResult) => endSpan(span) // 正常结束span routeResult case Failure(ex) => endSpanWithError(span, ex) // 带错误信息结束span throw ex } } }
2. 修改接口调用方式
避免让auth()提前依赖外部的隐式ctx,调整为让wrapWithTracing内部传递隐式上下文:
def get: Route = { path("/get") { mapResponse(wrapToJson) { implicit ctx => // 确保auth的调用被包裹在延迟求值块中,避免提前执行 CustomDirectives.wrapWithTracing("/get") { auth() { param => complete { execute(param) } } }.apply(ctx) } } }
如果允许做微小风格调整(更符合Akka HTTP指令组合的写法),可以去掉手动调用.apply(ctx)的逻辑:
def get: Route = { path("/get") { mapResponse(wrapToJson) { CustomDirectives.wrapWithTracing("/get") { auth() { param => complete(execute(param)) } } } } }
核心逻辑
利用Scala传名参数的延迟求值特性,确保innerRoute(也就是auth()和execute()的逻辑)在wrapWithTracing启动span之后才被求值执行,同时避免在外部作用域提前触发需要隐式上下文的指令执行。
内容的提问来源于stack exchange,提问作者Nirmal
相关产品推荐
相关产品推荐

