| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.ignite.logger.log4j; |
| |
| import java.io.File; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.UUID; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteLogger; |
| import org.apache.ignite.internal.util.GridConcurrentHashSet; |
| import org.apache.ignite.internal.util.tostring.GridToStringExclude; |
| import org.apache.ignite.internal.util.typedef.C1; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.internal.A; |
| import org.apache.ignite.internal.util.typedef.internal.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.lang.IgniteClosure; |
| import org.apache.ignite.logger.LoggerNodeIdAndApplicationAware; |
| import org.apache.log4j.Appender; |
| import org.apache.log4j.Category; |
| import org.apache.log4j.ConsoleAppender; |
| import org.apache.log4j.FileAppender; |
| import org.apache.log4j.Level; |
| import org.apache.log4j.LogManager; |
| import org.apache.log4j.Logger; |
| import org.apache.log4j.PatternLayout; |
| import org.apache.log4j.helpers.FileWatchdog; |
| import org.apache.log4j.varia.LevelRangeFilter; |
| import org.apache.log4j.xml.DOMConfigurator; |
| import org.jetbrains.annotations.Nullable; |
| |
| import static org.apache.ignite.IgniteSystemProperties.IGNITE_CONSOLE_APPENDER; |
| import static org.apache.ignite.IgniteSystemProperties.IGNITE_QUIET; |
| |
| /** |
| * Log4j-based implementation for logging. This logger should be used |
| * by loaders that have prefer <a target=_new href="http://logging.apache.org/log4j/1.2/">log4j</a>-based logging. |
| * <p> |
| * Here is a typical example of configuring log4j logger in Ignite configuration file: |
| * <pre name="code" class="xml"> |
| * <property name="gridLogger"> |
| * <bean class="org.apache.ignite.logger.log4j.Log4JLogger"> |
| * <constructor-arg type="java.lang.String" value="config/ignite-log4j.xml"/> |
| * </bean> |
| * </property> |
| * </pre> |
| * and from your code: |
| * <pre name="code" class="java"> |
| * IgniteConfiguration cfg = new IgniteConfiguration(); |
| * ... |
| * URL xml = U.resolveIgniteUrl("config/custom-log4j.xml"); |
| * IgniteLogger log = new Log4JLogger(xml); |
| * ... |
| * cfg.setGridLogger(log); |
| * </pre> |
| * |
| * Please take a look at <a target=_new href="http://logging.apache.org/log4j/1.2/index.html">Apache Log4j 1.2</a> |
| * for additional information. |
| * <p> |
| * It's recommended to use Ignite logger injection instead of using/instantiating |
| * logger in your task/job code. See {@link org.apache.ignite.resources.LoggerResource} annotation about logger |
| * injection. |
| * |
| * @deprecated Log4j 1.x had reached end of life and contains critical vulnerabilities. See |
| * <a href="https://blogs.apache.org/foundation/entry/apache_logging_services_project_announces">the announcement</a>. |
| * Please, be aware this module will be removed in the next releases. |
| * Use <a href="https://ignite.apache.org/docs/latest/logging#using-log4j2">ignite-log4j2</a> module instead. |
| */ |
| @Deprecated |
| public class Log4JLogger implements IgniteLogger, LoggerNodeIdAndApplicationAware, Log4jFileAware { |
| /** */ |
| public static final String DEPRECATED_MSG = "The 'ignite-log4j' module is deprecated and will be removed in the " + |
| "next releases. Use 'ignite-log4j2' module instead."; |
| |
| /** Appenders. */ |
| private static Collection<FileAppender> fileAppenders = new GridConcurrentHashSet<>(); |
| |
| /** */ |
| private static volatile boolean inited; |
| |
| /** */ |
| private static volatile boolean quiet0; |
| |
| /** */ |
| private static final Object mux = new Object(); |
| |
| /** Logger implementation. */ |
| @GridToStringExclude |
| @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") |
| private Logger impl; |
| |
| /** Path to configuration file. */ |
| @GridToStringExclude |
| private final String cfg; |
| |
| /** Quiet flag. */ |
| private final boolean quiet; |
| |
| /** Node ID. */ |
| @GridToStringExclude |
| private UUID nodeId; |
| |
| /** |
| * Creates new logger and automatically detects if root logger already |
| * has appenders configured. If it does not, the root logger will be |
| * configured with default appender (analogous to calling |
| * {@link #Log4JLogger(boolean) Log4JLogger(boolean)} |
| * with parameter {@code true}, otherwise, existing appenders will be used (analogous |
| * to calling {@link #Log4JLogger(boolean) Log4JLogger(boolean)} |
| * with parameter {@code false}). |
| */ |
| public Log4JLogger() { |
| this(!isConfigured()); |
| } |
| |
| /** |
| * Creates new logger. If initialize parameter is {@code true} the Log4j |
| * logger will be initialized with default console appender and {@code INFO} |
| * log level. |
| * |
| * @param init If {@code true}, then a default console appender with |
| * following pattern layout will be created: {@code %d{ISO8601} %-5p [%c{1}] %m%n}. |
| * If {@code false}, then no implicit initialization will take place, |
| * and {@code Log4j} should be configured prior to calling this |
| * constructor. |
| */ |
| public Log4JLogger(boolean init) { |
| impl = Logger.getRootLogger(); |
| |
| if (init) { |
| // Implementation has already been inited, passing NULL. |
| addConsoleAppenderIfNeeded(Level.INFO, null); |
| |
| quiet = quiet0; |
| } |
| else |
| quiet = true; |
| |
| cfg = null; |
| |
| warning(DEPRECATED_MSG); |
| } |
| |
| /** |
| * Creates new logger with given implementation. |
| * |
| * @param impl Log4j implementation to use. |
| */ |
| public Log4JLogger(final Logger impl) { |
| assert impl != null; |
| |
| addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() { |
| @Override public Logger apply(Boolean init) { |
| return impl; |
| } |
| }); |
| |
| quiet = quiet0; |
| cfg = null; |
| |
| warning(DEPRECATED_MSG); |
| } |
| |
| /** |
| * Creates new logger with given implementation. |
| * |
| * @param impl Log4j implementation to use. |
| * @param path Configuration file/url path. |
| */ |
| private Log4JLogger(final Logger impl, final String path) { |
| assert impl != null; |
| |
| addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() { |
| @Override public Logger apply(Boolean init) { |
| return impl; |
| } |
| }); |
| |
| quiet = quiet0; |
| cfg = path; |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code path}. |
| * Calling this constructor is equivalent to calling {@code Log4JLogger(path, FileWatchdog.DEFAULT_DELAY}. |
| * |
| * @param path Path to log4j configuration XML file. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(final String path) throws IgniteCheckedException { |
| this(path, FileWatchdog.DEFAULT_DELAY); |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code path}. |
| * <p> |
| * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every |
| * {@code watchDelay} milliseconds, and update its configuration if the file was changed. |
| * See {@link DOMConfigurator#configureAndWatch(String, long)} for details. |
| * |
| * @param path Path to log4j configuration XML file. |
| * @param watchDelay delay in milliseconds used to check configuration file for changes. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(final String path, long watchDelay) throws IgniteCheckedException { |
| if (path == null) |
| throw new IgniteCheckedException("Configuration XML file for Log4j must be specified."); |
| |
| if (watchDelay < 0) |
| throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay); |
| |
| this.cfg = path; |
| |
| final File cfgFile = U.resolveIgnitePath(path); |
| |
| if (cfgFile == null) |
| throw new IgniteCheckedException("Log4j configuration path was not found: " + path); |
| |
| addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() { |
| @Override public Logger apply(Boolean init) { |
| if (init) { |
| if (watchDelay > 0) |
| DOMConfigurator.configureAndWatch(cfgFile.getPath(), watchDelay); |
| else |
| DOMConfigurator.configure(cfgFile.getPath()); |
| } |
| |
| return Logger.getRootLogger(); |
| } |
| }); |
| |
| quiet = quiet0; |
| |
| warning(DEPRECATED_MSG); |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code cfgFile}. |
| * Calling this constructor is equivalent to calling {@code Log4JLogger(cfgFile, FileWatchdog.DEFAULT_DELAY}. |
| * |
| * @param cfgFile Log4j configuration XML file. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(File cfgFile) throws IgniteCheckedException { |
| this(cfgFile, FileWatchdog.DEFAULT_DELAY); |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code cfgFile}. |
| * <p> |
| * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every |
| * {@code watchDelay} milliseconds, and update its configuration if the file was changed. |
| * See {@link DOMConfigurator#configureAndWatch(String, long)} for details. |
| * |
| * @param cfgFile Log4j configuration XML file. |
| * @param watchDelay delay in milliseconds used to check configuration file for changes. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(final File cfgFile, final long watchDelay) throws IgniteCheckedException { |
| if (cfgFile == null) |
| throw new IgniteCheckedException("Configuration XML file for Log4j must be specified."); |
| |
| if (!cfgFile.exists() || cfgFile.isDirectory()) |
| throw new IgniteCheckedException("Log4j configuration path was not found or is a directory: " + cfgFile); |
| |
| if (watchDelay < 0) |
| throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay); |
| |
| cfg = cfgFile.getAbsolutePath(); |
| |
| addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() { |
| @Override public Logger apply(Boolean init) { |
| if (init) { |
| if (watchDelay > 0) |
| DOMConfigurator.configureAndWatch(cfgFile.getPath(), watchDelay); |
| else |
| DOMConfigurator.configure(cfgFile.getPath()); |
| } |
| |
| return Logger.getRootLogger(); |
| } |
| }); |
| |
| quiet = quiet0; |
| |
| warning(DEPRECATED_MSG); |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code cfgUrl}. |
| * Calling this constructor is equivalent to calling {@code Log4JLogger(cfgUrl, FileWatchdog.DEFAULT_DELAY}. |
| * |
| * @param cfgUrl URL for Log4j configuration XML file. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(final URL cfgUrl) throws IgniteCheckedException { |
| this(cfgUrl, FileWatchdog.DEFAULT_DELAY); |
| } |
| |
| /** |
| * Creates new logger with given configuration {@code cfgUrl}. |
| * <p> |
| * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every |
| * {@code watchDelay} milliseconds, and update its configuration if the file was changed. |
| * See {@link DOMConfigurator#configureAndWatch(String, long)} for details. |
| * |
| * @param cfgUrl URL for Log4j configuration XML file. |
| * @param watchDelay delay in milliseconds used to check configuration file for changes. |
| * @throws IgniteCheckedException Thrown in case logger can't be created. |
| */ |
| public Log4JLogger(final URL cfgUrl, final long watchDelay) throws IgniteCheckedException { |
| if (cfgUrl == null) |
| throw new IgniteCheckedException("Configuration XML file for Log4j must be specified."); |
| |
| if (watchDelay < 0) |
| throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay); |
| |
| cfg = cfgUrl.getPath(); |
| |
| addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() { |
| @Override public Logger apply(Boolean init) { |
| if (init) { |
| if (watchDelay > 0) |
| DOMConfigurator.configureAndWatch(cfg, watchDelay); |
| else |
| DOMConfigurator.configure(cfg); |
| } |
| |
| return Logger.getRootLogger(); |
| } |
| }); |
| |
| quiet = quiet0; |
| |
| warning(DEPRECATED_MSG); |
| } |
| |
| /** |
| * Checks if Log4j is already configured within this VM or not. |
| * |
| * @return {@code True} if log4j was already configured, {@code false} otherwise. |
| */ |
| public static boolean isConfigured() { |
| return Logger.getRootLogger().getAllAppenders().hasMoreElements(); |
| } |
| |
| /** |
| * Sets level for internal log4j implementation. |
| * |
| * @param level Log level to set. |
| */ |
| public void setLevel(Level level) { |
| impl.setLevel(level); |
| } |
| |
| /** {@inheritDoc} */ |
| @Nullable @Override public String fileName() { |
| FileAppender fapp = F.first(fileAppenders); |
| |
| return fapp != null ? fapp.getFile() : null; |
| } |
| |
| /** |
| * Adds console appender when needed with some default logging settings. |
| * |
| * @param logLevel Optional log level. |
| * @param implInitC Optional log implementation init closure. |
| */ |
| private void addConsoleAppenderIfNeeded(@Nullable Level logLevel, |
| @Nullable IgniteClosure<Boolean, Logger> implInitC) { |
| if (inited) { |
| if (implInitC != null) |
| // Do not init. |
| impl = implInitC.apply(false); |
| |
| return; |
| } |
| |
| synchronized (mux) { |
| if (inited) { |
| if (implInitC != null) |
| // Do not init. |
| impl = implInitC.apply(false); |
| |
| return; |
| } |
| |
| if (implInitC != null) |
| // Init logger impl. |
| impl = implInitC.apply(true); |
| |
| boolean quiet = Boolean.valueOf(System.getProperty(IGNITE_QUIET, "true")); |
| |
| boolean consoleAppenderFound = false; |
| Category rootCategory = null; |
| ConsoleAppender errAppender = null; |
| |
| for (Category l = impl; l != null; ) { |
| if (!consoleAppenderFound) { |
| for (Enumeration appenders = l.getAllAppenders(); appenders.hasMoreElements(); ) { |
| Appender appender = (Appender)appenders.nextElement(); |
| |
| if (appender instanceof ConsoleAppender) { |
| if ("CONSOLE_ERR".equals(appender.getName())) { |
| // Treat CONSOLE_ERR appender as a system one and don't count it. |
| errAppender = (ConsoleAppender)appender; |
| |
| continue; |
| } |
| |
| consoleAppenderFound = true; |
| |
| break; |
| } |
| } |
| } |
| |
| if (l.getParent() == null) { |
| rootCategory = l; |
| |
| break; |
| } |
| else |
| l = l.getParent(); |
| } |
| |
| if (consoleAppenderFound && quiet) |
| // User configured console appender, but log is quiet. |
| quiet = false; |
| |
| if (!consoleAppenderFound && !quiet && Boolean.valueOf(System.getProperty(IGNITE_CONSOLE_APPENDER, "true"))) { |
| // Console appender not found => we've looked through all categories up to root. |
| assert rootCategory != null; |
| |
| // User launched ignite in verbose mode and did not add console appender with INFO level |
| // to configuration and did not set IGNITE_CONSOLE_APPENDER to false. |
| if (errAppender != null) { |
| rootCategory.addAppender(createConsoleAppender(Level.INFO)); |
| |
| if (errAppender.getThreshold() == Level.ERROR) |
| errAppender.setThreshold(Level.WARN); |
| } |
| else |
| // No error console appender => create console appender with no level limit. |
| rootCategory.addAppender(createConsoleAppender(Level.OFF)); |
| |
| if (logLevel != null) |
| impl.setLevel(logLevel); |
| } |
| |
| // If still don't have appenders, disable logging. |
| if (!isConfigured()) |
| impl.setLevel(Level.OFF); |
| |
| quiet0 = quiet; |
| inited = true; |
| } |
| } |
| |
| /** |
| * Creates console appender with some reasonable default logging settings. |
| * |
| * @param maxLevel Max logging level. |
| * @return New console appender. |
| */ |
| private Appender createConsoleAppender(Level maxLevel) { |
| String fmt = "[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"; |
| |
| // Configure output that should go to System.out |
| Appender app = new ConsoleAppender(new PatternLayout(fmt), ConsoleAppender.SYSTEM_OUT); |
| |
| LevelRangeFilter lvlFilter = new LevelRangeFilter(); |
| |
| lvlFilter.setLevelMin(Level.TRACE); |
| lvlFilter.setLevelMax(maxLevel); |
| |
| app.addFilter(lvlFilter); |
| |
| return app; |
| } |
| |
| /** |
| * Adds file appender. |
| * |
| * @param a Appender. |
| */ |
| public static void addAppender(FileAppender a) { |
| A.notNull(a, "a"); |
| |
| fileAppenders.add(a); |
| } |
| |
| /** |
| * Removes file appender. |
| * |
| * @param a Appender. |
| */ |
| public static void removeAppender(FileAppender a) { |
| A.notNull(a, "a"); |
| |
| fileAppenders.remove(a); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void setApplicationAndNode(@Nullable String application, UUID nodeId) { |
| A.notNull(nodeId, "nodeId"); |
| |
| this.nodeId = nodeId; |
| |
| updateFilePath(new Log4jNodeIdFilePath(application, nodeId)); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public UUID getNodeId() { |
| return nodeId; |
| } |
| |
| /** |
| * Gets files for all registered file appenders. |
| * |
| * @return List of files. |
| */ |
| public static Collection<String> logFiles() { |
| Collection<String> res = new ArrayList<>(fileAppenders.size()); |
| |
| for (FileAppender a : fileAppenders) |
| res.add(a.getFile()); |
| |
| return res; |
| } |
| |
| /** |
| * Gets {@link org.apache.ignite.IgniteLogger} wrapper around log4j logger for the given |
| * category. If category is {@code null}, then root logger is returned. If |
| * category is an instance of {@link Class} then {@code (Class)ctgr).getName()} |
| * is used as category name. |
| * |
| * @param ctgr {@inheritDoc} |
| * @return {@link org.apache.ignite.IgniteLogger} wrapper around log4j logger. |
| */ |
| @Override public Log4JLogger getLogger(Object ctgr) { |
| return new Log4JLogger(ctgr == null ? Logger.getRootLogger() : |
| ctgr instanceof Class ? Logger.getLogger(((Class<?>)ctgr).getName()) : |
| Logger.getLogger(ctgr.toString()), cfg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void trace(String msg) { |
| if (!impl.isTraceEnabled()) |
| warning("Logging at TRACE level without checking if TRACE level is enabled: " + msg); |
| |
| impl.trace(msg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void debug(String msg) { |
| if (!impl.isDebugEnabled()) |
| warning("Logging at DEBUG level without checking if DEBUG level is enabled: " + msg); |
| |
| impl.debug(msg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void info(String msg) { |
| if (!impl.isInfoEnabled()) |
| warning("Logging at INFO level without checking if INFO level is enabled: " + msg); |
| |
| impl.info(msg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void warning(String msg) { |
| impl.warn(msg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void warning(String msg, @Nullable Throwable e) { |
| impl.warn(msg, e); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void error(String msg) { |
| impl.error(msg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void error(String msg, @Nullable Throwable e) { |
| impl.error(msg, e); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean isTraceEnabled() { |
| return impl.isTraceEnabled(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean isDebugEnabled() { |
| return impl.isDebugEnabled(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean isInfoEnabled() { |
| return impl.isInfoEnabled(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public boolean isQuiet() { |
| return quiet; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(Log4JLogger.class, this, "config", this.cfg); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void updateFilePath(IgniteClosure<String, String> filePathClos) { |
| A.notNull(filePathClos, "filePathClos"); |
| |
| for (FileAppender a : fileAppenders) { |
| if (a instanceof Log4jFileAware) { |
| ((Log4jFileAware)a).updateFilePath(filePathClos); |
| |
| a.activateOptions(); |
| } |
| } |
| } |
| |
| /** |
| * Cleans up the logger configuration. Should be used in unit tests only for sequential tests run with |
| * different configurations |
| */ |
| static void cleanup() { |
| synchronized (mux) { |
| if (inited) |
| LogManager.shutdown(); |
| |
| inited = false; |
| } |
| } |
| } |