aboutsummaryrefslogblamecommitdiff
path: root/core-reporting/src/main/scala/xyz/driver/core/reporting/Reporter.scala
blob: 469084ca92c3abc2f65bae27aad465fb5a9ac9a9 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12

                       









                                                                                                                     
                                                                                                                      





























                                                                                                                        

                                                                                                                         





                                                                                                                
                   













                                                                                             
                                                                                                        











                                                                                        

                                                                                   





















                                                                                                                      




                                                                                                                     












                                                                                                                 
                          


                                                       






                                                                                                                   


                                                                                                          
                             

                                                                                                        
                                              

                                      

                                                                                                               
                                                      

                               

                                                                                                         
                                                
 
                              

                                                                                                        
                                              




                 














                                                                                                                                               







                                              
 
package xyz.driver.core
package reporting

import scala.concurrent.Future

/** Context-aware diagnostic utility for distributed systems, combining logging and tracing.
  *
  * Diagnostic messages (i.e. logs) are a vital tool for monitoring applications. Tying such messages to an
  * execution context, such as a stack trace, simplifies debugging greatly by giving insight to the chains of events
  * that led to a particular message. In synchronous systems, execution contexts can easily be determined by an
  * external observer, and, as such, do not need to be propagated explicitly to sub-components (e.g. a stack trace on
  * the JVM shows all relevant information). In asynchronous systems and especially distributed systems however,
  * execution contexts are not easily determined by an external observer and hence need to be explicitly passed across
  * service boundaries.
  *
  * This reporter provides tracing and logging utilities that explicitly require references to execution contexts
  * (called [[SpanContext]]s here) intended to be passed across service boundaries. It embraces Scala's
  * implicit-parameter-as-a-context paradigm.
  *
  * Tracing is intended to be compatible with the
  * [[https://github.com/opentracing/specification/blob/master/specification.md OpenTrace specification]], and hence its
  * guidelines on naming and tagging apply to methods provided by this Reporter as well.
  *
  * Usage example:
  * {{{
  * val reporter: Reporter = ???
  * object Repo {
  *   def getUserQuery(userId: String)(implicit ctx: SpanContext) = reporter.trace("query"){ implicit ctx =>
  *     reporter.debug("Running query")
  *     // run query
  *   }
  * }
  * object Service {
  *   def getUser(userId: String)(implicit ctx: SpanContext) = reporter.trace("get_user"){ implicit ctx =>
  *     reporter.debug("Getting user")
  *     Repo.getUserQuery(userId)
  *   }
  * }
  * reporter.traceRoot("static_get", Map("user" -> "john")) { implicit ctx =>
  *   Service.getUser("john")
  * }
  * }}}
  *
  * '''Note that computing traces may be a more expensive operation than traditional logging frameworks provide (in terms
  * of memory and processing). It should be used in interesting and actionable code paths.'''
  *
  * @define rootWarning Note: the idea of the reporting framework is to pass along references to traces as
  *                     implicit parameters. This method should only be used for top-level traces when no parent
  *                     traces are available.
  */
trait Reporter {
  import Reporter._

  def traceWithOptionalParent[A](
      name: String,
      tags: Map[String, String],
      parent: Option[(SpanContext, CausalRelation)])(op: SpanContext => A): A
  def traceWithOptionalParentAsync[A](
      name: String,
      tags: Map[String, String],
      parent: Option[(SpanContext, CausalRelation)])(op: SpanContext => Future[A]): Future[A]

  /** Trace the execution of an operation, if no parent trace is available.
    *
    * $rootWarning
    */
  final def traceRoot[A](name: String, tags: Map[String, String] = Map.empty)(op: SpanContext => A): A =
    traceWithOptionalParent(
      name,
      tags,
      None
    )(op)

  /** Trace the execution of an asynchronous operation, if no parent trace is available.
    *
    * $rootWarning
    *
    * @see traceRoot
    */
  final def traceRootAsync[A](name: String, tags: Map[String, String] = Map.empty)(
      op: SpanContext => Future[A]): Future[A] =
    traceWithOptionalParentAsync(
      name,
      tags,
      None
    )(op)

