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

com.persist.logging.FileAppender.scala Maven / Gradle / Ivy

The newest version!
package com.persist.logging

import java.io.{PrintWriter, BufferedOutputStream, FileOutputStream, File}
import akka.actor._
import com.persist.JsonOps._
import scala.concurrent.duration._
import scala.concurrent.{Promise, ExecutionContext, Future}
import scala.language.postfixOps

private[logging] object FileAppenderActor {

  trait AppendMessages

  case class AppendAdd(data: String, line: String) extends AppendMessages

  case class AppendClose(p: Promise[Unit]) extends AppendMessages

  case object AppendFlush extends AppendMessages

  def props(path: String, category: String) = Props(new FileAppenderActor(path, category))
}

private[logging] class FileAppenderActor(path: String, category: String)
  extends Actor with ActorLogging {

  import FileAppenderActor._

  private[this] val system = context.system
  private[this] implicit val ec: ExecutionContext = context.dispatcher
  private[this] var lastDate: String = ""
  private[this] var optw: Option[PrintWriter] = None


  private[this] var flushTimer: Option[Cancellable] = None


  private def scheduleFlush() {
    val time = system.scheduler.scheduleOnce(2 seconds) {
      self ! AppendFlush
    }
    flushTimer = Some(time)
  }

  scheduleFlush()

  private def open(date: String) {
    val dir = s"$path"
    new File(dir).mkdirs()
    val fname = s"$dir/$category.$date.log"
    val w = new PrintWriter(new BufferedOutputStream(new FileOutputStream(fname, true)))
    optw = Some(w)
    lastDate = date
  }

  def receive = {
    case AppendAdd(date, line) =>
      optw match {
        case Some(w) =>
          if (date != lastDate) {
            w.close()
            open(date)
          }
        case None =>
          open(date)
      }
      optw match {
        case Some(w) =>
          w.println(line)
        case None =>
      }
    case AppendFlush =>
      optw match {
        case Some(w) =>
          w.flush()
        case None =>
      }
      scheduleFlush()

    case AppendClose(p) =>
      flushTimer match {
        case Some(t) => t.cancel()
        case None =>
      }
      optw match {
        case Some(w) =>
          w.close()
          optw = None
        case None =>
      }
      p.success(())
      context.stop(self)
    case x: Any => log.warn(s"Bad appender message: $x")(() => DefaultSourceLocation)
  }

  override def postStop() {
    optw match {
      case Some(w) =>
        w.close()
        optw = None
      case None =>
    }
  }
}

private[logging] case class FilesAppender(actorRefFactory: ActorRefFactory,
                                          path: String, category: String) {

  import FileAppenderActor._

  private[this] val fileAppenderActor = actorRefFactory.actorOf(
    FileAppenderActor.props(path, category),
    name = s"FileAppender.$category")

  def add(date: String, line: String) {
    fileAppenderActor ! AppendAdd(date, line)
  }

  def close(): Future[Unit] = {
    val p = Promise[Unit]()
    fileAppenderActor ! AppendClose(p)
    p.future
  }
}

/**
 * Companion object for FileAppender class.
 */
object FileAppender extends LogAppenderBuilder {
  /**
   * Constructor for a file appender.
   * @param factory an Akka factory.
   * @param stdHeaders the headers that are fixes for this service.
   * @return
   */
  def apply(factory: ActorRefFactory, stdHeaders: Map[String, RichMsg]) = new FileAppender(factory, stdHeaders)
}

/**
 * An appender that writes log messages to files.
 * @param factory
 * @param stdHeaders the headers that are fixes for this service.
 */
class FileAppender(factory: ActorRefFactory, stdHeaders: Map[String, RichMsg]) extends LogAppender {
  private[this] val system = factory match {
    case context: ActorContext => context.system
    case s: ActorSystem => s
  }
  private[this] implicit val executionContext = factory.dispatcher
  private[this] val config = system.settings.config.getConfig("com.persist.logging.appenders.file")
  private[this] val fullHeaders = config.getBoolean("fullHeaders")
  private[this] val logPath = config.getString("logPath")
  private[this] val sort = config.getBoolean("sorted")
  private[this] val serviceInPath = config.getBoolean("serviceInPath")
  private[this] val fileAppenders = scala.collection.mutable.HashMap[String, FilesAppender]()
  private[this] val fullPath: String = if (serviceInPath) {
    logPath + "/" + jgetString(stdHeaders, "@service", "name")
  } else {
    logPath
  }

  /**
   * Write the log message to a file.
   * @param baseMsg the message to be logged.
   * @param category  the kinds of log (for example, "common").
   */
  def append(baseMsg: Map[String, RichMsg], category: String): Unit = {
    val msg = if (fullHeaders) stdHeaders ++ baseMsg else baseMsg
    val fa = fileAppenders.get(category) match {
      case Some(a) => a
      case None =>
        val a = FilesAppender(factory, fullPath, category)
        fileAppenders += (category -> a)
        a
    }
    val date = jgetString(msg, "@timestamp").substring(0, 10)
    fa.add(date, Compact(msg, safe = true, sort = sort))
  }

  /**
    * Called just before the logger shuts down.
    * @return a future that is completed when finished.
    */
  def finish(): Future[Unit] = {
    Future.successful(())
  }

  /**
   * Closes the file appender.
   * @return a future that is completed when the close is complete.
   */
  def stop(): Future[Unit] = {
    val fs = for ((category, appender) <- fileAppenders) yield {
      appender.close()
    }
    Future.sequence(fs).map(s => ())
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy