001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.log4j.config;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URI;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Properties;
027
028import org.apache.logging.log4j.Level;
029import org.apache.logging.log4j.core.appender.ConsoleAppender;
030import org.apache.logging.log4j.core.appender.ConsoleAppender.Target;
031import org.apache.logging.log4j.core.config.Configuration;
032import org.apache.logging.log4j.core.config.ConfigurationFactory;
033import org.apache.logging.log4j.core.config.ConfigurationSource;
034import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
035import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
036import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
037import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
038import org.apache.logging.log4j.status.StatusLogger;
039
040/**
041 * Experimental ConfigurationFactory for Log4j 1.2 properties files.
042 * <p>
043 * Currently supports:
044 * </p>
045 * <ul>
046 * <li>log4j.debug</li>
047 * <li>log4j.rootLogger</li>
048 * <li>log4j.logger</li>
049 * <li>log4j.appender</li>
050 * <li>org.apache.log4j.ConsoleAppender</li>
051 * <li>org.apache.log4j.PatternLayout</li>
052 * <ul>
053 * <li>Follow</li>
054 * <li>Target</li>
055 * <li>layout = org.apache.log4j.PatternLayout</li>
056 * <li>layout = org.apache.log4j.SimpleLayout</li>
057 * <li>layout = org.apache.log4j.TTCCLayout (partial)</li>
058 * <li>layout = org.apache.log4j.HtmlLayout (partial)</li>
059 * <li>layout = org.apache.log4j.XmlLayout (partial)</li>
060 * <li>layout.ConversionPattern</li>
061 * </ul>
062 * </ul>
063 */
064// TODO
065// @Plugin(name = "Log4j1ConfigurationFactory", category = ConfigurationFactory.CATEGORY)
066//
067// Best Value?
068// @Order(50)
069public class Log4j1ConfigurationFactory extends ConfigurationFactory {
070
071    private Map<String, String> buildClassToPropertyPrefixMap(final Properties properties,
072            final String[] sortedAppenderNames) {
073        final String prefix = "log4j.appender.";
074        final int preLength = prefix.length();
075        final Map<String, String> map = new HashMap<>(sortedAppenderNames.length);
076        for (final Entry<Object, Object> entry : properties.entrySet()) {
077            final Object keyObj = entry.getKey();
078            if (keyObj != null) {
079                final String key = keyObj.toString();
080                if (key.startsWith(prefix)) {
081                    if (key.indexOf('.', preLength) < 0) {
082                        final String name = key.substring(preLength);
083                        if (Arrays.binarySearch(sortedAppenderNames, name) >= 0) {
084                            final Object value = entry.getValue();
085                            if (value != null) {
086                                map.put(name, value.toString());
087                            }
088                        }
089                    }
090                }
091            }
092        }
093        return map;
094    }
095
096    private void buildConsoleAppender(final Properties properties, final String name,
097            final ConfigurationBuilder<BuiltConfiguration> builder) {
098        final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, "CONSOLE");
099        buildConsoleAppenderTarget(properties, name, builder, appenderBuilder);
100        buildAppenderLayout(properties, name, builder, appenderBuilder);
101        buildConsoleAppenderFollow(properties, name, builder, appenderBuilder);
102        builder.add(appenderBuilder);
103    }
104
105    private void buildAppenderLayout(final Properties properties, final String name,
106            final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) {
107        final String layoutValue = getLog4jAppenderValue(properties, name, "layout", null);
108        if (layoutValue != null) {
109            final String cpValue = getLog4jAppenderValue(properties, name, "layout.ConversionPattern", null);
110            switch (layoutValue) {
111            case "org.apache.log4j.PatternLayout": {
112                appenderBuilder.add(newPatternLayout(builder, cpValue));
113                break;
114            }
115            case "org.apache.log4j.EnhancedPatternLayout": {
116                appenderBuilder.add(newPatternLayout(builder, cpValue));
117                break;
118            }
119            case "org.apache.log4j.SimpleLayout": {
120                appenderBuilder.add(newPatternLayout(builder, "%level - %m%n"));
121                break;
122            }
123            case "org.apache.log4j.TTCCLayout": {
124                // TODO We do not have a %d for the time since the start of the app?
125                appenderBuilder.add(newPatternLayout(builder, "%relative [%threadName] %level %logger - %m%n"));
126                break;
127            }
128            case "org.apache.log4j.HTMLLayout": {
129                appenderBuilder.add(builder.newLayout("HtmlLayout"));
130                break;
131            }
132            case "org.apache.log4j.XMLLayout": {
133                appenderBuilder.add(builder.newLayout("XmlLayout"));
134                break;
135            }
136            default:
137                reportWarning("Unsupported value for console appender layout: " + layoutValue);
138            }
139        }
140    }
141
142    private LayoutComponentBuilder newPatternLayout(final ConfigurationBuilder<BuiltConfiguration> builder,
143            final String pattern) {
144        final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout");
145        if (pattern != null) {
146            layoutBuilder.addAttribute("pattern", pattern);
147        }
148        return layoutBuilder;
149    }
150
151    private void buildConsoleAppenderTarget(final Properties properties, final String name,
152            final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) {
153        final String value = getLog4jAppenderValue(properties, name, "Target", "System.out");
154        if (value != null) {
155            final Target target;
156            switch (value) {
157            case "System.out":
158                target = ConsoleAppender.Target.SYSTEM_OUT;
159                break;
160            case "System.err":
161                target = ConsoleAppender.Target.SYSTEM_ERR;
162                break;
163            default:
164                reportWarning("Unknow value for console Target: " + value);
165                target = null;
166            }
167            if (target != null) {
168                appenderBuilder.addAttribute("target", target);
169            }
170        }
171    }
172
173    private void buildConsoleAppenderFollow(final Properties properties, final String name,
174            final ConfigurationBuilder<BuiltConfiguration> builder, final AppenderComponentBuilder appenderBuilder) {
175        final String value = getLog4jAppenderValue(properties, name, "Follow", "false");
176        if (value != null) {
177            appenderBuilder.addAttribute("follow", Boolean.valueOf(value).booleanValue());
178        }
179    }
180
181    Configuration createConfiguration(final String configName, final URI configLocation,
182            final ConfigurationBuilder<BuiltConfiguration> builder) throws IOException {
183        builder.setConfigurationName(configName);
184        final Properties properties = load(configLocation);
185        if (properties == null) {
186            return null;
187        }
188        // DEBUG
189        final String debugValue = getLog4jValue(properties, "debug");
190        if (Boolean.valueOf(debugValue)) {
191            builder.setStatusLevel(Level.DEBUG);
192        }
193        // Root
194        final String[] sortedAppenderNamesC = buildRootLogger(builder, getRootCategoryValue(properties));
195        final String[] sortedAppenderNamesL = buildRootLogger(builder, getRootLoggerValue(properties));
196        final String[] sortedAppenderNames = sortedAppenderNamesL.length > 0 ? sortedAppenderNamesL
197                : sortedAppenderNamesC;
198        // Appenders
199        final Map<String, String> classNameToProperty = buildClassToPropertyPrefixMap(properties, sortedAppenderNames);
200        for (final Entry<String, String> entry : classNameToProperty.entrySet()) {
201            final String appenderName = entry.getKey();
202            switch (entry.getValue()) {
203            case "org.apache.log4j.ConsoleAppender":
204                buildConsoleAppender(properties, appenderName, builder);
205                break;
206            default:
207                reportWarning("Ignoring appender " + appenderName
208                        + "; consider porting your configuration file to the current Log4j format.");
209            }
210        }
211        // Loggers
212        buildLoggers(properties, "log4j.category.", builder);
213        buildLoggers(properties, "log4j.logger.", builder);
214        return builder.build();
215    }
216
217    private String[] buildRootLogger(final ConfigurationBuilder<BuiltConfiguration> builder,
218            final String rootLoggerValue) {
219        if (rootLoggerValue == null) {
220            return new String[0];
221        }
222        final String[] rootLoggerParts = rootLoggerValue.split("\\s*,\\s*");
223        final Level rootLoggerLevel = rootLoggerParts.length > 0 ? Level.valueOf(rootLoggerParts[0]) : Level.ERROR;
224        builder.add(builder.newRootLogger(rootLoggerLevel));
225        final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length);
226        Arrays.sort(sortedAppenderNames);
227        return sortedAppenderNames;
228    }
229
230    private void buildLoggers(final Properties properties, final String prefix,
231            final ConfigurationBuilder<BuiltConfiguration> builder) {
232        final int preLength = prefix.length();
233        for (final Entry<Object, Object> entry : properties.entrySet()) {
234            final Object keyObj = entry.getKey();
235            if (keyObj != null) {
236                final String key = keyObj.toString();
237                if (key.startsWith(prefix)) {
238                    final String name = key.substring(preLength);
239                    final Object value = entry.getValue();
240                    if (value != null) {
241                        builder.add(builder.newLogger(name, Level.valueOf(value.toString())));
242                    }
243                }
244            }
245        }
246
247    }
248
249    @Override
250    public Configuration getConfiguration(final ConfigurationSource source) {
251        return getConfiguration(source.toString(), null);
252    }
253
254    @Override
255    public Configuration getConfiguration(final String name, final URI configLocation) {
256        try {
257            return createConfiguration(name, configLocation, newConfigurationBuilder());
258        } catch (final IOException e) {
259            StatusLogger.getLogger().error(e);
260            return null;
261        }
262    }
263
264    private String getLog4jAppenderValue(final Properties properties, final String appenderName,
265            final String attributeName, final String defaultValue) {
266        return properties.getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue);
267    }
268
269    private String getLog4jValue(final Properties properties, final String key) {
270        return properties.getProperty("log4j." + key);
271    }
272
273    private String getRootCategoryValue(final Properties properties) {
274        return getLog4jValue(properties, "rootCategory");
275    }
276
277    private String getRootLoggerValue(final Properties properties) {
278        return getLog4jValue(properties, "rootLogger");
279    }
280
281    @Override
282    protected String[] getSupportedTypes() {
283        return new String[] { "*.properties", ".xml" };
284    }
285
286    private Properties load(final URI uri) throws IOException {
287        final Properties properties = toProperties(uri);
288        final String rootCategoryValue = getRootCategoryValue(properties);
289        final String rootLoggerValue = getRootLoggerValue(properties);
290        if (rootCategoryValue == null && rootLoggerValue == null) {
291            // This is not a Log4j 1 properties file.
292            return null;
293        }
294        return properties;
295    }
296
297    private void reportWarning(final String msg) {
298        StatusLogger.getLogger().warn("Log4j version 1 to 2 configuration bridge: " + msg);
299
300    }
301
302    private Properties toProperties(final URI uri) throws IOException {
303        final Properties properties;
304        try (InputStream in = uri.toURL().openStream()) {
305            properties = new Properties();
306            if (uri.toString().endsWith(".xml")) {
307                properties.loadFromXML(in);
308            } else {
309                properties.load(in);
310            }
311        }
312        return properties;
313    }
314}