app/logging/RequestLoggingFilter.scala (27 lines of code) (raw):
package logging
import net.logstash.logback.marker.Markers.appendEntries
import org.apache.pekko.stream.Materializer
import play.api.mvc.{Filter, RequestHeader, Result}
import play.api.{Logging, MarkerContext}
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters.*
import scala.util.{Failure, Success}
/** Logs all requests and responses. Fields logged:
* - type: always "access" to distinguish it from an app log entry
* - origin: the IP address of the client
* - referrer: the referrer header
* - method: the HTTP method
* - status: the HTTP status code
* - duration: the duration of the request in milliseconds
* - protocol: the HTTP protocol version
* - requested_uri: the requested URI surprisingly
* - content_length: the length of the response body
* - message: summary for a successful request or error and stacktrace if request failed
*
* Largely stolen from
* https://github.com/guardian/cdk-playground/blob/02e91848c5c70f72c281a02a5f7107c6de0298d4/app/RequestLoggingFilter.scala
*/
class RequestLoggingFilter(override val mat: Materializer)(implicit ec: ExecutionContext) extends Filter with Logging {
override def apply(next: RequestHeader => Future[Result])(request: RequestHeader): Future[Result] = {
val start = System.currentTimeMillis()
val result = next(request)
def markerContext(logEntry: LogEntry) = MarkerContext(appendEntries(logEntry.otherFields.asJava))
result onComplete {
case Success(response) =>
val duration = System.currentTimeMillis() - start
val logEntry = LogEntry.requestAndResponse(request, response, duration)
logger.info(logEntry.message)(markerContext(logEntry))
case Failure(err) =>
val duration = System.currentTimeMillis() - start
val logEntry = LogEntry.error(request, duration)
logger.info(logEntry.message)(markerContext(logEntry))
logger.error(s"Error for ${request.method} ${request.uri}", err)
}
result
}
}