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

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

The newest version!
package io.policarp.logback

import java.util.concurrent.ConcurrentLinkedQueue

import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.AppenderBase
import io.policarp.logback.hec.SplunkHecClient
import io.policarp.logback.hec.skinnyhttp.{SkinnyHecClient, SkinnyHttpLogFilter}
import monix.eval.{Task, TaskCircuitBreaker}
import monix.execution.Scheduler
import monix.execution.atomic.AtomicLong
import monix.reactive.{Consumer, Observable}
import org.reactivestreams.{Publisher, Subscriber, Subscription}

import scala.beans.BeanProperty
import scala.concurrent.duration._

class SplunkHecAppender extends SplunkHecAppenderBase with SkinnyHecClient {
  this.addFilter(new SkinnyHttpLogFilter())
}

trait SplunkHecAppenderBase extends AppenderBase[ILoggingEvent] {
  self: SplunkHecClient =>

  @BeanProperty var buffer: Int = 25
  @BeanProperty var flush: Int = 30
  @BeanProperty var parallelism: Int = Runtime.getRuntime.availableProcessors()
  @BeanProperty var layout: SplunkHecJsonLayoutBase = SplunkHecJsonLayout()

  private implicit val scheduler = Scheduler.computation(parallelism)

  private implicit lazy val logPublisher = new LogPublisher()
  private lazy val logStream = Observable.fromReactivePublisher(logPublisher).bufferTimedAndCounted(flush seconds, buffer)
  private lazy val logConsumer = Consumer.foreachParallelAsync[Task[Unit]](parallelism)(task => task)

  override def start() = {
    super.start()
    implicit val impliedLayout = layout
    logStream.map(postTask).consumeWith(logConsumer).runAsync
  }

  override def append(event: ILoggingEvent) = logPublisher.enqueue(event)
}

private[logback] class LogPublisher()(implicit scheduler: Scheduler) extends Publisher[ILoggingEvent] {

  val logQueue = new ConcurrentLinkedQueue[ILoggingEvent]()

  private val LogPublisherCircuitBreaker = TaskCircuitBreaker (
    maxFailures = 5,
    resetTimeout = 10 nanos,
    exponentialBackoffFactor = 2,
    maxResetTimeout = 1 minute
  )

  def enqueue(event: ILoggingEvent) = logQueue.add(event)

  override def subscribe(subscriber: Subscriber[_ >: ILoggingEvent]) = {
    subscriber.onSubscribe(new Subscription {

      @volatile var cancelled = false

      override def cancel() = { cancelled = true }

      override def request(n: Long) = if (!cancelled) {
        val requests = AtomicLong(n)
        val pollingTask = Task {
          val currentRequests = requests.get
          for (_ <- 1L to currentRequests) {
            Option(logQueue.poll()) match {
              case None =>
                throw new IllegalStateException("logQueue is empty") // so trigger circuit breaker
              case Some(event) =>
                subscriber.onNext(event)
                requests.decrement()
            }
          }
        }
        LogPublisherCircuitBreaker.protect(pollingTask).onErrorRestartIf(_ => requests.get != 0).runAsync
      }
    })
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy