All Downloads are FREE. Search and download functionalities are using the official Maven repository.

olon.common.BoxLogging.scala Maven / Gradle / Ivy

package olon
package common

/**
 * Mix this trait in to get some low-cost implicits for logging boxes
 * easily. The consumer will need to implement a `[[logBoxError]]`
 * method to log messages with an optional `Throwable`, as well as its related
 * friends for trace, debug, info, and warn levels. This allows abstracting out
 * where and what the actual logger is.
 *
 * With this mixed in, boxes will have `[[logFailure]]` and
 * `[[logFailure]]` methods. The first logs all `[[Failure]]`s as well
 * as `[[Empty]]`. The second logs only `Failure`s and
 * `[[ParamFailure]]`s, treating `Empty` as a valid value. These both log their
 * respective items at ERROR level. You can also use `traceLog*`, `debugLog*`,
 * `infoLog*`, and `warnLog*` if you want to log at other levels (e.g., you can
 * use `infoLogFailure` to log an `Empty` or `Failure` at `INFO` level).
 *
 * All of these return the box unchanged, so you can continue to use it
 * in `for` comprehensions, call `openOr` on it, etc.
 *
 * There is an implementation for anyone who wants to use Lift's
 * `[[Loggable]]` trait called `[[LoggableBoxLogging]]`. Another implementaiton
 * is available for use with a plain SLF4J logger, `SLF4JBoxLogging`. You can
 * also implement a version for any other logging adapter. Lastly, you can
 * simply import `BoxLogging._` to get the methods available at a top level;
 * however, note that using them this way will lose information about where the
 * log message came from.
 *
 * Here is an example of how you might use this in system that executes a third-
 * party service and notifies another system of failures.
 *
 * {{{
 * val systemResult: Box[ServiceReturn] =
 *   system
 *     .executeService(true, requester)
 *     .map(...)
 *     .logFailure("Failed to execute service") match {
 *       case Full(content) =>
 *         content
 *       case failure: Failure =>
 *         otherSystem.notifyFailure(failure)
 *       case Empty =>
 *         otherSystem.notifyFailure(Failure("No idea what happened."))
 *     }
 * }}}
 */
trait BoxLogging {
  private[this] def logBoxError(message: String): Unit = {
    logBoxError(message, None)
  }
  private[this] def logBoxWarn(message: String): Unit = {
    logBoxWarn(message, None)
  }
  private[this] def logBoxInfo(message: String): Unit = {
    logBoxInfo(message, None)
  }
  private[this] def logBoxDebug(message: String): Unit = {
    logBoxDebug(message, None)
  }
  private[this] def logBoxTrace(message: String): Unit = {
    logBoxTrace(message, None)
  }

  /**
   * Called with an error message and possibly a throwable that caused
   * the error in question. Should ERROR log the message and the throwable.
   *
   * Exists in order to abstract away logger abstractions. Abstractception,
   * as it were.
   */
  protected def logBoxError(message: String, throwable: Option[Throwable]): Unit
  /**
   * Called with a warn message and possibly a throwable that caused the issue
   * in question. Should WARN log the message and the throwable.
   *
   * Exists in order to abstract away logger abstractions. Abstractception,
   * as it were.
   */
  protected def logBoxWarn(message: String, throwable: Option[Throwable]): Unit
  /**
   * Called with an info message and possibly a throwable that caused the issue
   * in question. Should INFO log the message and the throwable.
   *
   * Exists in order to abstract away logger abstractions. Abstractception,
   * as it were.
   */
  protected def logBoxInfo(message: String, throwable: Option[Throwable]): Unit
  /**
   * Called with a debug message and possibly a throwable that caused the issue
   * in question. Should DEBUG log the message and the throwable.
   *
   * Exists in order to abstract away logger abstractions. Abstractception,
   * as it were.
   */
  protected def logBoxDebug(message: String, throwable: Option[Throwable]): Unit
  /**
   * Called with a trace message and possibly a throwable that caused the issue
   * in question. Should TRACE log the message and the throwable.
   *
   * Exists in order to abstract away logger abstractions. Abstractception,
   * as it were.
   */
  protected def logBoxTrace(message: String, throwable: Option[Throwable]): Unit

