如何记录 Akka HTTP 客户端请求
Posted
技术标签:
【中文标题】如何记录 Akka HTTP 客户端请求【英文标题】:How does one log Akka HTTP client requests 【发布时间】:2015-12-05 04:21:50 【问题描述】:我需要记录 akka http 客户端请求及其响应。虽然似乎有用于记录这些请求的 API 提示,但没有关于应该如何完成的明确文档。我的方法是创建一个记录的请求,它透明地包装Http().singleRequest(req)
,如下所示:
def loggedRequest(req: HttpRequest)
(implicit system: ActorSystem, ctx: ExecutionContext, m: Materializer): Future[HttpResponse] =
Http().singleRequest(req).map resp ⇒
Unmarshal(resp.entity).to[String].foreachs ⇒
system.log.info(req.toString)
system.log.info(resp.toString + "\n" + s)
resp
不幸的是,我必须通过 unmarshal 或简单地请求 resp.entity.dataBytes
来获取未来,以便恢复响应的主体。我得到了日志记录,但承诺已经完成,我不能再将实体解组为实际数据。一个可行的解决方案将记录请求和响应并通过此测试用例,而不会抛出“承诺已完成”的IllegalStateException
:
describe("Logged rest requests")
it("deliver typed responses")
val foo = Rest.loggedRequest(Get(s"http://127.0.0.1:9000/some/path"))
val resp = foo.futureValue(patience)
resp.status shouldBe StatusCodes.OK
val res = Unmarshal(resp.entity).to[MyClass].futureValue
欢迎提出想法。
【问题讨论】:
我也在尝试这样做。你找到解决办法了吗? 【参考方案1】:我发现的解决方案之一是使用:
import akka.http.scaladsl.server.directives.DebuggingDirectives
val clientRouteLogged = DebuggingDirectives.logRequestResult("Client ReST", Logging.InfoLevel)(clientRoute)
Http().bindAndHandle(clientRouteLogged, interface, port)
这可以轻松记录请求并以原始(字节)格式生成。问题是这些日志完全不可读。这就是它变得复杂的地方。
这是我的示例,它对请求/响应的实体进行编码并将其写入记录器。
您可以将函数传递给:
DebuggingDirectives.logRequestResult
def logRequestResult(magnet: LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit])
这是使用magnet pattern编写的函数:
LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit]
地点:
LoggingMagnet[T](f: LoggingAdapter ⇒ T)
感谢我们可以访问记录请求和结果所需的所有部分。我们有 LoggingAdapter、HttpRequest 和 RouteResult
就我而言,我创建了一个内部函数。我不想再次传递所有参数。
def logRequestResult(level: LogLevel, route: Route)
(implicit m: Materializer, ex: ExecutionContext) =
def myLoggingFunction(logger: LoggingAdapter)(req: HttpRequest)(res: Any): Unit =
val entry = res match
case Complete(resp) =>
entityAsString(resp.entity).map(data ⇒ LogEntry(s"$req.method $req.uri: $resp.status \n entity: $data", level))
case other =>
Future.successful(LogEntry(s"$other", level))
entry.map(_.logTo(logger))
DebuggingDirectives.logRequestResult(LoggingMagnet(log => myLoggingFunction(log)))(route)
最重要的部分是我将 myLoggingFunction 放入 logRequestResult 的最后一行。
名为myLoggingFunction的函数,简单匹配服务器计算的结果,并基于它创建一个LogEntry。
最后一件事是允许从流中解码结果实体的方法。
def entityAsString(entity: HttpEntity)
(implicit m: Materializer, ex: ExecutionContext): Future[String] =
entity.dataBytes
.map(_.decodeString(entity.contentType().charset().value))
.runWith(Sink.head)
该方法可以轻松添加到任何 akka-http 路由中。
val myLoggedRoute = logRequestResult(Logging.InfoLevel, clinetRoute)
Http().bindAndHandle(myLoggedRoute, interface, port)
【讨论】:
请更正错别字。请更正缩进。并且可能添加更多关于您的代码到底在做什么的信息。 AFAICS 的问题是关于在客户端记录请求和响应,而这个答案是关于在服务器上记录请求和响应,对吧? 这对于像日志记录这样的横切关注点来说工作量太大了。应该很简单。此外,我同意 Arnout 的观点,即这并没有提供有关记录客户端请求的解决方案。 @ArnoutEngelen AFAICS 这个问题没有要求“在客户端记录”,而是“记录客户端请求”,其中“客户端”是多余的。所以“如何记录请求和响应”会更准确 问题是关于 HTTP 客户端请求的,使用的代码示例用于 HTTP 客户端。我一直在寻找相同的答案,但这个答案是错误的。【参考方案2】:对于另一种解决方案,此代码记录请求 IP 并将随机数与每个请求和响应相关联,以便它们可以在日志中关联。它还记录响应时间。
由于请求可能需要一段时间才能处理,并且可能会失败,所以我想立即查看请求,并查看响应是否以及何时返回。
RequestFields
只是我在请求中关心的数据。默认情况下有很多噪音。
val logRequestResponse: Directive0 =
extractRequestContext flatMap ctx =>
extractClientIP flatMap ip =>
val id = scala.math.abs(rand.nextLong).toString
onSuccess(RequestFields.fromIdIpAndRequest(id, ip, ctx.request)) flatMap req =>
logger.info("request", req.asJson)
val i = Instant.now()
mapRouteResultWith result =>
Result.fromIdStartTimeAndRouteResult(id, i, result) map res =>
logger.info("response", res.asJson)
result
【讨论】:
谢谢肖恩 - 是否可以发布完整的代码,包括导入?目前还不清楚您的自定义代码是什么,以及内置的 akka 代码是什么。【参考方案3】:我的完整解决方案,灵感来自@seanmcl
trait TraceDirectives extends LazyLogging
private val counter: AtomicLong = new AtomicLong(0)
def log: Directive0 = count flatMap requestId =>
mapInnerRoute(addLoggingToRoute(requestId, _))
private def count: Directive1[Long] = Directive innerRouteSupplier =>
ctx =>
innerRouteSupplier(Tuple1(counter.incrementAndGet()))(ctx)
private def addLoggingToRoute(requestId: Long, innerRoute: Route): Route =
ctx =>
val requestStopwatch = Stopwatch.createStarted()
extractClientIP ip =>
logger.info("Http request, id: , uri: , forwarded ip: ", requestId, ctx.request.uri, ip)
mapResponse(httpResponse =>
logger.info("Http response, id: , code: , time: ", requestId, httpResponse.status.intValue(), requestStopwatch.toString)
httpResponse
)(innerRoute)
(ctx)
object TraceDirectives extends TraceDirectives
【讨论】:
如何在我的路由中实现 i ? 我已将日志修复为公开。将其用作任何其他指令:post IctTraceDirectives.log complete(null) 你应该显示导入: import java.util.concurrent.atomic.AtomicLong import akka.http.scaladsl.server.Directive, Directive0, Directive1, Route import akka.http.scaladsl.server .Directives._ import com.google.common.base.Stopwatch import com.typesafe.scalalogging.LazyLogging ...并提及依赖关系:“com.typesafe.scala-logging”%%“scala-logging”%“3.9。 2"以上是关于如何记录 Akka HTTP 客户端请求的主要内容,如果未能解决你的问题,请参考以下文章
使用 formFields 导致 UnsupportedRequestContentTypeRejection 的 Akka Http 路由测试