diff --git a/akka-http-tests/src/test/scala/akka/http/server/directives/DebuggingDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/server/directives/DebuggingDirectivesSpec.scala new file mode 100644 index 0000000000..e4e128802b --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/server/directives/DebuggingDirectivesSpec.scala @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.server +package directives + +import akka.event.LoggingAdapter +import akka.http.util._ + +class DebuggingDirectivesSpec extends RoutingSpec { + var debugMsg = "" + + def resetDebugMsg(): Unit = { debugMsg = "" } + + val log = new LoggingAdapter { + def isErrorEnabled = true + def isWarningEnabled = true + def isInfoEnabled = true + def isDebugEnabled = true + + def notifyError(message: String): Unit = {} + def notifyError(cause: Throwable, message: String): Unit = {} + def notifyWarning(message: String): Unit = {} + def notifyInfo(message: String): Unit = {} + def notifyDebug(message: String): Unit = { debugMsg += message + '\n' } + } + + "The 'logRequest' directive" should { + "produce a proper log message for incoming requests" in { + val route = + withLog(log)( + logRequest("1")( + completeOk)) + + resetDebugMsg() + Get("/hello") ~> route ~> check { + response shouldEqual Ok + debugMsg shouldEqual "1: HttpRequest(HttpMethod(GET),http://example.com/hello,List(),Strict(none/none,ByteString()),HttpProtocol(HTTP/1.1))\n" + } + } + } + + "The 'logResponse' directive" should { + "produce a proper log message for outgoing responses" in { + val route = + withLog(log)( + logResult("2")( + completeOk)) + + resetDebugMsg() + Get("/hello") ~> route ~> check { + response shouldEqual Ok + debugMsg shouldEqual "2: Complete(HttpResponse(200 OK,List(),Strict(none/none,ByteString()),HttpProtocol(HTTP/1.1)))\n" + } + } + } + + "The 'logRequestResponse' directive" should { + "produce proper log messages for outgoing responses, thereby showing the corresponding request" in { + val route = + withLog(log)( + logRequestResult("3")( + completeOk)) + + resetDebugMsg() + Get("/hello") ~> route ~> check { + response shouldEqual Ok + debugMsg shouldEqual """|3: Response for + | Request : HttpRequest(HttpMethod(GET),http://example.com/hello,List(),Strict(none/none,ByteString()),HttpProtocol(HTTP/1.1)) + | Response: Complete(HttpResponse(200 OK,List(),Strict(none/none,ByteString()),HttpProtocol(HTTP/1.1))) + |""".stripMarginWithNewline("\n") + } + } + } + +} \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/server/Directives.scala b/akka-http/src/main/scala/akka/http/server/Directives.scala index 20df4eef19..2edd93f9e7 100644 --- a/akka-http/src/main/scala/akka/http/server/Directives.scala +++ b/akka-http/src/main/scala/akka/http/server/Directives.scala @@ -13,7 +13,7 @@ trait Directives extends RouteConcatenation with CacheConditionDirectives //with ChunkingDirectives with CookieDirectives - //with DebuggingDirectives + with DebuggingDirectives with CodingDirectives with ExecutionDirectives //with FileAndResourceDirectives diff --git a/akka-http/src/main/scala/akka/http/server/directives/DebuggingDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/DebuggingDirectives.scala new file mode 100644 index 0000000000..851cc6ef21 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/server/directives/DebuggingDirectives.scala @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.server +package directives + +import akka.event.Logging._ +import akka.event.LoggingAdapter +import akka.http.model._ + +trait DebuggingDirectives { + import BasicDirectives._ + + def logRequest(magnet: LoggingMagnet[HttpRequest ⇒ Unit]): Directive0 = + extractRequestContext.flatMap { ctx ⇒ + magnet.f(ctx.log)(ctx.request) + pass + } + + def logResult(magnet: LoggingMagnet[RouteResult ⇒ Unit]): Directive0 = + extractRequestContext.flatMap { ctx ⇒ + mapRouteResult { result ⇒ + magnet.f(ctx.log)(result) + result + } + } + + def logRequestResult(magnet: LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit]): Directive0 = + extractRequestContext.flatMap { ctx ⇒ + val logResult = magnet.f(ctx.log)(ctx.request) + mapRouteResult { result ⇒ + logResult(result) + result + } + } +} + +object DebuggingDirectives extends DebuggingDirectives + +case class LoggingMagnet[T](f: LoggingAdapter ⇒ T) // # logging-magnet + +object LoggingMagnet { + implicit def forMessageFromMarker[T](marker: String) = // # message-magnets + forMessageFromMarkerAndLevel[T](marker -> DebugLevel) + + implicit def forMessageFromMarkerAndLevel[T](markerAndLevel: (String, LogLevel)) = // # message-magnets + forMessageFromFullShow[T] { + val (marker, level) = markerAndLevel + Message ⇒ LogEntry(Message, marker, level) + } + + implicit def forMessageFromShow[T](show: T ⇒ String) = // # message-magnets + forMessageFromFullShow[T](msg ⇒ LogEntry(show(msg), DebugLevel)) + + implicit def forMessageFromFullShow[T](show: T ⇒ LogEntry): LoggingMagnet[T ⇒ Unit] = // # message-magnets + LoggingMagnet(log ⇒ show(_).logTo(log)) + + implicit def forRequestResponseFromMarker(marker: String) = // # request-response-magnets + forRequestResponseFromMarkerAndLevel(marker -> DebugLevel) + + implicit def forRequestResponseFromMarkerAndLevel(markerAndLevel: (String, LogLevel)) = // # request-response-magnets + forRequestResponseFromFullShow { + val (marker, level) = markerAndLevel + request ⇒ response ⇒ Some( + LogEntry("Response for\n Request : " + request + "\n Response: " + response, marker, level)) + } + + implicit def forRequestResponseFromFullShow(show: HttpRequest ⇒ RouteResult ⇒ Option[LogEntry]): LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit] = // # request-response-magnets + LoggingMagnet { log ⇒ + request ⇒ + val showResult = show(request) + result ⇒ showResult(result).foreach(_.logTo(log)) + } +} + +case class LogEntry(obj: Any, level: LogLevel = DebugLevel) { + def logTo(log: LoggingAdapter): Unit = { + log.log(level, obj.toString) + } +} + +object LogEntry { + def apply(obj: Any, marker: String, level: LogLevel): LogEntry = + LogEntry(if (marker.isEmpty) obj else marker + ": " + obj, level) +}