http - Play Framework : How to process server errors in HTTP filters?

标签 http logging error-handling playframework

我正在尝试创建一个 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/

相关文章:

java - 保存字段标记为可更新 = false 的实体时,禁用警告 "entity was modified, but it won' t be update because the property is immutable

python - 而无限循环python

android - 从 Web 服务获取数据时强制关闭/崩溃

http - 在 go net/http 中获取 http body

c# - 使用C# HttpWebRequest 发送json数据到web服务

logging - 如何使用 NLog 登录到具有不同日志记录级别的多个目标?

java - Log4j 2 中的单独错误记录

angular - 使用拦截器在 Angular 4.3 中使用新 header 重试 Http 请求

http - 如何使用 "POST to PUT redirect"建模 REST API?

reactjs - Redux中的验证处理