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
応答の本文を回復するため。ロギングを取得しますが、約束は完了し、エンティティを実際のデータに非整列化できなくなります。実用的なソリューションは、リクエストとレスポンスを記録し、IllegalStateException
なしでこのテストケースに合格し、「Promise already completed」がスローされます。
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)
要求を簡単にログに記録でき、raw(バイト)形式で結果を記録できます。問題は、これらのログが完全に読み取れないことです。そして、ここが複雑になった場所です。
要求/応答のエンティティをエンコードし、ロガーに書き込む私の例を次に示します。
以下に関数を渡すことができます。
DebuggingDirectives.logRequestResult
def logRequestResult(magnet: LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit])
これは、 マグネットパターン を使用して記述された関数です。
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)
}
最も重要な部分は、logRequestResultにmyLoggingFunctionを配置する最後の行です。
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)
別の解決策として、このコードはリクエスト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)
private 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