  /** Trace the execution of an operation, in relation to a parent context.
    *
    * @param name The name of the operation. Note that this name should not be too specific. According to the
    *             OpenTrace RFC: "An operation name, a human-readable string which concisely represents the work done
    *             by the Span (for example, an RPC method name, a function name, or the name of a subtask or stage
    *             within a larger computation). The operation name should be the most general string that identifies a
    *             (statistically) interesting class of Span instances. That is, `"get_user"` is better than
    *             `"get_user/314159"`".
    * @param tags Attributes associated with the traced event. Following the above example, if `"get_user"` is an
    *             operation name, a good tag would be `("account_id" -> 314159)`.
    * @param relation Relation of the operation to its parent context.
    * @param op The operation to be traced. The trace will complete once the operation returns.
    * @param ctx Context of the parent trace.
    * @tparam A Return type of the operation.
    * @return The value of the child operation.
    */
  final def trace[A](
      name: String,
      tags: Map[String, String] = Map.empty,
      relation: CausalRelation = CausalRelation.Child)(op: /* implicit (gotta wait for Scala 3) */ SpanContext => A)(
      implicit ctx: SpanContext): A =
    traceWithOptionalParent(
      name,
      tags,
      Some(ctx -> relation)
    )(op)

  /** Trace the operation of an asynchronous operation.
    *
    * Contrary to the synchronous version of this method, a trace is completed once the child operation completes
    * (rather than returns).
    *
    * @see trace
    */
  final def traceAsync[A](
      name: String,
      tags: Map[String, String] = Map.empty,
      relation: CausalRelation = CausalRelation.Child)(
      op: /* implicit (gotta wait for Scala 3) */ SpanContext => Future[A])(implicit ctx: SpanContext): Future[A] =
    traceWithOptionalParentAsync(
      name,
      tags,
      Some(ctx -> relation)
    )(op)

  /** Log a message. */
  def log(severity: Severity, message: String, reason: Option[Throwable])(implicit ctx: SpanContext): Unit

  /** Log a debug message. */
  final def debug(message: String)(implicit ctx: SpanContext): Unit = log(Severity.Debug, message, None)
  final def debug(message: String, reason: Throwable)(implicit ctx: SpanContext): Unit =
    log(Severity.Debug, message, Some(reason))

  /** Log an informational message. */
  final def info(message: String)(implicit ctx: SpanContext): Unit = log(Severity.Informational, message, None)
  final def info(message: String, reason: Throwable)(implicit ctx: SpanContext): Unit =
    log(Severity.Informational, message, Some(reason))

  /** Log a warning message. */
  final def warn(message: String)(implicit ctx: SpanContext): Unit = log(Severity.Warning, message, None)
  final def warn(message: String, reason: Throwable)(implicit ctx: SpanContext): Unit =
    log(Severity.Warning, message, Some(reason))

  /** Log an error message. */
  final def error(message: String)(implicit ctx: SpanContext): Unit = log(Severity.Error, message, None)
  final def error(message: String, reason: Throwable)(implicit ctx: SpanContext): Unit =
    log(Severity.Error, message, Some(reason))

}

object Reporter {

  /** A relation in cause.
    *
    * Corresponds to
    * [[https://github.com/opentracing/specification/blob/master/specification.md#references-between-spans OpenTrace references between spans]]
    */
  sealed trait CausalRelation
  object CausalRelation {

    /** One event is the child of another. The parent completes once the child is complete. */
    case object Child extends CausalRelation

    /** One event follows from another, not necessarily being the parent. */
    case object Follows extends CausalRelation
  }

  sealed trait Severity
  object Severity {
    case object Debug         extends Severity
    case object Informational extends Severity
    case object Warning       extends Severity
    case object Error         extends Severity
  }

}