Scala 如何记录Akka HTTP客户端请求

Scala 如何记录Akka HTTP客户端请求,scala,akka,akka-http,Scala,Akka,Akka Http,我需要记录akka http客户端请求及其响应。虽然似乎有记录这些请求的API提示,但没有明确的文档说明应该如何做。我的方法是创建一个记录的请求,它透明地包装Http()。singleRequest(req),如下所示: def loggedRequest(req: HttpRequest) (implicit system: ActorSystem, ctx: ExecutionContext, m: Materializer): Future[HttpRe

我需要记录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
  }
}
不幸的是,为了恢复响应主体,我必须通过解组或简单地请求
resp.entity.dataBytes
来把握未来。我得到了日志记录,但承诺完成了,我无法再将实体解组到实际数据。工作解决方案将记录请求和响应,并通过此测试用例,而不会抛出“Promise ready completed”(承诺已完成)的
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
  }
}

欢迎想法。

我发现的解决方案之一是使用:

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])
这是使用以下方法编写的函数:

其中:

LoggingMagnet[T](f: LoggingAdapter ⇒ T)
因此,我们可以访问记录请求和结果所需的所有部分。我们有LoggingAdapter、HttpRequest和RouterResult

在我的例子中,我创建了一个内部函数。我不想再次传递所有参数

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)
}
最重要的部分是我将MyLogging函数放入logRequestResult的最后一行

名为myLoggingFunction的函数,简单地匹配服务器计算的结果,并基于它创建一个日志条目

最后一件事是允许从流中解码结果实体的方法

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)

对于另一个解决方案,此代码记录请求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
        }
      }
    }
  }
}

我的完整解决方案,灵感来自@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

我也试着这么做。你找到解决办法了吗?请更正输入错误。请更正缩进。也许还可以添加一些关于你的代码到底在做什么的信息。AFAICS问题是关于在客户端记录请求和响应,而这个答案是关于在服务器上记录请求和响应,对吗?这对于像日志这样的交叉关注来说太多了。它应该很简单。此外,我同意Arnout的观点,即这并不能提供记录客户机请求的解决方案。@ArnoutEngelen AFAICS问题不是要求“在客户机上记录”,而是要求“记录客户机请求”,其中“客户机”是多余的。因此,“如何记录请求和响应”会更准确。问题是关于HTTP客户端请求的,所使用的代码示例用于HTTP客户端。我也在寻找同样的答案,这个答案是错误的。谢谢肖恩-有可能发布完整的代码,包括导入吗?您的自定义代码和akka代码中的内置代码尚不清楚。我如何在我的路线中实现I?我已将日志固定为public。将其用作任何其他指令:post{IctTraceDirectives.log{complete(null)}您应该显示导入:import java.util.concurrent.atomic.AtomicLong import akka.http.scaladsl.server.{指令,Directive0,Directive1,Route}导入akka.http.scaladsl.server.directions.\uimport com.google.common.base.Stopwatch import com.typesafe.scalalogg.LazyLogging。。。并提到依赖项:“com.typesafe.scala日志”%%“scala日志”%%“3.9.2”
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