View Javadoc
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  package org.apache.log4j.config;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URI;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Properties;
27  
28  import org.apache.logging.log4j.Level;
29  import org.apache.logging.log4j.core.appender.ConsoleAppender;
30  import org.apache.logging.log4j.core.appender.ConsoleAppender.Target;
31  import org.apache.logging.log4j.core.config.Configuration;
32  import org.apache.logging.log4j.core.config.ConfigurationFactory;
33  import org.apache.logging.log4j.core.config.ConfigurationSource;
34  import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
35  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
36  import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
37  import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
38  import org.apache.logging.log4j.status.StatusLogger;
39  
40  /**
41   * Experimental ConfigurationFactory for Log4j 1.2 properties files.
42   * <p>
43   * Currently supports:
44   * </p>
45   * <ul>
46   * <li>log4j.debug</li>
47   * <li>log4j.rootLogger</li>
48   * <li>log4j.logger</li>
49   * <li>log4j.appender</li>
50   * <li>org.apache.log4j.ConsoleAppender</li>
51   * <li>org.apache.log4j.PatternLayout</li>
52   * <ul>
53   * <li>Follow</li>
54   * <li>Target</li>
55   * <li>layout = org.apache.log4j.PatternLayout</li>
56   * <li>layout = org.apache.log4j.SimpleLayout</li>
57   * <li>layout = org.apache.log4j.TTCCLayout (partial)</li>
58   * <li>layout = org.apache.log4j.HtmlLayout (partial)</li>
59   * <li>layout = org.apache.log4j.XmlLayout (partial)</li>
60   * <li>layout.ConversionPattern</li>
61   * </ul>
62   * </ul>
63   */
64  // TODO
65  // @Plugin(name = "Log4j1ConfigurationFactory", category = ConfigurationFactory.CATEGORY)
66  //
67  // Best Value?
68  // @Order(50)
69  public class Log4j1ConfigurationFactory extends ConfigurationFactory {
70  
71      private Map<String, String> buildClassToPropertyPrefixMap(final Properties properties,
72              final String[] sortedAppenderNames) {
73          final String prefix = "log4j.appender.";
74          final int preLength = prefix.length();
75          final Map<String, String> map = new HashMap<>(sortedAppenderNames.length);
76          for (final Entry<Object, Object> entry : properties.entrySet()) {
77              final Object keyObj = entry.getKey();
78              if (keyObj != null) {
79                  final String key = keyObj.toString();
80                  if (key.startsWith(prefix)) {
81                      if (key.indexOf('.', preLength) < 0) {
82                          final String name = key.substring(preLength);
83                          if (Arrays.binarySearch(sortedAppenderNames, name) >= 0) {
84                              final Object value = entry.getValue();
85                              if (value != null) {
86                                  map.put(name, value.toString());
87                              }
88                          }
89                      }
90                  }
91              }
92          }
93          return map;
94      }
95  
96      private void buildConsoleAppender(final Properties properties, final String name,
97              final ConfigurationBuilder<BuiltConfiguration> builder) {
98          final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, "CONSOLE");
99          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 }