我正在尝试创建一个 HTTP 过滤器,用于记录有关请求的一些信息,比如 header ,以及请求主体的有限(因此内存不会爆炸)部分,以防出现错误。
为此,我遵循了文档 ( https://www.playframework.com/documentation/2.6.x/ScalaHttpFilters ) 并提出了如下内容:
class RequestErrorLogFilter @Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult = accumulator.through(flow).map { result =>
// this code doesn't get executed in case of an exception in a controller
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult
}
}
}
这对于客户端错误(如 400 - 错误请求)或从 Controller 返回的任何错误都适用,但如果 Controller 内部发生异常,则不会执行过滤器的“回调”,因此没有机会记录发生的事情。
还有一个更简单的“AccessLogHttpFilter”也有同样的问题,我认为这是使用 play 应用程序创建访问日志的推荐解决方案:
class LoggingFilter @Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}
有没有办法让 play 在出现异常时调用 http 过滤器代码? 还有其他解决方法吗?
最佳答案
想通了。
要让 EssentialFilter 处理错误,您需要向累加器添加 .recover() 调用:
class RequestErrorLogFilter @Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult: Accumulator[ByteString, Result] = accumulator.through(flow).map { result =>
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult.recover {
case error =>
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error: $error. Request head: $headerText")
throw error
}
}
}
}
对于一个简单的 Filter,您需要对 future 的结果调用 .failed.foreach:
class LoggingFilter @Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
val eventualResult = nextFilter(requestHeader)
eventualResult.failed.foreach { error: Throwable =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned 500 $error")
}
eventualResult.map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}
关于http - Play Framework : How to process server errors in HTTP filters?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48137647/