我已经分发了HTTP头中服务之间的请求(特别是用于opentracing)的跟踪信息。
我希望能够在任何地方提供此信息,因此我可以在日志中包含trace-id,传播到下游请求等。
因此,我希望在处理请求之前解析标头以创建Span
,使跨区可用于处理代码,然后在处理请求后进行一些清理。
起初看起来过滤器似乎是正确的方法,但是有一些问题:
Accumulator
,以便累积步骤包含在具有线程本地集的上下文中。我能做的最好的事情是使用Accumulator.source
作为我返回的累加器,然后使用嵌套的动作累加器和来自它的源和一个包装所有方法的自定义物理化器来设置线程本地人。但是这仍然不起作用,我认为因为物化器正在调度到其他线程。我已经为akka配置了默认调度程序来跟踪这些线程本地,但是在这种情况下,物化器似乎绕过了它。所以我几乎放弃了过滤器。我的下一个尝试是创建一个自定义ActionBuilder来包装Actions以将Span附加到每个请求,并使用thread-locals set运行Action in。这种作品,对于行动的身体。但是,我现在稍后创建了Span,在解析了body之后,BodyParser仍然无法访问它。它也相当不方便,因为我现在必须更改所有控制器以使用它来创建Actions而不是Action
对象(并且有很多它们)。
是否有某种方法可以将数据带外传播(意思是,不是Body Content本身的一部分)从body解析器或更早的传播到Action的主体。或者将数据从Filter发送到Accumulator执行的代码?
答案 0 :(得分:0)
听起来你已经尝试了以下内容,但我仍然想提供一个答案,只是为了明确你所尝试的内容。
ActionTransformer
可以无条件地向请求添加信息:
case class Span(id: String, message: String)
class RequestWithSpan[A](val span: Span, request: Request[A])
extends WrappedRequest[A](request)
class SpanAction(val parser: BodyParser[AnyContent])(implicit val executionContext: ExecutionContext)
extends ActionBuilder[RequestWithSpan, AnyContent]
with ActionTransformer[Request, RequestWithSpan] {
def transform[A](request: Request[A]) = Future.successful {
val span = Span(
id = request.headers("X-SPAN-ID"),
message = request.headers("X-SPAN-MESSAGE")
)
new RequestWithSpan(span, request)
}
}
class SomeController(
...
someService: SomeService,
spanAction: SpanAction) extends AbstractController(cc) with ... {
def someEndpoint: Action[AnyContent] = {
spanAction { implicit request =>
implicit val span = request.span
someService.doSomething(...)(span)
Ok(span.toString)
}
}
}
正如你所说的那样,现在不得不在任何地方注入SpanAction
。
顺便说一下,将跟踪/调试信息透明地添加到请求并让它在整个系统中流淌的想法真的很酷!