java.util.logging backend for ActorLogging system, see #2805

[RK: assembled commit log from original branch, squashed]
akka-contrib:
- an event handler which properly sets the ThreadID on log records
- a LoggingAdapter which does synchronous logging to j.u.l.Logger
- a small logging mixin for the aforementioned adapter
This commit is contained in:
Sam Halliday 2012-12-01 23:38:48 +00:00 committed by Roland
parent 0c9ad2f791
commit 280e1aa21a
3 changed files with 226 additions and 0 deletions

17
akka-contrib/docs/jul.rst Normal file
View file

@ -0,0 +1,17 @@
Java Logging (JUL)
=================
This extension module provides a logging backend which uses the `java.util.logging` (j.u.l)
API to do the endpoint logging for `akka.event.Logging`.
Provided with this module is an implementation of `akka.event.LoggingAdapter` which is independent of any `ActorSystem` being in place. This means that j.u.l can be used as the backend, via the Akka Logging API, for both Actor and non-Actor codebases.
To enable j.u.l as the `akka.event.Logging` backend, use the following Akka config:
event-handlers = ["akka.contrib.jul.JavaLoggingEventHandler"]
To access the `akka.event.Logging` API from non-Actor code, mix in `akka.contrib.jul.JavaLogging`.
This module is preferred over SLF4J with its JDK14 backend, due to integration issues resulting in the incorrect handling of `threadId`, `className` and `methodName`.
This extension module was contributed by Sam Halliday.

View file

@ -0,0 +1,133 @@
package akka.contrib.jul
import akka.event.Logging._
import akka.actor._
import akka.event.LoggingAdapter
import java.util.logging
import concurrent.{ ExecutionContext, Future }
/**
* Makes the Akka `Logging` API available as the `log`
* field, using `java.util.logging` as the backend.
*
* This trait does not require an `ActorSystem` and is
* encouraged to be used as a general purpose Scala
* logging API.
*
* For `Actor`s, use `ActorLogging` instead.
*/
trait JavaLogging {
@transient
protected lazy val log = new JavaLoggingAdapter {
def logger = logging.Logger.getLogger(JavaLogging.this.getClass.getName)
}
}
/**
* `java.util.logging` EventHandler.
*/
class JavaLoggingEventHandler extends Actor {
def receive = {
case event @ Error(cause, logSource, logClass, message)
log(logging.Level.SEVERE, cause, logSource, logClass, message, event)
case event @ Warning(logSource, logClass, message)
log(logging.Level.WARNING, null, logSource, logClass, message, event)
case event @ Info(logSource, logClass, message)
log(logging.Level.INFO, null, logSource, logClass, message, event)
case event @ Debug(logSource, logClass, message)
log(logging.Level.CONFIG, null, logSource, logClass, message, event)
case InitializeLogger(_)
sender ! LoggerInitialized
}
@inline
def log(level: logging.Level, cause: Throwable, logSource: String, logClass: Class[_], message: Any, event: LogEvent) {
val logger = logging.Logger.getLogger(logSource)
val record = new logging.LogRecord(level, message.toString)
record.setLoggerName(logger.getName)
record.setThrown(cause)
record.setThreadID(event.thread.getId.toInt)
record.setSourceClassName(logClass.getName)
record.setSourceMethodName(null) // lost forever
logger.log(record)
}
}
trait JavaLoggingAdapter extends LoggingAdapter {
def logger: logging.Logger
/** Override-able option for asynchronous logging */
def loggingExecutionContext: Option[ExecutionContext] = None
def isErrorEnabled = logger.isLoggable(logging.Level.SEVERE)
def isWarningEnabled = logger.isLoggable(logging.Level.WARNING)
def isInfoEnabled = logger.isLoggable(logging.Level.INFO)
def isDebugEnabled = logger.isLoggable(logging.Level.CONFIG)
protected def notifyError(message: String) {
log(logging.Level.SEVERE, null, message)
}
protected def notifyError(cause: Throwable, message: String) {
log(logging.Level.SEVERE, cause, message)
}
protected def notifyWarning(message: String) {
log(logging.Level.WARNING, null, message)
}
protected def notifyInfo(message: String) {
log(logging.Level.INFO, null, message)
}
protected def notifyDebug(message: String) {
log(logging.Level.CONFIG, null, message)
}
@inline
def log(level: logging.Level, cause: Throwable, message: String) {
val record = new logging.LogRecord(level, message)
record.setLoggerName(logger.getName)
record.setThrown(cause)
updateSource(record)
if (loggingExecutionContext.isDefined) {
implicit val context = loggingExecutionContext.get
Future(logger.log(record)).onFailure {
case thrown: Throwable thrown.printStackTrace()
}
} else
logger.log(record)
}
// it is unfortunate that this workaround is needed
private def updateSource(record: logging.LogRecord) {
val stack = Thread.currentThread.getStackTrace
val source = stack.find {
frame
val cname = frame.getClassName
!cname.startsWith("akka.contrib.jul.") &&
!cname.startsWith("akka.event.LoggingAdapter") &&
!cname.startsWith("java.lang.reflect.") &&
!cname.startsWith("sun.reflect.")
}
if (source.isDefined) {
record.setSourceClassName(source.get.getClassName)
record.setSourceMethodName(source.get.getMethodName)
} else {
record.setSourceClassName(null)
record.setSourceMethodName(null)
}
}
}

