scala 如何记录 Akka HTTP 客户端请求
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32475471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does one log Akka HTTP client requests
提问by David Weber
I need to log akka http client requests as well as their responses. While there seems to be a hint of API for logging these requests, there is no clear documentation on how it should be done. My approach has been to create a logged request which transparently wraps Http().singleRequest(req)as follows:
我需要记录 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].foreach{s ?
system.log.info(req.toString)
system.log.info(resp.toString + "\n" + s)
}
resp
}
}
Unfortunately, I have to grab the future either through the unmarshal or by simply requesting resp.entity.dataBytesin order to recover the body of the response. I get the logging but the promise gets completed and I can no longer unmarshal the entity to the actual data. A working solution would log the request and response and pass this test case without an IllegalStateExceptionwith "Promise already completed" being thrown:
不幸的是,我必须通过解组或简单地请求来获取未来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
}
}
Ideas welcome.
欢迎提出想法。
采纳答案by Tomasz Wujec
One of the solution I've found is to use a:
我发现的解决方案之一是使用:
import akka.http.scaladsl.server.directives.DebuggingDirectives
val clientRouteLogged = DebuggingDirectives.logRequestResult("Client ReST", Logging.InfoLevel)(clientRoute)
Http().bindAndHandle(clientRouteLogged, interface, port)
Which can easily log the request and result in raw(bytes) format. The problem is that those logs are completely unreadable. And here is place where it became complicated.
它可以轻松记录请求并以原始(字节)格式生成。问题是这些日志完全不可读。这是变得复杂的地方。
Here is my example that encode the entity of the request/response and write it to the logger.
这是我对请求/响应的实体进行编码并将其写入记录器的示例。
You can pass a function to:
您可以将函数传递给:
DebuggingDirectives.logRequestResult
def logRequestResult(magnet: LoggingMagnet[HttpRequest ? RouteResult ? Unit])
That is function written using magnet pattern:
这是使用磁铁模式编写的函数:
LoggingMagnet[HttpRequest ? RouteResult ? Unit]
Where:
在哪里:
LoggingMagnet[T](f: LoggingAdapter ? T)
Thanks to that we have access to all parts that we need to log the request and result. We have LoggingAdapter, HttpRequest and RouteResult
因此,我们可以访问记录请求和结果所需的所有部分。我们有 LoggingAdapter、HttpRequest 和 RouteResult
In my case I've create an inside function. I don't want to pass all the parameters again.
就我而言,我创建了一个内部函数。我不想再次传递所有参数。
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)
}
The most important part is the last line where I put myLoggingFunction in to logRequestResult.
最重要的部分是我将 myLoggingFunction 放入 logRequestResult 的最后一行。
The function called myLoggingFunction, simple matched the result of server computation and create a LogEntry based on it.
名为 myLoggingFunction 的函数,简单地匹配服务器计算的结果并基于它创建一个 LogEntry。
The last thing is a method that allows to decode the result entity from a stream.
最后一件事是允许从流中解码结果实体的方法。
def entityAsString(entity: HttpEntity)
(implicit m: Materializer, ex: ExecutionContext): Future[String] = {
entity.dataBytes
.map(_.decodeString(entity.contentType().charset().value))
.runWith(Sink.head)
}
The method can be easily add to any akka-http route.
该方法可以轻松添加到任何 akka-http 路由。
val myLoggedRoute = logRequestResult(Logging.InfoLevel, clinetRoute)
Http().bindAndHandle(myLoggedRoute, interface, port)
回答by seanmcl
For another solution, this code logs the request IP and associates a random number with each request and response so they can be associated in the logs. It also records the response time.
对于另一个解决方案,此代码记录请求 IP 并将随机数与每个请求和响应相关联,以便它们可以在日志中关联。它还记录响应时间。
Since the request may take awhile to process, and may fail, I wanted to see the request immediately, and see the response if and when it returns.
由于请求可能需要一段时间来处理,并且可能会失败,我想立即查看请求,并在返回时查看响应。
RequestFieldsis just the data I care about from the request. There's a lot of noise by default.
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
}
}
}
}
}
回答by G.Domozhirov
My complete solution, inspired by @seanmcl
我的完整解决方案,灵感来自@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

