参考 Apache Spark 的源码自定义实现 Logging 日志打印工具
Posted Shockang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了参考 Apache Spark 的源码自定义实现 Logging 日志打印工具相关的知识,希望对你有一定的参考价值。
前言
本文隶属于专栏《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 配置你可以无脑复制
以上是关于参考 Apache Spark 的源码自定义实现 Logging 日志打印工具的主要内容,如果未能解决你的问题,请参考以下文章
在 Apache Spark Python 中自定义 K-means 的距离公式