可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In Java, the standard idiom for logging is to create a static variable for a logger object and use that in the various methods.
In Scala, it looks like the idiom is to create a Logging trait with a logger member and mixin the trait in concrete classes. This means that each time an object is created it calls the logging framework to get a logger and also the object is bigger due to the additional reference.
Is there an alternative that allows the ease of use of "with Logging" while still using a per-class logger instance?
EDIT: My question is not about how one can write a logging framework in Scala, but rather how to use an existing one (log4j) without incurring an overhead of performance (getting a reference for each instance) or code complexity. Also, yes, I want to use log4j, simply because I'll use 3rd party libraries written in Java that are likely to use log4j.
回答1:
I'd just stick to the "with Logging" approach. Clean design wins every time - if you get the boilerplate out the way then chances are that you can find far more useful gains achievable in other areas.
Keep in mind that the logging framework will cache loggers, so you still have one per class, even if every instance of that class happens to hold a (inexpensive) reference.
Without proof that logger references are harming your heap, this smells a lot like premature optimization... Just relax and don't worry about it, unless a profiler tells you otherwise.
On an unrelated note, you might also want to look into using slf4j and logback instead of log4j. slf4j has a cleaner design that fits better with idiomatic scala.
回答2:
I used log4j with Scala by creating a trait and having the logger by per-instances not per-class. With some Scala magic and manifests, you might be able to change the logger to be static (internal object), but I'm not 100% sure. Personally, I agree with @KevinWright that making the logger static is a premature optimization.
Also note that the code below has the log messages as by-name, meaning that your logger calls don't need to be wrapped in `if (log.isDebugEnabled()); complex log messages created via string concatenation won't be evaluated unless the log level is appropriate. See this link for more info: http://www.naildrivin5.com/scalatour/wiki_pages/TypeDependentClosures
http://github.com/davetron5000/shorty/blob/master/src/main/scala/shorty/Logs.scala
package shorty
import org.apache.log4j._
trait Logs {
private[this] val logger = Logger.getLogger(getClass().getName());
import org.apache.log4j.Level._
def debug(message: => String) = if (logger.isEnabledFor(DEBUG)) logger.debug(message)
def debug(message: => String, ex:Throwable) = if (logger.isEnabledFor(DEBUG)) logger.debug(message,ex)
def debugValue[T](valueName: String, value: => T):T = {
val result:T = value
debug(valueName + " == " + result.toString)
result
}
def info(message: => String) = if (logger.isEnabledFor(INFO)) logger.info(message)
def info(message: => String, ex:Throwable) = if (logger.isEnabledFor(INFO)) logger.info(message,ex)
def warn(message: => String) = if (logger.isEnabledFor(WARN)) logger.warn(message)
def warn(message: => String, ex:Throwable) = if (logger.isEnabledFor(WARN)) logger.warn(message,ex)
def error(ex:Throwable) = if (logger.isEnabledFor(ERROR)) logger.error(ex.toString,ex)
def error(message: => String) = if (logger.isEnabledFor(ERROR)) logger.error(message)
def error(message: => String, ex:Throwable) = if (logger.isEnabledFor(ERROR)) logger.error(message,ex)
def fatal(ex:Throwable) = if (logger.isEnabledFor(FATAL)) logger.fatal(ex.toString,ex)
def fatal(message: => String) = if (logger.isEnabledFor(FATAL)) logger.fatal(message)
def fatal(message: => String, ex:Throwable) = if (logger.isEnabledFor(FATAL)) logger.fatal(message,ex)
}
class Foo extends SomeBaseClass with Logs {
def doit(s:Option[String]) = {
debug("Got param " + s)
s match {
case Some(string) => info(string)
case None => error("Expected something!")
}
}
}
回答3:
If you are truly concerned about space overhead and/or extra time in object initializers, a good strategy can be to have a logging trait that leaves the logger abstract as in
trait Logging {
def logger: Logger
def debug(message: String) { logger.debug(message) }
def warn(message: String) { logger.warn(message) }
}
For classes which need to be as lightweight as possible then you can do
object MustBeLightweight {
val logger = Logger.getLog(classOf[MustBeLightweight])
}
class MustBeLightWeight extends Logging {
final def logger = MustBeLightweight.logger
}
The JIT might even inline debug
warn
and logger
in this case.
You can also have a trait to mix in for classes where the overhead of an additional field is not a problem
trait PerInstanceLog {
val logger = Logger.getLog(this.getClass())
}
A further option is to leave logging out of the class and put it completely in an object as in
object Foo {
object log extends Logging {
override val logger = Logger.getLogger(classOf[Foo])
}
}
class Foo {
import Foo.log._
def someMethod() = { warn("xyz") }
}
I agree with Kevin though, don't add the complexity unless you need it.
回答4:
object Log {
def log(message: String) = {
.....
}
}
No?
回答5:
Sometimes logging at the package level is the right answer. Scala makes this easier than java because objects can be defined directly in a package. If you defined a Log like this:
package example
object Log extends au.com.langdale.util.PackageLogger
This Log is available everywhere in package example. To get more fine-grained logging, you can scatter similar definitions around the package hierarchy. Or you can defined all the package loggers together like this:
package example {
import au.com.langdale.util.PackageLogger
object Log extends PackageLogger
package frobber {
object Log extends PackageLogger
package undulater {
object Log extends PackageLogger
}
}
}
The PackageLogger class could be defined as follows (assuming SLF4J):
package au.com.langdale.util
import org.slf4j.LoggerFactory
class PackageLogger {
val name = { val c = getClass.getName; c.substring(0, c.lastIndexOf('.')) }
val inner = LoggerFactory.getLogger(name)
// various convenient logging methods follow....
def apply( mesg: => Any ) = inner.info(mesg.toString)
def info( mesg: String ) = inner.info(mesg)
def warn( mesg: String ) = inner.warn(mesg)
def error( mesg: String ) = inner.error(mesg)
}
回答6:
Here's a quick hack (which I haven't actually been using, honest ;@)
object LogLevel extends Enumeration {
val Error = Value(" ERROR ")
val Warning = Value(" WARNING ")
val Info = Value(" INFO ")
val Debug = Value(" DEBUG ")
}
trait Identity {
val id: String
}
trait Logging extends Identity {
import LogLevel._
abstract class LogWriter {
protected val writer: Actor
protected val tstampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ")
def tstamp = tstampFormat.format(new Date)
def log(id: String, logLevel: LogLevel.Value, msg: String) {
writer ! (tstamp + id + logLevel + msg)
}
}
object NullLogWriter extends LogWriter {
protected val writer = actor{loop {react{case msg: String =>}}}
}
object ConsoleWriter extends LogWriter {
protected val writer = actor{loop {react {case msg: String => Console.out.println(msg); Console.flush case _ =>}}}
}
class FileWriter(val file: File) extends LogWriter {
require(file != null)
require(file.canWrite)
protected val writer = actor{loop {react {case msg: String => destFile.println(msg); destFile.flush case _ =>}}}
private val destFile = {
try {new PrintStream(new FileOutputStream(file))}
catch {case e => ConsoleWriter.log("FileWriter", LogLevel.Error, "Unable to create FileWriter for file " + file +
" exception was: " + e); Console.out}
}
}
protected var logWriter: LogWriter = ConsoleWriter
protected var logLevel = Info
def setLoggingLevel(level: LogLevel.Value) {logLevel = level}
def setLogWriter(lw: LogWriter) {if (lw != null) logWriter = lw}
def logError(msg: => String) {if (logLevel <= Error) logWriter.log(id, Error, msg)}
def logWarning(msg: => String) {if (logLevel <= Warning) logWriter.log(id, Warning, msg)}
def logInfo(msg: => String) {if (logLevel <= Info) logWriter.log(id, Info, msg)}
def logDebug(msg: => String) {if (logLevel <= Debug) logWriter.log(id, Debug, msg)}
}
Hope it's of some use.
回答7:
The typesafe logging API (https://github.com/typesafehub/scalalogging) has a trait for adding a logger val to a class but its use is optional. It initializes the variable using getClass getName which half the time will be worthless because frequently your actual class name will be gobbledygook.
So if you don't want the trait adding the extra variable to each instance you certainly don't need to use it and can simply put the logger val in your companion object and do an import in the class of the companion objects members so you don't need to qualify it.
回答8:
One way is to extends the Logger to the companion object:
object A extends LoggerSupport
class A {
import A._
log("hi")
}
trait LoggerSupport{
val logger = LoggerFactory.getLogger(this.getClass)
def log(msg : String)= logger.log(msg)
}
//classes of the logging framework
trait Logger{
def log(msg : String) : Unit
}
object LoggerFactory{
def getLogger(classOfLogger : Class[_]) : Logger = ...
}
Alternatively you can cache the logger instances:
import collection.mutable.Map
object LoggerCache{
var cache : Map[Class[_], Logger] = Map()
def logger(c : Class[_]) = cache.getOrElseUpdate(c, LoggerFactory.getLogger(c))
}
trait LoggerSupport{
def log(msg : String) = LoggerCache.logger(this.getClass).log(msg)
}
class A extends LoggerSupport{
log("hi")
}
This is easier to use but will have worse performance. Performance will be really bad when you're go to discard most of the log messages (because of the log level configuration).