| /* |
| * 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.log4j.config; |
| |
| import org.apache.log4j.Appender; |
| import org.apache.log4j.Layout; |
| import org.apache.log4j.LogManager; |
| import org.apache.log4j.PatternLayout; |
| import org.apache.log4j.bridge.AppenderAdapter; |
| import org.apache.log4j.bridge.AppenderWrapper; |
| import org.apache.log4j.builders.BuilderManager; |
| import org.apache.log4j.helpers.OptionConverter; |
| import org.apache.log4j.spi.ErrorHandler; |
| import org.apache.log4j.spi.Filter; |
| import org.apache.logging.log4j.core.LoggerContext; |
| import org.apache.logging.log4j.core.config.ConfigurationSource; |
| import org.apache.logging.log4j.core.config.LoggerConfig; |
| import org.apache.logging.log4j.core.config.status.StatusConfiguration; |
| import org.apache.logging.log4j.core.filter.AbstractFilterable; |
| import org.apache.logging.log4j.util.LoaderUtil; |
| |
| import java.io.InputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.SortedMap; |
| import java.util.StringTokenizer; |
| import java.util.TreeMap; |
| |
| /** |
| * Construct a configuration based on Log4j 1 properties. |
| */ |
| public class PropertiesConfiguration extends Log4j1Configuration { |
| |
| private static final String CATEGORY_PREFIX = "log4j.category."; |
| private static final String LOGGER_PREFIX = "log4j.logger."; |
| private static final String ADDITIVITY_PREFIX = "log4j.additivity."; |
| private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; |
| private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; |
| private static final String APPENDER_PREFIX = "log4j.appender."; |
| private static final String LOGGER_REF = "logger-ref"; |
| private static final String ROOT_REF = "root-ref"; |
| private static final String APPENDER_REF_TAG = "appender-ref"; |
| public static final long DEFAULT_DELAY = 60000; |
| public static final String DEBUG_KEY="log4j.debug"; |
| |
| private static final String INTERNAL_ROOT_NAME = "root"; |
| |
| private final Map<String, Appender> registry; |
| |
| /** |
| * Constructor. |
| * @param loggerContext The LoggerContext. |
| * @param source The ConfigurationSource. |
| * @param monitorIntervalSeconds The monitoring interval in seconds. |
| */ |
| public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, |
| int monitorIntervalSeconds) { |
| super(loggerContext, source, monitorIntervalSeconds); |
| registry = new HashMap<>(); |
| } |
| |
| public void doConfigure() { |
| InputStream is = getConfigurationSource().getInputStream(); |
| Properties props = new Properties(); |
| try { |
| props.load(is); |
| } catch (Exception e) { |
| LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e); |
| return; |
| } |
| // If we reach here, then the config file is alright. |
| doConfigure(props); |
| } |
| |
| /** |
| * Read configuration from a file. <b>The existing configuration is |
| * not cleared nor reset.</b> If you require a different behavior, |
| * then call {@link LogManager#resetConfiguration |
| * resetConfiguration} method before calling |
| * <code>doConfigure</code>. |
| * |
| * <p>The configuration file consists of statements in the format |
| * <code>key=value</code>. The syntax of different configuration |
| * elements are discussed below. |
| * |
| * <p>The level value can consist of the string values OFF, FATAL, |
| * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A |
| * custom level value can be specified in the form |
| * level#classname. By default the repository-wide threshold is set |
| * to the lowest possible value, namely the level <code>ALL</code>. |
| * </p> |
| * |
| * |
| * <h3>Appender configuration</h3> |
| * |
| * <p>Appender configuration syntax is: |
| * <pre> |
| * # For appender named <i>appenderName</i>, set its class. |
| * # Note: The appender name can contain dots. |
| * log4j.appender.appenderName=fully.qualified.name.of.appender.class |
| * |
| * # Set appender specific options. |
| * log4j.appender.appenderName.option1=value1 |
| * ... |
| * log4j.appender.appenderName.optionN=valueN |
| * </pre> |
| * <p> |
| * For each named appender you can configure its {@link Layout}. The |
| * syntax for configuring an appender's layout is: |
| * <pre> |
| * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class |
| * log4j.appender.appenderName.layout.option1=value1 |
| * .... |
| * log4j.appender.appenderName.layout.optionN=valueN |
| * </pre> |
| * <p> |
| * The syntax for adding {@link Filter}s to an appender is: |
| * <pre> |
| * log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class |
| * log4j.appender.appenderName.filter.ID.option1=value1 |
| * ... |
| * log4j.appender.appenderName.filter.ID.optionN=valueN |
| * </pre> |
| * The first line defines the class name of the filter identified by ID; |
| * subsequent lines with the same ID specify filter option - value |
| * pairs. Multiple filters are added to the appender in the lexicographic |
| * order of IDs. |
| * <p> |
| * The syntax for adding an {@link ErrorHandler} to an appender is: |
| * <pre> |
| * log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class |
| * log4j.appender.appenderName.errorhandler.appender-ref=appenderName |
| * log4j.appender.appenderName.errorhandler.option1=value1 |
| * ... |
| * log4j.appender.appenderName.errorhandler.optionN=valueN |
| * </pre> |
| * |
| * <h3>Configuring loggers</h3> |
| * |
| * <p>The syntax for configuring the root logger is: |
| * <pre> |
| * log4j.rootLogger=[level], appenderName, appenderName, ... |
| * </pre> |
| * |
| * <p>This syntax means that an optional <em>level</em> can be |
| * supplied followed by appender names separated by commas. |
| * |
| * <p>The level value can consist of the string values OFF, FATAL, |
| * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A |
| * custom level value can be specified in the form |
| * <code>level#classname</code>. |
| * |
| * <p>If a level value is specified, then the root level is set |
| * to the corresponding level. If no level value is specified, |
| * then the root level remains untouched. |
| * |
| * <p>The root logger can be assigned multiple appenders. |
| * |
| * <p>Each <i>appenderName</i> (separated by commas) will be added to |
| * the root logger. The named appender is defined using the |
| * appender syntax defined above. |
| * |
| * <p>For non-root categories the syntax is almost the same: |
| * <pre> |
| * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ... |
| * </pre> |
| * |
| * <p>The meaning of the optional level value is discussed above |
| * in relation to the root logger. In addition however, the value |
| * INHERITED can be specified meaning that the named logger should |
| * inherit its level from the logger hierarchy. |
| * |
| * <p>If no level value is supplied, then the level of the |
| * named logger remains untouched. |
| * |
| * <p>By default categories inherit their level from the |
| * hierarchy. However, if you set the level of a logger and later |
| * decide that that logger should inherit its level, then you should |
| * specify INHERITED as the value for the level value. NULL is a |
| * synonym for INHERITED. |
| * |
| * <p>Similar to the root logger syntax, each <i>appenderName</i> |
| * (separated by commas) will be attached to the named logger. |
| * |
| * <p>See the <a href="../../../../manual.html#additivity">appender |
| * additivity rule</a> in the user manual for the meaning of the |
| * <code>additivity</code> flag. |
| * |
| * |
| * # Set options for appender named "A1". |
| * # Appender "A1" will be a SyslogAppender |
| * log4j.appender.A1=org.apache.log4j.net.SyslogAppender |
| * |
| * # The syslog daemon resides on www.abc.net |
| * log4j.appender.A1.SyslogHost=www.abc.net |
| * |
| * # A1's layout is a PatternLayout, using the conversion pattern |
| * # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will |
| * # include # the relative time since the start of the application in |
| * # milliseconds, followed by the level of the log request, |
| * # followed by the two rightmost components of the logger name, |
| * # followed by the callers method name, followed by the line number, |
| * # the nested diagnostic context and finally the message itself. |
| * # Refer to the documentation of {@link PatternLayout} for further information |
| * # on the syntax of the ConversionPattern key. |
| * log4j.appender.A1.layout=org.apache.log4j.PatternLayout |
| * log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n |
| * |
| * # Set options for appender named "A2" |
| * # A2 should be a RollingFileAppender, with maximum file size of 10 MB |
| * # using at most one backup file. A2's layout is TTCC, using the |
| * # ISO8061 date format with context printing enabled. |
| * log4j.appender.A2=org.apache.log4j.RollingFileAppender |
| * log4j.appender.A2.MaxFileSize=10MB |
| * log4j.appender.A2.MaxBackupIndex=1 |
| * log4j.appender.A2.layout=org.apache.log4j.TTCCLayout |
| * log4j.appender.A2.layout.ContextPrinting=enabled |
| * log4j.appender.A2.layout.DateFormat=ISO8601 |
| * |
| * # Root logger set to DEBUG using the A2 appender defined above. |
| * log4j.rootLogger=DEBUG, A2 |
| * |
| * # Logger definitions: |
| * # The SECURITY logger inherits is level from root. However, it's output |
| * # will go to A1 appender defined above. It's additivity is non-cumulative. |
| * log4j.logger.SECURITY=INHERIT, A1 |
| * log4j.additivity.SECURITY=false |
| * |
| * # Only warnings or above will be logged for the logger "SECURITY.access". |
| * # Output will go to A1. |
| * log4j.logger.SECURITY.access=WARN |
| * |
| * |
| * # The logger "class.of.the.day" inherits its level from the |
| * # logger hierarchy. Output will go to the appender's of the root |
| * # logger, A2 in this case. |
| * log4j.logger.class.of.the.day=INHERIT |
| * </pre> |
| * |
| * <p>Refer to the <b>setOption</b> method in each Appender and |
| * Layout for class specific options. |
| * |
| * <p>Use the <code>#</code> or <code>!</code> characters at the |
| * beginning of a line for comments. |
| * |
| * <p> |
| */ |
| private void doConfigure(Properties properties) { |
| String status = "error"; |
| String value = properties.getProperty(DEBUG_KEY); |
| if (value == null) { |
| value = properties.getProperty("log4j.configDebug"); |
| if (value != null) { |
| LOGGER.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); |
| } |
| } |
| |
| if (value != null) { |
| status = OptionConverter.toBoolean(value, false) ? "debug" : "error"; |
| } |
| |
| final StatusConfiguration statusConfig = new StatusConfiguration().setStatus(status); |
| statusConfig.initialize(); |
| |
| configureRoot(properties); |
| parseLoggers(properties); |
| |
| LOGGER.debug("Finished configuring."); |
| } |
| |
| // -------------------------------------------------------------------------- |
| // Internal stuff |
| // -------------------------------------------------------------------------- |
| |
| private void configureRoot(Properties props) { |
| String effectiveFrefix = ROOT_LOGGER_PREFIX; |
| String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); |
| |
| if (value == null) { |
| value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props); |
| effectiveFrefix = ROOT_CATEGORY_PREFIX; |
| } |
| |
| if (value == null) { |
| LOGGER.debug("Could not find root logger information. Is this OK?"); |
| } else { |
| LoggerConfig root = getRootLogger(); |
| parseLogger(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); |
| } |
| } |
| |
| /** |
| * Parse non-root elements, such non-root categories and renderers. |
| */ |
| private void parseLoggers(Properties props) { |
| Enumeration enumeration = props.propertyNames(); |
| while (enumeration.hasMoreElements()) { |
| String key = (String) enumeration.nextElement(); |
| if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { |
| String loggerName = null; |
| if (key.startsWith(CATEGORY_PREFIX)) { |
| loggerName = key.substring(CATEGORY_PREFIX.length()); |
| } else if (key.startsWith(LOGGER_PREFIX)) { |
| loggerName = key.substring(LOGGER_PREFIX.length()); |
| } |
| String value = OptionConverter.findAndSubst(key, props); |
| LoggerConfig loggerConfig = getLogger(loggerName); |
| if (loggerConfig == null) { |
| boolean additivity = getAdditivityForLogger(props, loggerName); |
| loggerConfig = new LoggerConfig(loggerName, org.apache.logging.log4j.Level.ERROR, additivity); |
| addLogger(loggerName, loggerConfig); |
| } |
| parseLogger(props, loggerConfig, key, loggerName, value); |
| } |
| } |
| } |
| |
| /** |
| * Parse the additivity option for a non-root category. |
| */ |
| private boolean getAdditivityForLogger(Properties props, String loggerName) { |
| boolean additivity = true; |
| String key = ADDITIVITY_PREFIX + loggerName; |
| String value = OptionConverter.findAndSubst(key, props); |
| LOGGER.debug("Handling {}=[{}]", key, value); |
| // touch additivity only if necessary |
| if ((value != null) && (!value.equals(""))) { |
| additivity = OptionConverter.toBoolean(value, true); |
| } |
| return additivity; |
| } |
| |
| /** |
| * This method must work for the root category as well. |
| */ |
| private void parseLogger(Properties props, LoggerConfig logger, String optionKey, String loggerName, String value) { |
| |
| LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value); |
| // We must skip over ',' but not white space |
| StringTokenizer st = new StringTokenizer(value, ","); |
| |
| // If value is not in the form ", appender.." or "", then we should set the level of the logger. |
| |
| if (!(value.startsWith(",") || value.equals(""))) { |
| |
| // just to be on the safe side... |
| if (!st.hasMoreTokens()) { |
| return; |
| } |
| |
| String levelStr = st.nextToken(); |
| LOGGER.debug("Level token is [{}].", levelStr); |
| |
| org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR : |
| OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG); |
| logger.setLevel(level); |
| LOGGER.debug("Logger {} level set to {}", loggerName, level); |
| } |
| |
| Appender appender; |
| String appenderName; |
| while (st.hasMoreTokens()) { |
| appenderName = st.nextToken().trim(); |
| if (appenderName == null || appenderName.equals(",")) { |
| continue; |
| } |
| LOGGER.debug("Parsing appender named \"{}\".", appenderName); |
| appender = parseAppender(props, appenderName); |
| if (appender != null) { |
| LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, |
| logger.getName()); |
| logger.addAppender(getAppender(appenderName), null, null); |
| } else { |
| LOGGER.debug("Appender named [{}}] not found.", appenderName); |
| } |
| } |
| } |
| |
| public Appender parseAppender(Properties props, String appenderName) { |
| Appender appender = registry.get(appenderName); |
| if ((appender != null)) { |
| LOGGER.debug("Appender \"" + appenderName + "\" was already parsed."); |
| return appender; |
| } |
| // Appender was not previously initialized. |
| final String prefix = APPENDER_PREFIX + appenderName; |
| final String layoutPrefix = prefix + ".layout"; |
| final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter."; |
| String className = OptionConverter.findAndSubst(prefix, props); |
| appender = manager.parseAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props, this); |
| if (appender == null) { |
| appender = buildAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props); |
| } else { |
| registry.put(appenderName, appender); |
| if (appender instanceof AppenderWrapper) { |
| addAppender(((AppenderWrapper) appender).getAppender()); |
| } else { |
| addAppender(new AppenderAdapter(appender).getAdapter()); |
| } |
| } |
| return appender; |
| } |
| |
| private Appender buildAppender(final String appenderName, final String className, final String prefix, |
| final String layoutPrefix, final String filterPrefix, final Properties props) { |
| Appender appender = newInstanceOf(className, "Appender"); |
| if (appender == null) { |
| return null; |
| } |
| appender.setName(appenderName); |
| appender.setLayout(parseLayout(layoutPrefix, appenderName, props)); |
| final String errorHandlerPrefix = prefix + ".errorhandler"; |
| String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); |
| if (errorHandlerClass != null) { |
| ErrorHandler eh = parseErrorHandler(props, errorHandlerPrefix, errorHandlerClass, appender); |
| if (eh != null) { |
| appender.setErrorHandler(eh); |
| } |
| } |
| parseAppenderFilters(props, filterPrefix, appenderName); |
| String[] keys = new String[] { |
| layoutPrefix, |
| }; |
| addProperties(appender, keys, props, prefix); |
| if (appender instanceof AppenderWrapper) { |
| addAppender(((AppenderWrapper) appender).getAppender()); |
| } else { |
| addAppender(new AppenderAdapter(appender).getAdapter()); |
| } |
| registry.put(appenderName, appender); |
| return appender; |
| } |
| |
| public Layout parseLayout(String layoutPrefix, String appenderName, Properties props) { |
| String layoutClass = OptionConverter.findAndSubst(layoutPrefix, props); |
| if (layoutClass == null) { |
| return null; |
| } |
| Layout layout = manager.parseLayout(layoutClass, layoutPrefix, props, this); |
| if (layout == null) { |
| layout = buildLayout(layoutPrefix, layoutClass, appenderName, props); |
| } |
| return layout; |
| } |
| |
| private Layout buildLayout(String layoutPrefix, String className, String appenderName, Properties props) { |
| Layout layout = newInstanceOf(className, "Layout"); |
| if (layout == null) { |
| return null; |
| } |
| LOGGER.debug("Parsing layout options for \"{}\".", appenderName); |
| PropertySetter.setProperties(layout, props, layoutPrefix + "."); |
| LOGGER.debug("End of parsing for \"{}\".", appenderName); |
| return layout; |
| } |
| |
| public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, |
| final String errorHandlerClass, final Appender appender) { |
| ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler"); |
| final String[] keys = new String[] { |
| errorHandlerPrefix + "." + ROOT_REF, |
| errorHandlerPrefix + "." + LOGGER_REF, |
| errorHandlerPrefix + "." + APPENDER_REF_TAG |
| }; |
| addProperties(eh, keys, props, errorHandlerPrefix); |
| return eh; |
| } |
| |
| public void addProperties(final Object obj, final String[] keys, final Properties props, final String prefix) { |
| final Properties edited = new Properties(); |
| props.stringPropertyNames().stream().filter((name) -> { |
| if (name.startsWith(prefix)) { |
| for (String key : keys) { |
| if (name.equals(key)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| }).forEach((name) -> edited.put(name, props.getProperty(name))); |
| PropertySetter.setProperties(obj, edited, prefix + "."); |
| } |
| |
| |
| public Filter parseAppenderFilters(Properties props, String filterPrefix, String appenderName) { |
| // extract filters and filter options from props into a hashtable mapping |
| // the property name defining the filter class to a list of pre-parsed |
| // name-value pairs associated to that filter |
| int fIdx = filterPrefix.length(); |
| SortedMap<String, List<NameValue>> filters = new TreeMap<>(); |
| Enumeration e = props.keys(); |
| String name = ""; |
| while (e.hasMoreElements()) { |
| String key = (String) e.nextElement(); |
| if (key.startsWith(filterPrefix)) { |
| int dotIdx = key.indexOf('.', fIdx); |
| String filterKey = key; |
| if (dotIdx != -1) { |
| filterKey = key.substring(0, dotIdx); |
| name = key.substring(dotIdx + 1); |
| } |
| List<NameValue> filterOpts = filters.computeIfAbsent(filterKey, k -> new ArrayList<>()); |
| if (dotIdx != -1) { |
| String value = OptionConverter.findAndSubst(key, props); |
| filterOpts.add(new NameValue(name, value)); |
| } |
| } |
| } |
| |
| Filter head = null; |
| Filter next = null; |
| for (Map.Entry<String, List<NameValue>> entry : filters.entrySet()) { |
| String clazz = props.getProperty(entry.getKey()); |
| Filter filter = null; |
| if (clazz != null) { |
| filter = manager.parseFilter(clazz, filterPrefix, props, this); |
| if (filter == null) { |
| LOGGER.debug("Filter key: [{}] class: [{}] props: {}", entry.getKey(), clazz, entry.getValue()); |
| filter = buildFilter(clazz, appenderName, entry.getValue()); |
| } |
| } |
| if (filter != null) { |
| if (head != null) { |
| head = filter; |
| next = filter; |
| } else { |
| next.setNext(filter); |
| next = filter; |
| } |
| } |
| } |
| return head; |
| } |
| |
| private Filter buildFilter(String className, String appenderName, List<NameValue> props) { |
| Filter filter = newInstanceOf(className, "Filter"); |
| if (filter != null) { |
| PropertySetter propSetter = new PropertySetter(filter); |
| for (NameValue property : props) { |
| propSetter.setProperty(property.key, property.value); |
| } |
| propSetter.activate(); |
| } |
| return filter; |
| } |
| |
| |
| private static <T> T newInstanceOf(String className, String type) { |
| try { |
| return LoaderUtil.newInstanceOf(className); |
| } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | |
| InstantiationException | InvocationTargetException ex) { |
| LOGGER.error("Unable to create {} {} due to {}:{}", type, className, |
| ex.getClass().getSimpleName(), ex.getMessage()); |
| return null; |
| } |
| } |
| |
| private static class NameValue { |
| String key, value; |
| |
| NameValue(String key, String value) { |
| this.key = key; |
| this.value = value; |
| } |
| |
| public String toString() { |
| return key + "=" + value; |
| } |
| } |
| |
| } |