前言
本文隶属于专栏《1000个问题搞定大数据技术体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见
1000个问题搞定大数据技术体系
正文
下面的日志打印工具可以无脑应用于任何的 scala 工程中。
package com.shockang.study.spark.internal
import org.apache.log4j._
import org.slf4j.{Logger, LoggerFactory}
import org.slf4j.bridge.SLF4JBridgeHandler
import org.slf4j.impl.StaticLoggerBinder
/**
* 日志的工具特质。参考 Apache Spark 的源码实现: [[org.apache.spark.internal.Logging]]
*/
trait Logging {
// log 字段设置为 transient
// 这样实现了 Logging 接口的对象就可以序列化,并且可以在其他机器上使用。
@transient private var log_ : Logger = null
// 获得当前对象的日志名称
protected def logName = {
// 忽略 Scala 对象名称的 "$" 符号后面的内容
this.getClass.getName.stripSuffix("$")
}
// 得到或者创建当前对象的 logger
protected def log: Logger = {
if (log_ == null) {
initializeLogIfNecessary(false)
log_ = LoggerFactory.getLogger(logName)
}
log_
}
// =======================================================================
// 处理单个字符串的方法
// =======================================================================
protected def logInfo(msg: => String): Unit = {
if (log.isInfoEnabled) log.info(msg)
}
protected def logDebug(msg: => String): Unit = {
if (log.isDebugEnabled) log.debug(msg)
}
protected def logTrace(msg: => String): Unit = {
if (log.isTraceEnabled) log.trace(msg)
}
protected def logWarning(msg: => String): Unit = {
if (log.isWarnEnabled) log.warn(msg)
}
protected def logError(msg: => String): Unit = {
if (log.isErrorEnabled) log.error(msg)
}
// =======================================================================
// 处理字符串和 Throwable 的方法
// =======================================================================
protected def logInfo(msg: => String, throwable: Throwable): Unit = {
if (log.isInfoEnabled) log.info(msg, throwable)
}
protected def logDebug(msg: => String, throwable: Throwable): Unit = {
if (log.isDebugEnabled) log.debug(msg, throwable)
}
protected def logTrace(msg: => String, throwable: Throwable): Unit = {
if (log.isTraceEnabled) log.trace(msg, throwable)
}
protected def logWarning(msg: => String, throwable: Throwable): Unit = {
if (log.isWarnEnabled) log.warn(msg, throwable)
}
protected def logError(msg: => String, throwable: Throwable): Unit = {
if (log.isErrorEnabled) log.error(msg, throwable)
}
protected def isTraceEnabled(): Boolean = {
log.isTraceEnabled
}
// =======================================================================
// 日志对象的初始化
// =======================================================================
protected def initializeLogIfNecessary(isInterpreter: Boolean): Unit = {
initializeLogIfNecessary(isInterpreter, silent = false)
}
protected def initializeLogIfNecessary(
isInterpreter: Boolean,
silent: Boolean = false): Boolean = {
if (!Logging.initialized) {
Logging.initLock.synchronized {
if (!Logging.initialized) {
initializeLogging(isInterpreter, silent)
return true
}
}
}
false
}
// 测试使用
private[spark] def initializeForcefully(isInterpreter: Boolean, silent: Boolean): Unit = {
initializeLogging(isInterpreter, silent)
}
private def initializeLogging(isInterpreter: Boolean, silent: Boolean): Unit = {
// 如果 Log4j 1.2 使用了,但是没有初始化,加载默认的 log4j 配置文件。
if (Logging.isLog4j12()) {
val log4j12Initialized = LogManager.getRootLogger.getAllAppenders.hasMoreElements
// scalastyle:off println
if (!log4j12Initialized) {
Logging.defaultLog4jConfig = true
val defaultLogProps = "conf/log4j-defaults.properties"
Option(getClass.getClassLoader.getResource(defaultLogProps)) match {
case Some(url) =>
PropertyConfigurator.configure(url)
if (!silent) {
System.err.println(s"使用默认的 log4j 配置文件: $defaultLogProps")
}
case None =>
System.err.println(s"无法加载配置文件: $defaultLogProps")
}
}
val rootLogger = LogManager.getRootLogger()
if (Logging.defaultRootLevel == null) {
Logging.defaultRootLevel = rootLogger.getLevel()
}
// scalastyle:on println
}
Logging.initialized = true
// 强制调用 slf4j 来初始化,避免通过多线程来触发:
// http://mailman.qos.ch/pipermail/slf4j-dev/2010-April/002956.html
log
}
}
private[spark] object Logging {
@volatile private var initialized = false
@volatile private var defaultRootLevel: Level = null
@volatile private var defaultLog4jConfig = false
val initLock = new Object()
try {
// 如果用户为了使用 JUL 进行日志打印,移除了 slf4j 到 JUL 的桥接方法,
// 这里我们使用反射来处理这种场景。
// scalastyle:off classforname
val bridgeClass = Class.forName("org.slf4j.bridge.SLF4JBridgeHandler",
true, Thread.currentThread().getContextClassLoader)
.asInstanceOf[Class[SLF4JBridgeHandler]]
// scalastyle:on classforname
bridgeClass.getMethod("removeHandlersForRootLogger").invoke(null)
val installed = bridgeClass.getMethod("isInstalled").invoke(null).asInstanceOf[Boolean]
if (!installed) {
bridgeClass.getMethod("install").invoke(null)
}
} catch {
case e: ClassNotFoundException => // 无法进行日志打印所以直接静默失败
}
/**
* 将日志记录系统标记为未初始化。
* 这将尽最大努力将日志记录系统重置为其初始状态,以便下一个使用日志记录的类再次触发初始化。
*/
def uninitialize(): Unit = initLock.synchronized {
if (isLog4j12()) {
if (defaultLog4jConfig) {
defaultLog4jConfig = false
LogManager.resetConfiguration()
} else {
val rootLogger = LogManager.getRootLogger()
rootLogger.setLevel(defaultRootLevel)
}
}
this.initialized = false
}
private def isLog4j12(): Boolean = {
// 这里将区别绑定的是 log4j 1.2(使用的 org.slf4j.impl.Log4jLoggerFactory)
// 还是 log4j 2.0(使用的 org.apache.logging.slf4j.Log4jLoggerFactory)
val binderClass = StaticLoggerBinder.getSingleton.getLoggerFactoryClassStr
"org.slf4j.impl.Log4jLoggerFactory".equals(binderClass)
}
}
scalastyle 配置请参考我的这篇博客——
Scala 的代码风格怎么统一?这份 scalastyle 配置你可以无脑复制
版权声明:本文为Shockang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。