1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *    http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package org.apache.spark.internal
19
20import org.apache.log4j.{Level, LogManager, PropertyConfigurator}
21import org.slf4j.{Logger, LoggerFactory}
22import org.slf4j.impl.StaticLoggerBinder
23
24import org.apache.spark.util.Utils
25
26/**
27 * Utility trait for classes that want to log data. Creates a SLF4J logger for the class and allows
28 * logging messages at different levels using methods that only evaluate parameters lazily if the
29 * log level is enabled.
30 */
31private[spark] trait Logging {
32
33  // Make the log field transient so that objects with Logging can
34  // be serialized and used on another machine
35  @transient private var log_ : Logger = null
36
37  // Method to get the logger name for this object
38  protected def logName = {
39    // Ignore trailing $'s in the class names for Scala objects
40    this.getClass.getName.stripSuffix("$")
41  }
42
43  // Method to get or create the logger for this object
44  protected def log: Logger = {
45    if (log_ == null) {
46      initializeLogIfNecessary(false)
47      log_ = LoggerFactory.getLogger(logName)
48    }
49    log_
50  }
51
52  // Log methods that take only a String
53  protected def logInfo(msg: => String) {
54    if (log.isInfoEnabled) log.info(msg)
55  }
56
57  protected def logDebug(msg: => String) {
58    if (log.isDebugEnabled) log.debug(msg)
59  }
60
61  protected def logTrace(msg: => String) {
62    if (log.isTraceEnabled) log.trace(msg)
63  }
64
65  protected def logWarning(msg: => String) {
66    if (log.isWarnEnabled) log.warn(msg)
67  }
68
69  protected def logError(msg: => String) {
70    if (log.isErrorEnabled) log.error(msg)
71  }
72
73  // Log methods that take Throwables (Exceptions/Errors) too
74  protected def logInfo(msg: => String, throwable: Throwable) {
75    if (log.isInfoEnabled) log.info(msg, throwable)
76  }
77
78  protected def logDebug(msg: => String, throwable: Throwable) {
79    if (log.isDebugEnabled) log.debug(msg, throwable)
80  }
81
82  protected def logTrace(msg: => String, throwable: Throwable) {
83    if (log.isTraceEnabled) log.trace(msg, throwable)
84  }
85
86  protected def logWarning(msg: => String, throwable: Throwable) {
87    if (log.isWarnEnabled) log.warn(msg, throwable)
88  }
89
90  protected def logError(msg: => String, throwable: Throwable) {
91    if (log.isErrorEnabled) log.error(msg, throwable)
92  }
93
94  protected def isTraceEnabled(): Boolean = {
95    log.isTraceEnabled
96  }
97
98  protected def initializeLogIfNecessary(isInterpreter: Boolean): Unit = {
99    if (!Logging.initialized) {
100      Logging.initLock.synchronized {
101        if (!Logging.initialized) {
102          initializeLogging(isInterpreter)
103        }
104      }
105    }
106  }
107
108  private def initializeLogging(isInterpreter: Boolean): Unit = {
109    // Don't use a logger in here, as this is itself occurring during initialization of a logger
110    // If Log4j 1.2 is being used, but is not initialized, load a default properties file
111    val binderClass = StaticLoggerBinder.getSingleton.getLoggerFactoryClassStr
112    // This distinguishes the log4j 1.2 binding, currently
113    // org.slf4j.impl.Log4jLoggerFactory, from the log4j 2.0 binding, currently
114    // org.apache.logging.slf4j.Log4jLoggerFactory
115    val usingLog4j12 = "org.slf4j.impl.Log4jLoggerFactory".equals(binderClass)
116    if (usingLog4j12) {
117      val log4j12Initialized = LogManager.getRootLogger.getAllAppenders.hasMoreElements
118      // scalastyle:off println
119      if (!log4j12Initialized) {
120        val defaultLogProps = "org/apache/spark/log4j-defaults.properties"
121        Option(Utils.getSparkClassLoader.getResource(defaultLogProps)) match {
122          case Some(url) =>
123            PropertyConfigurator.configure(url)
124            System.err.println(s"Using Spark's default log4j profile: $defaultLogProps")
125          case None =>
126            System.err.println(s"Spark was unable to load $defaultLogProps")
127        }
128      }
129
130      if (isInterpreter) {
131        // Use the repl's main class to define the default log level when running the shell,
132        // overriding the root logger's config if they're different.
133        val rootLogger = LogManager.getRootLogger()
134        val replLogger = LogManager.getLogger(logName)
135        val replLevel = Option(replLogger.getLevel()).getOrElse(Level.WARN)
136        if (replLevel != rootLogger.getEffectiveLevel()) {
137          System.err.printf("Setting default log level to \"%s\".\n", replLevel)
138          System.err.println("To adjust logging level use sc.setLogLevel(newLevel). " +
139            "For SparkR, use setLogLevel(newLevel).")
140          rootLogger.setLevel(replLevel)
141        }
142      }
143      // scalastyle:on println
144    }
145    Logging.initialized = true
146
147    // Force a call into slf4j to initialize it. Avoids this happening from multiple threads
148    // and triggering this: http://mailman.qos.ch/pipermail/slf4j-dev/2010-April/002956.html
149    log
150  }
151}
152
153private object Logging {
154  @volatile private var initialized = false
155  val initLock = new Object()
156  try {
157    // We use reflection here to handle the case where users remove the
158    // slf4j-to-jul bridge order to route their logs to JUL.
159    val bridgeClass = Utils.classForName("org.slf4j.bridge.SLF4JBridgeHandler")
160    bridgeClass.getMethod("removeHandlersForRootLogger").invoke(null)
161    val installed = bridgeClass.getMethod("isInstalled").invoke(null).asInstanceOf[Boolean]
162    if (!installed) {
163      bridgeClass.getMethod("install").invoke(null)
164    }
165  } catch {
166    case e: ClassNotFoundException => // can't log anything yet so just fail silently
167  }
168}
169