View file

@ -0,0 +1,76 @@
package akka.contrib.jul
import com.typesafe.config.ConfigFactory
import akka.actor.{ ActorSystem, Actor, ActorLogging, Props }
import akka.testkit.AkkaSpec
import java.util.logging
import java.io.ByteArrayInputStream
object JavaLoggingEventHandlerSpec {
val config = ConfigFactory.parseString("""
akka {
loglevel = INFO
event-handlers = ["akka.contrib.jul.JavaLoggingEventHandler"]
}""")
class LogProducer extends Actor with ActorLogging {
def receive = {
case e: Exception
log.error(e, e.getMessage)
case (s: String, x: Int)
log.info(s, x)
}
}
}
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class JavaLoggingEventHandlerSpec extends AkkaSpec(JavaLoggingEventHandlerSpec.config) {
val logger = logging.Logger.getLogger("akka://JavaLoggingEventHandlerSpec/user/log")
logger.setUseParentHandlers(false) // turn off output of test LogRecords
logger.addHandler(new logging.Handler {
def publish(record: logging.LogRecord) {
testActor ! record
}
def flush() {}
def close() {}
})
val producer = system.actorOf(Props[JavaLoggingEventHandlerSpec.LogProducer], name = "log")
"JavaLoggingEventHandler" must {
"log error with stackTrace" in {
producer ! new RuntimeException("Simulated error")
val record = expectMsgType[logging.LogRecord]
record must not be (null)
record.getMillis must not be (0)
record.getThreadID must not be (0)
record.getLevel must be(logging.Level.SEVERE)
record.getMessage must be("Simulated error")
record.getThrown.isInstanceOf[RuntimeException] must be(true)
record.getSourceClassName must be("akka.contrib.jul.JavaLoggingEventHandlerSpec$LogProducer")
record.getSourceMethodName must be(null)
}
"log info without stackTrace" in {
producer ! ("{} is the magic number", 3)
val record = expectMsgType[logging.LogRecord]
record must not be (null)
record.getMillis must not be (0)
record.getThreadID must not be (0)
record.getLevel must be(logging.Level.INFO)
record.getMessage must be("3 is the magic number")
record.getThrown must be(null)
record.getSourceClassName must be("akka.contrib.jul.JavaLoggingEventHandlerSpec$LogProducer")
record.getSourceMethodName must be(null)
}
}
}