2015-09-09 9 views
20

Potrzebuję rejestrować żądania klienta HTTP akka, a także ich odpowiedzi. Chociaż wydaje się, że istnieje interfejs API do rejestrowania tych żądań, nie ma jasnej dokumentacji, jak należy to zrobić. Moje podejście było stworzenie zalogowany wniosek, który przejrzyście otacza Http().singleRequest(req) następująco:W jaki sposób jeden dziennik żąda klienta HTTP Akka

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 
    } 
} 

Niestety, muszę złapać przyszłość albo poprzez unmarshal lub po prostu zainteresowanie resp.entity.dataBytes w celu odzyskania ciała odpowiedzi. Dostaję rejestrację, ale obietnica zostaje zakończona i nie mogę już zwolnić jednostki z rzeczywistych danych. Roztwór roboczy będzie rejestrować żądanie i odpowiedź i przekazać tę sprawę testową bez IllegalStateException z „Obietnica już zakończone” wyrzucane:

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 
    } 
} 

pomysły mile widziane.

+1

Próbuję zrobić to samo. Znalazłeś rozwiązanie? –

Odpowiedz

22

Jeden roztwór I zostały stwierdzone jest, aby użyć:

import akka.http.scaladsl.server.directives.DebuggingDirectives 

val clientRouteLogged = DebuggingDirectives.logRequestResult("Client ReST", Logging.InfoLevel)(clientRoute) 
Http().bindAndHandle(clientRouteLogged, interface, port) 

który można łatwo zalogować żądanie i spowodować w formacie RAW (bajtów). Problem polega na tym, że te dzienniki są całkowicie nieczytelne. I tu jest miejsce, w którym stało się skomplikowane.

Oto mój przykład, który koduje encję żądania/odpowiedzi i zapisuje ją do rejestratora.

można przekazać funkcję:

DebuggingDirectives.logRequestResult 

def logRequestResult(magnet: LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit]) 

To jest funkcja napisane przy użyciu magnet pattern:

LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit] 

Gdzie:

LoggingMagnet[T](f: LoggingAdapter ⇒ T) 

Dzięki temu mamy dostęp do wszystkich części że musimy zarejestrować żądanie i wynik. Mamy LoggingAdapter, HttpRequest i RouteResult

W moim przypadku utworzyłem funkcję wewnętrzną. Nie chcę ponownie przekazywać wszystkich parametrów.

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) 
} 

Najważniejszą częścią jest ostatnia linia, w której umieszczam funkcję myLoggingFunction w logRequestResult.

Funkcja o nazwie myLoggingFunction, po prostu dopasowała wynik obliczeń serwera i utworzyła oparty na nim LogEntry.

Ostatnią rzeczą jest metoda, która pozwala dekodować jednostkę wynikową ze strumienia.

def entityAsString(entity: HttpEntity) 
        (implicit m: Materializer, ex: ExecutionContext): Future[String] = { 
entity.dataBytes 
    .map(_.decodeString(entity.contentType().charset().value)) 
    .runWith(Sink.head) 
} 

Metodę można łatwo dodać do dowolnej trasy akka-http.

val myLoggedRoute = logRequestResult(Logging.InfoLevel, clinetRoute) 
Http().bindAndHandle(myLoggedRoute, interface, port) 
+1

Popraw błędy. Proszę poprawić wcięcie. A może dodać trochę więcej informacji o tym, co dokładnie robi twój kod. –

+4

AFAICS pytanie dotyczyło rejestrowania żądań i odpowiedzi u klienta, podczas gdy ta odpowiedź dotyczy rejestrowania żądań i odpowiedzi na serwerze, prawda? –

+1

To zbyt dużo pracy tylko w przypadku problemu przekrojowego, takiego jak rejestracja. To powinno być proste. Poza tym, zgadzam się z Arnout, że nie zapewnia to rozwiązania dotyczącego rejestrowania żądań klientów. – vijar

4

innego rozwiązania, kod ten rejestruje żądania IP i kojarzy liczbę losową z każdego żądania i odpowiedzi dzięki czemu mogą one być związane w dziennikach. Zapisuje również czas reakcji.

Ponieważ żądanie może chwilę potrwać, a może się nie powieść, chciałem natychmiast zobaczyć żądanie i zobaczyć odpowiedź, jeśli i kiedy powróci.

RequestFields to tylko dane, na których zależy mi od wniosku. Domyślnie jest dużo hałasu.

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 
     } 
     } 
    } 
    } 
} 
Powiązane problemy