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:
parent
0c9ad2f791
commit
280e1aa21a
3 changed files with 226 additions and 0 deletions
17
akka-contrib/docs/jul.rst
Normal file
17
akka-contrib/docs/jul.rst
Normal 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.
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue