2015-09-09 3 views
20

Devo registrare le richieste del client HTTP di akka e le relative risposte. Mentre sembra esserci un suggerimento di API per la registrazione di queste richieste, non esiste una documentazione chiara su come dovrebbe essere fatto. Il mio approccio è stato quello di creare una richiesta di login che avvolge in modo trasparente Http().singleRequest(req) come segue:Come si accede a un client HTTP Akka richiede

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

Purtroppo, devo afferrare il futuro sia attraverso il unmarshal o semplicemente richiedendo resp.entity.dataBytes al fine di recuperare il corpo della risposta. Ottengo il logging ma la promessa viene completata e non riesco più a distinguere l'entità dai dati reali. Una soluzione di lavoro potrebbe registrare la richiesta e risposta e passare questo banco di prova senza un IllegalStateException con "promessa già completato" essere gettato:

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

Idee benvenuto.

+1

Sto provando a fare lo stesso. Hai trovato una soluzione? –

risposta

22

Una delle soluzioni che ho trovato è quello di utilizzare un:

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

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

che può facilmente accedere alla richiesta e il risultato in formato RAW (byte). Il problema è che quei log sono completamente illeggibili. E qui è il posto dove è diventato complicato.

Ecco il mio esempio che codifica l'entità della richiesta/risposta e lo scrive nel registratore.

È possibile passare una funzione di:

DebuggingDirectives.logRequestResult 

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

che è funzione scritto usando magnet pattern:

LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit] 

Dove:

LoggingMagnet[T](f: LoggingAdapter ⇒ T) 

Grazie a che abbiamo accesso a tutte le parti che dobbiamo registrare la richiesta e il risultato. Abbiamo LoggingAdapter, HttpRequest e RouteResult

Nel mio caso ho creato una funzione interna. Non voglio passare di nuovo tutti i parametri.

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

La parte più importante è l'ultima linea dove ho messo myLoggingFunction per logRequestResult.

La funzione denominata myLoggingFunction, corrisponde semplicemente al risultato del calcolo del server e crea un LogEntry basato su di esso.

L'ultima cosa è un metodo che consente di decodificare l'entità risultato da un flusso.

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

Il metodo può essere facilmente aggiunto a qualsiasi percorso akka-http.

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

Si prega di correggere gli errori di battitura. Si prega di correggere l'indentazione. E magari aggiungi qualche informazione in più su cosa sta facendo esattamente il tuo codice. –

+4

AFAICS la domanda riguardava la registrazione delle richieste e delle risposte sul client, mentre questa risposta riguarda la registrazione delle richieste e delle risposte sul server, giusto? –

+1

Questo è troppo lavoro solo per questioni trasversali come la registrazione. Dovrebbe essere semplice Inoltre, sono d'accordo con Arnout sul fatto che questo non fornisce una soluzione sulla registrazione delle richieste dei client. – vijar

4

Per un'altra soluzione, questo codice registra il PI richiesta e associa un numero casuale a ogni richiesta e risposta, in modo che possano essere associati nei registri. Registra anche il tempo di risposta.

Poiché la richiesta può richiedere un po 'di tempo per elaborare e potrebbe non riuscire, volevo vedere immediatamente la richiesta e vedere la risposta se e quando ritorna.

RequestFields sono solo i dati che mi interessano dalla richiesta. C'è un sacco di rumore di default.

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