  implicit class LogEmptyOrFailure[T](val box: Box[T]) extends AnyRef {
    private def doLog(message: String, logFn: (String, Option[Throwable])=>Unit, onEmpty: ()=>Unit) = {
      box match {
        case ParamFailure(failureMessage, exception, Full(chain), param) =>
          logFn(s"$message: $failureMessage with param $param", exception)
          chain.logFailure(s"$failureMessage with param $param caused by", logFn)

        case ParamFailure(failureMessage, exception, _, param) =>
          logFn(s"$message: $failureMessage with param $param", exception)

        case Failure(failureMessage, exception, Full(chain)) =>
          logFn(s"$message: $failureMessage", exception)
          chain.logFailure(s"$failureMessage caused by", logFn)

        case Failure(failureMessage, exception, _) =>
          logFn(s"$message: $failureMessage", exception)

        case Empty =>
          onEmpty()

        case _: Full[_] => // nothing to log
      }
    }

    private def logFailure(message: String, logFn: (String, Option[Throwable])=>Unit): Unit = {
      doLog(message, logFn, ()=>logFn(s"$message: Box was Empty.", None))
    }

    def logEmptyBox(message: String): Box[T] = {
      doLog(message, logBoxError, ()=>logBoxError(s"$message: Box was Empty."))
      box
    }

    def warnLogEmptyBox(message: String): Box[T] = {
      doLog(message, logBoxWarn, ()=>logBoxWarn(s"$message: Box was Empty."))
      box
    }

    def infoLogEmptyBox(message: String): Box[T] = {
      doLog(message, logBoxInfo, ()=>logBoxInfo(s"$message: Box was Empty."))
      box
    }

    def debugLogEmptyBox(message: String): Box[T] = {
      doLog(message, logBoxDebug, ()=>logBoxDebug(s"$message: Box was Empty."))
      box
    }

    def traceLogEmptyBox(message: String): Box[T] = {
      doLog(message, logBoxTrace, ()=>logBoxTrace(s"$message: Box was Empty."))
      box
    }

    def logFailure(message: String): Box[T] = {
      doLog(message, logBoxError, ()=>())
      box
    }

    def warnLogFailure(message: String): Box[T] = {
      doLog(message, logBoxWarn, ()=>())
      box
    }

    def infoLogFailure(message: String): Box[T] = {
      doLog(message, logBoxInfo, ()=>())
      box
    }

    def debugLogFailure(message: String): Box[T] = {
      doLog(message, logBoxDebug, ()=>())
      box
    }

    def traceLogFailure(message: String): Box[T] = {
      doLog(message, logBoxTrace, ()=>())
      box
    }
  }
}

/**
 * A version of `[[BoxLogging]]` with a default implementation of
 * `logBoxError` that logs to the logger provided by Lift's
 * `[[Loggable]]`.
 */
trait LoggableBoxLogging extends BoxLogging with Loggable {
  protected def logBoxError(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.error(message, throwable)
      case _ =>
        logger.error(message)
    }
  }
  protected def logBoxWarn(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.warn(message, throwable)
      case _ =>
        logger.warn(message)
    }
  }
  protected def logBoxInfo(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.info(message, throwable)
      case _ =>
        logger.info(message)
    }
  }
  protected def logBoxDebug(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.debug(message, throwable)
      case _ =>
        logger.debug(message)
    }
  }
  protected def logBoxTrace(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        // Below, disambiguate between the trace helper for tracing a value and
        // the one that takes a throwable.
        logger.trace(message: AnyRef, throwable)
      case _ =>
        logger.trace(message)
    }
  }
}

trait SLF4JBoxLogging extends BoxLogging {
  protected val logger: org.slf4j.Logger

  protected def logBoxError(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.error(message, throwable)
      case _ =>
        logger.error(message)
    }
  }
  protected def logBoxWarn(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.warn(message, throwable)
      case _ =>
        logger.warn(message)
    }
  }
  protected def logBoxInfo(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.info(message, throwable)
      case _ =>
        logger.info(message)
    }
  }
  protected def logBoxDebug(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.debug(message, throwable)
      case _ =>
        logger.debug(message)
    }
  }
  protected def logBoxTrace(message: String, throwable: Option[Throwable]) = {
    throwable match {
      case Some(throwable) =>
        logger.trace(message, throwable)
      case _ =>
        logger.trace(message)
    }
  }
}

/**
 * A convenience singleton for `[[BoxLogging]]` that makes `logFailure`
 * and `logFailure` available for all code after it's been imported
 * as `import com.elemica.common.BoxLogging._`. Logging done this way
 * will come from `BoxLogging`, not from the class where the box was
 * logged.
 */
object BoxLogging extends LoggableBoxLogging




© 2015 - 2025 Weber Informatics LLC | Privacy Policy