0

In my Scalatra routes, I often use halt() to fail fast:

val user: User = userRepository.getUserById(params("userId"))
    .getOrElse {
        logger.warn(s"Unknown user: $userId")
        halt(404, s"Unknown user: $userId")
    }

As shown in the example, I also want to log a warning in those cases. But I'd like to avoid the code duplication between the halt() and the logger. It would be a lot cleaner to simply do:

val user: User = userRepository.getUserById(params("userId"))
    .getOrElse(halt(404, s"Unknown user: $userId"))

What would be the best way of logging all "HaltExceptions" in a cross-cutting manner ?

I've considered:

1) Overriding the halt() method in my route:

override def halt[T](status: Integer, body: T, headers: Map[String, String])(implicit evidence$1: Manifest[T]): Nothing = {
    logger.warn(s"Halting with status $status and message: $body")
    super.halt(status, body, headers)
}

Aside from the weird method signature, I don't really like this approach, because I could be calling the real halt() by mistake instead of the overridden method, for example if I'm halting outside the route. In this case, no warning would be logged.

2) Use trap() to log all error responses:

trap(400 to 600) {
    logger.warn(s"Error returned with status $status and body ${extractBodyInSomeWay()}")
}

But I'm not sure it's the best approach, especially since it adds 201 routes to the _statusRoutes Map (one mapping for each integer in the range...). I also don't know how to extract the body here ?

3) Enable some kind of response logging in Jetty for specific status codes ?


What would be the best approach to do this? Am I even approaching this correctly?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Etienne Neveu
  • 12,604
  • 9
  • 36
  • 59

1 Answers1

1

The easiest solution is doing it in a servlet filter like below:

package org.scalatra.example

import javax.servlet._
import javax.servlet.http.HttpServletResponse

class LoggingFilter extends Filter {

  override def init(filterConfig: FilterConfig): Unit = ()
  override def destroy(): Unit = ()

  override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
    chain.doFilter(request, response)

    val status = response.asInstanceOf[HttpServletResponse].getStatus
    if (status >= 400 && status <= 600) {
      // Do logging here!
    }
  }
}

Register this filter in your Bootstrap class (or it's possible even in web.xml):

package org.scalatra.example

import org.scalatra._
import javax.servlet.ServletContext

class ScalatraBootstrap extends LifeCycle {
  override def init(context: ServletContext): Unit = {
    context.addFilter("loggingFilter", new LoggingFilter())
    context.getFilterRegistration("loggingFilter")
      .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")

    // mount your servlets or filters
    ...
  }
}

In my opinion, Scalatra should provide a way to trap halting easier essentially. In fact, there is a method named renderHaltException in ScalatraBase, it looks to be possible to add logging by overriding this method at a glance: https://github.com/scalatra/scalatra/blob/cec3f75e3484f2233274b1af900f078eb15c35b1/core/src/main/scala/org/scalatra/ScalatraBase.scala#L512

However we can't do it actually because HaltException is package private and it can be accessed inside of org.scalatra package only. I wonder HaltException should be public.

Naoki Takezoe
  • 471
  • 2
  • 7