From 5f5a8af078cf34b267876a3a8feafc8ccdf4310e Mon Sep 17 00:00:00 2001
From: Abhishek Sharma <abhioncbr@yahoo.com>
Date: Sun, 28 Sep 2014 10:34:10 +0530
Subject: [PATCH] Initial Code For Log4j Binary Appender

---
 .../scala/kafka/utils/Log4jBinaryAppender.scala    |  327 ++++++++++++++++++++
 1 file changed, 327 insertions(+)
 create mode 100644 core/src/main/scala/kafka/utils/Log4jBinaryAppender.scala

diff --git a/core/src/main/scala/kafka/utils/Log4jBinaryAppender.scala b/core/src/main/scala/kafka/utils/Log4jBinaryAppender.scala
new file mode 100644
index 0000000..837e9f2
--- /dev/null
+++ b/core/src/main/scala/kafka/utils/Log4jBinaryAppender.scala
@@ -0,0 +1,327 @@
+package kafka.utils
+
+import java.io.DataOutputStream
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.GregorianCalendar
+import java.util.Locale
+import java.util.TimeZone
+import org.apache.log4j.AppenderSkeleton
+import org.apache.log4j.Layout
+import org.apache.log4j.helpers.LogLog
+import org.apache.log4j.spi.ErrorCode
+import org.apache.log4j.spi.LoggingEvent
+import Log4jBinaryAppender._
+
+object Log4jBinaryAppender {
+
+  private val gmtTimeZone = TimeZone.getTimeZone("GMT")
+}
+
+class Log4jBinaryAppender extends AppenderSkeleton with Logging {
+
+  private var scheduledFilename: String = null
+
+  private var sdf: SimpleDateFormat = null
+
+  private var now: Date = new Date()
+
+  private var rc: RollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault)
+
+  private var nextCheck: Long = System.currentTimeMillis() - 1
+
+  private var fileAppend: Boolean = true
+
+  private var fileName: String = null
+
+  private var dostream: DataOutputStream = null
+
+  private var datePattern: String = "'.'yyyy-MM-dd"
+
+  def setDatePattern(datePattern: String) {
+    this.datePattern = datePattern
+  }
+
+  def setFile(fileName: String) {
+    if (fileName != null) {
+      val value = fileName.trim()
+      this.fileName = value
+    }
+  }
+
+  private def write(string: String) {
+    try {
+      this.dostream.writeUTF(string)
+    } catch {
+      case e: IOException => errorHandler.error("Failed to write [" + string + "].", e, ErrorCode.WRITE_FAILURE)
+    }
+  }
+
+  private def reset() {
+    closeStream()
+    dostream = null
+  }
+
+  private def closeStream() {
+    if (dostream != null) {
+      try {
+        dostream.close()
+      } catch {
+        case e: IOException => error("Could not close " + dostream, e)
+      }
+    }
+  }
+
+  private def checkEntryConditions(): Boolean = {
+    if (this.closed) {
+      warn("Not allowed to write to a closed appender.")
+      return false
+    }
+    if (this.dostream == null) {
+      errorHandler.error("No output stream or file set for the appender named [" + name + "].")
+      return false
+    }
+    if (this.layout == null) {
+      errorHandler.error("No layout set for the appender named [" + name + "].")
+      return false
+    }
+    true
+  }
+
+  private def rollOver() {
+    if (datePattern == null) {
+      errorHandler.error("Missing DatePattern option in rollOver().")
+      return
+    }
+    val datedFilename = fileName + sdf.format(now)
+    if (scheduledFilename == datedFilename) {
+      return
+    }
+    this.closeStream()
+    val target = new File(scheduledFilename)
+    if (target.exists()) {
+      target.delete()
+    }
+    val file = new File(fileName)
+    val result = file.renameTo(target)
+    if (result) {
+      debug(fileName + " -> " + scheduledFilename)
+    } else {
+      error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].")
+    }
+    try {
+      this.setFile(fileName, true)
+    } catch {
+      case e: IOException => errorHandler.error("setFile(" + fileName + ", true) call failed.")
+    }
+    scheduledFilename = datedFilename
+  }
+
+  private def subAppend(event: LoggingEvent) {
+    val n = System.currentTimeMillis()
+    if (n >= nextCheck) {
+      now.setTime(n)
+      nextCheck = rc.getNextCheckMillis(now)
+      try {
+        rollOver()
+      } catch {
+        case ioe: IOException => {
+          if (ioe.isInstanceOf[InterruptedIOException]) {
+            Thread.currentThread().interrupt()
+          }
+          error("rollOver() failed.", ioe)
+        }
+      }
+    }
+    write(this.layout.format(event))
+    if (layout.ignoresThrowable()) {
+      val s = event.getThrowableStrRep
+      if (s != null) {
+        val len = s.length
+        for (i <- 0 until len) {
+          write(s(i))
+          write(Layout.LINE_SEP)
+        }
+      }
+    }
+  }
+
+  def close() {
+    synchronized {
+      if (this.closed) {
+        return
+      }
+      this.closed = true
+      reset()
+    }
+  }
+
+  def requiresLayout(): Boolean = true
+
+  protected override def append(event: LoggingEvent) {
+    synchronized {
+      if (!checkEntryConditions()) {
+        return
+      }
+      subAppend(event)
+    }
+  }
+
+  override def activateOptions() {
+    if (datePattern != null && fileName != null) {
+      try {
+        setFile(this.fileName, this.fileAppend)
+        now.setTime(System.currentTimeMillis())
+        sdf = new SimpleDateFormat(datePattern)
+        val rollingConstant = computeCheckPeriod()
+        rc.setType(rollingConstant)
+        val file = new File(fileName)
+        scheduledFilename = fileName + sdf.format(new Date(file.lastModified()))
+      } catch {
+        case e: java.io.IOException => errorHandler.error("setFile(" + this.fileName + "," + this.fileAppend + ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE)
+      }
+    } else {
+      warn("File option not set for appender [" + name + "].")
+      warn("Are you using FileAppender instead of ConsoleAppender?")
+    }
+  }
+
+  private def computeCheckPeriod(): java.lang.Integer = {
+    val rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault)
+    val epoch = new Date(0)
+    if (datePattern != null) {
+      var i = RollingConstant.TOP_OF_MINUTE
+      while (i <= RollingConstant.TOP_OF_MONTH) {
+        val simpleDateFormat = new SimpleDateFormat(datePattern)
+        simpleDateFormat.setTimeZone(gmtTimeZone)
+        val r0 = simpleDateFormat.format(epoch)
+        rollingCalendar.setType(i)
+        val next = new Date(rollingCalendar.getNextCheckMillis(epoch))
+        val r1 = simpleDateFormat.format(next)
+        if (r0 != null && r1 != null && r0 != r1) {
+          return i
+        }
+        i += 1
+      }
+    }
+    RollingConstant.TOP_OF_TROUBLE
+  }
+
+  private def setFile(fileName: String, append: Boolean) {
+    synchronized {
+      debug("setFile called: " + fileName + ", " + append)
+      reset()
+      var fostream: FileOutputStream = null
+      try {
+        fostream = new FileOutputStream(fileName, append)
+        dostream = new DataOutputStream(fostream)
+      } catch {
+        case ex: FileNotFoundException => {
+          val parentName = new File(fileName).getParent
+          dostream = new DataOutputStream(fostream)
+          if (parentName != null) {
+            val parentDir = new File(parentName)
+            if (!parentDir.exists() && parentDir.mkdirs()) {
+              fostream = new FileOutputStream(fileName, append)
+              dostream = new DataOutputStream(fostream)
+            } else {
+              throw ex
+            }
+          } else {
+            throw ex
+          }
+        }
+      }
+      debug("setFile ended")
+    }
+  }
+}
+
+object RollingConstant {
+
+  val TOP_OF_TROUBLE = -1
+
+  val TOP_OF_MINUTE = 0
+
+  val TOP_OF_HOUR = 1
+
+  val HALF_DAY = 2
+
+  val TOP_OF_DAY = 3
+
+  val TOP_OF_WEEK = 4
+
+  val TOP_OF_MONTH = 5
+}
+
+@SerialVersionUID(-3560331770601814177L)
+class RollingCalendar(tz: TimeZone, locale: Locale) extends GregorianCalendar(tz, locale) {
+
+  var rollingConstant: Int = RollingConstant.TOP_OF_TROUBLE
+
+  def setType(rollingConstant: Int) {
+    this.rollingConstant = rollingConstant
+  }
+
+  def getNextCheckMillis(now: Date): Long = getNextCheckDate(now).getTime
+
+  def getNextCheckDate(now: Date): Date = {
+    this.setTime(now)
+    rollingConstant match {
+      case RollingConstant.TOP_OF_MINUTE =>
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        this.add(Calendar.MINUTE, 1)
+
+      case RollingConstant.TOP_OF_HOUR =>
+        this.set(Calendar.MINUTE, 0)
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        this.add(Calendar.HOUR_OF_DAY, 1)
+
+      case RollingConstant.HALF_DAY =>
+        this.set(Calendar.MINUTE, 0)
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        var hour = get(Calendar.HOUR_OF_DAY)
+        if (hour < 12) {
+          this.set(Calendar.HOUR_OF_DAY, 12)
+        } else {
+          this.set(Calendar.HOUR_OF_DAY, 0)
+          this.add(Calendar.DAY_OF_MONTH, 1)
+        }
+
+      case RollingConstant.TOP_OF_DAY =>
+        this.set(Calendar.HOUR_OF_DAY, 0)
+        this.set(Calendar.MINUTE, 0)
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        this.add(Calendar.DATE, 1)
+
+      case RollingConstant.TOP_OF_WEEK =>
+        this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek)
+        this.set(Calendar.HOUR_OF_DAY, 0)
+        this.set(Calendar.MINUTE, 0)
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        this.add(Calendar.WEEK_OF_YEAR, 1)
+
+      case RollingConstant.TOP_OF_MONTH =>
+        this.set(Calendar.DATE, 1)
+        this.set(Calendar.HOUR_OF_DAY, 0)
+        this.set(Calendar.MINUTE, 0)
+        this.set(Calendar.SECOND, 0)
+        this.set(Calendar.MILLISECOND, 0)
+        this.add(Calendar.MONTH, 1)
+
+      case default => throw new IllegalStateException("Unknown periodicity type.")
+    }
+    getTime
+  }
+}
\ No newline at end of file
-- 
1.7.9.5

