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

io.policarp.logback.SplunkHecJsonLayout.scala Maven / Gradle / Ivy

package io.policarp.logback

import ch.qos.logback.classic.pattern._
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.LayoutBase
import io.policarp.logback.json.{ BaseJson, FullEventJson }
import org.json4s.native.Serialization._

import scala.beans.BeanProperty
import scala.collection._

/**
 * Base Logback LayoutBase class used for formatting log events in JSON for Splunk. This supplies
 * the expected JSON format referenced here: http://dev.splunk.com/view/event-collector/SP-CAAAE6M
 */
trait SplunkHecJsonLayoutBase extends LayoutBase[ILoggingEvent] {

  @BeanProperty var host: String = ""
  @BeanProperty var source: String = ""
  @BeanProperty var sourcetype: String = ""
  @BeanProperty var index: String = ""
}

package object json {

  trait EventJson

  case class BaseJson(
    time: Long,
    event: EventJson,
    host: Option[String] = None,
    source: Option[String] = None,
    sourcetype: Option[String] = None,
    index: Option[String] = None
  )

  case class FullEventJson(
    message: String,
    level: String,
    thread: String,
    logger: String,
    callingClass: Option[String] = None,
    callingMethod: Option[String] = None,
    callingLine: Option[String] = None,
    callingFile: Option[String] = None,
    exception: Option[String] = None,
    stacktrace: Option[List[String]] = None,
    customFields: Option[mutable.HashMap[String, String]] = None
  ) extends EventJson
}

object SplunkHecJsonLayout {

  // ref: ch.qos.logback.classic.PatternLayout
  lazy val classOfCallerConverter = new ClassOfCallerConverter
  lazy val methodOfCallerConverter = new MethodOfCallerConverter
  lazy val lineOfCallerConverter = new LineOfCallerConverter
  lazy val fileOfCallerConverter = new FileOfCallerConverter
  lazy val extendedThrowableProxyConverter = new ExtendedThrowableProxyConverter

  implicit class FilterEmpty(s: String) {
    def filterEmptyConversion = if (s.isEmpty || s == "?") None else Some(s)
  }

  def parseStackTrace(event: ILoggingEvent, max: Int): Option[List[String]] = {
    Option(event.getThrowableProxy).flatMap(proxy => {
      Option(proxy.getStackTraceElementProxyArray).map(stacktrace => {
        val length = stacktrace.length
        val list = stacktrace.iterator.take(max).map(trace =>
          trace.getStackTraceElement.toString).toList
        list ++ (if (length > max) List("...") else Nil)
      })
    })
  }
}

/**
 * This layout provides a 'good' amount of logging data from an ILoggingEvent. It also supports
 * custom fields to append to every log message configurable via Logback.
 */
case class SplunkHecJsonLayout() extends SplunkHecJsonLayoutBase {

  import SplunkHecJsonLayout._

  @BeanProperty var maxStackTrace: Int = 500

  private val customFields = new mutable.HashMap[String, String]()

  def setCustom(customField: String): Unit = {
    customField.split("=", 2) match {
      case Array(key, value) => customFields += (key.trim -> value.trim)
      case _ => // ignoring anything else
    }
  }

  override def doLayout(event: ILoggingEvent) = {

    implicit val format = org.json4s.DefaultFormats

    val eventJson = FullEventJson(
      event.getFormattedMessage,
      event.getLevel.levelStr,
      event.getThreadName,
      event.getLoggerName,
      classOfCallerConverter.convert(event).filterEmptyConversion,
      methodOfCallerConverter.convert(event).filterEmptyConversion,
      lineOfCallerConverter.convert(event).filterEmptyConversion,
      fileOfCallerConverter.convert(event).filterEmptyConversion,
      extendedThrowableProxyConverter.convert(event).filterEmptyConversion,
      parseStackTrace(event, maxStackTrace),
      if (customFields.isEmpty) None else Some(customFields)
    )

    val baseJson = BaseJson(
      event.getTimeStamp,
      eventJson,
      if (host.isEmpty) None else Some(host),
      if (source.isEmpty) None else Some(source),
      if (sourcetype.isEmpty) None else Some(sourcetype),
      if (index.isEmpty) None else Some(index)
    )

    write(baseJson)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy