1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.config;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Properties;
26 import java.util.TreeMap;
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.FileAppender;
31 import org.apache.logging.log4j.core.appender.NullAppender;
32 import org.apache.logging.log4j.core.appender.RollingFileAppender;
33 import org.apache.logging.log4j.core.config.ConfigurationException;
34 import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
35 import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
36 import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
37 import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
38 import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
39 import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
40 import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
41 import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
42 import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
43 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
44 import org.apache.logging.log4j.status.StatusLogger;
45 import org.apache.logging.log4j.util.Strings;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class Log4j1ConfigurationParser {
64
65 private static final String COMMA_DELIMITED_RE = "\\s*,\\s*";
66 private static final String ROOTLOGGER = "rootLogger";
67 private static final String ROOTCATEGORY = "rootCategory";
68 private static final String TRUE = "true";
69 private static final String FALSE = "false";
70
71 private final Properties properties = new Properties();
72 private StrSubstitutor strSubstitutorProperties;
73 private StrSubstitutor strSubstitutorSystem;
74
75 private final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory
76 .newConfigurationBuilder();
77
78
79
80
81
82
83
84
85
86
87
88
89 public ConfigurationBuilder<BuiltConfiguration> buildConfigurationBuilder(final InputStream input)
90 throws IOException {
91 try {
92 properties.load(input);
93 strSubstitutorProperties = new ConfigurationStrSubstitutor(properties);
94 strSubstitutorSystem = new ConfigurationStrSubstitutor(System.getProperties());
95 final String rootCategoryValue = getLog4jValue(ROOTCATEGORY);
96 final String rootLoggerValue = getLog4jValue(ROOTLOGGER);
97 if (rootCategoryValue == null && rootLoggerValue == null) {
98
99 warn("Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
100
101
102 }
103 builder.setConfigurationName("Log4j1");
104
105 final String debugValue = getLog4jValue("debug");
106 if (Boolean.parseBoolean(debugValue)) {
107 builder.setStatusLevel(Level.DEBUG);
108 }
109
110 buildRootLogger(getLog4jValue(ROOTCATEGORY));
111 buildRootLogger(getLog4jValue(ROOTLOGGER));
112
113 final Map<String, String> appenderNameToClassName = buildClassToPropertyPrefixMap();
114 for (final Map.Entry<String, String> entry : appenderNameToClassName.entrySet()) {
115 final String appenderName = entry.getKey();
116 final String appenderClass = entry.getValue();
117 buildAppender(appenderName, appenderClass);
118 }
119
120 buildLoggers("log4j.category.");
121 buildLoggers("log4j.logger.");
122 buildProperties();
123 return builder;
124 } catch (final IllegalArgumentException e) {
125 throw new ConfigurationException(e);
126 }
127 }
128
129 private void buildProperties() {
130 for (final Map.Entry<Object, Object> entry : new TreeMap<>(properties).entrySet()) {
131 final String key = entry.getKey().toString();
132 if (!key.startsWith("log4j.") && !key.equals(ROOTCATEGORY) && !key.equals(ROOTLOGGER)) {
133 builder.addProperty(key, Objects.toString(entry.getValue(), Strings.EMPTY));
134 }
135 }
136 }
137
138 private void warn(final String string) {
139 System.err.println(string);
140 }
141
142 private Map<String, String> buildClassToPropertyPrefixMap() {
143 final String prefix = "log4j.appender.";
144 final int preLength = prefix.length();
145 final Map<String, String> map = new HashMap<>();
146 for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
147 final Object keyObj = entry.getKey();
148 if (keyObj != null) {
149 final String key = keyObj.toString();
150 if (key.startsWith(prefix)) {
151 if (key.indexOf('.', preLength) < 0) {
152 final String name = key.substring(preLength);
153 final Object value = entry.getValue();
154 if (value != null) {
155 map.put(name, value.toString());
156 }
157 }
158 }
159 }
160 }
161 return map;
162 }
163
164 private void buildAppender(final String appenderName, final String appenderClass) {
165 switch (appenderClass) {
166 case "org.apache.log4j.ConsoleAppender":
167 buildConsoleAppender(appenderName);
168 break;
169 case "org.apache.log4j.FileAppender":
170 buildFileAppender(appenderName);
171 break;
172 case "org.apache.log4j.DailyRollingFileAppender":
173 buildDailyRollingFileAppender(appenderName);
174 break;
175 case "org.apache.log4j.RollingFileAppender":
176 buildRollingFileAppender(appenderName);
177 break;
178 case "org.apache.log4j.varia.NullAppender":
179 buildNullAppender(appenderName);
180 break;
181 default:
182 reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName);
183 }
184 }
185
186 private void buildConsoleAppender(final String appenderName) {
187 final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, ConsoleAppender.PLUGIN_NAME);
188 final String targetValue = getLog4jAppenderValue(appenderName, "Target", "System.out");
189 if (targetValue != null) {
190 final ConsoleAppender.Target target;
191 switch (targetValue) {
192 case "System.out":
193 target = ConsoleAppender.Target.SYSTEM_OUT;
194 break;
195 case "System.err":
196 target = ConsoleAppender.Target.SYSTEM_ERR;
197 break;
198 default:
199 reportWarning("Unknown value for console Target: " + targetValue);
200 target = null;
201 }
202 if (target != null) {
203 appenderBuilder.addAttribute("target", target);
204 }
205 }
206 buildAttribute(appenderName, appenderBuilder, "Follow", "follow");
207 if (FALSE.equalsIgnoreCase(getLog4jAppenderValue(appenderName, "ImmediateFlush"))) {
208 reportWarning("ImmediateFlush=false is not supported on Console appender");
209 }
210 buildAppenderLayout(appenderName, appenderBuilder);
211 builder.add(appenderBuilder);
212 }
213
214 private void buildFileAppender(final String appenderName) {
215 final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, FileAppender.PLUGIN_NAME);
216 buildFileAppender(appenderName, appenderBuilder);
217 builder.add(appenderBuilder);
218 }
219
220 private void buildFileAppender(final String appenderName, final AppenderComponentBuilder appenderBuilder) {
221 buildMandatoryAttribute(appenderName, appenderBuilder, "File", "fileName");
222 buildAttribute(appenderName, appenderBuilder, "Append", "append");
223 buildAttribute(appenderName, appenderBuilder, "BufferedIO", "bufferedIo");
224 buildAttribute(appenderName, appenderBuilder, "BufferSize", "bufferSize");
225 buildAttribute(appenderName, appenderBuilder, "ImmediateFlush", "immediateFlush");
226 buildAppenderLayout(appenderName, appenderBuilder);
227 }
228
229 private void buildDailyRollingFileAppender(final String appenderName) {
230 final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName,
231 RollingFileAppender.PLUGIN_NAME);
232 buildFileAppender(appenderName, appenderBuilder);
233 final String fileName = getLog4jAppenderValue(appenderName, "File");
234 final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", fileName + "'.'yyyy-MM-dd");
235 appenderBuilder.addAttribute("filePattern", fileName + "%d{" + datePattern + "}");
236 final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
237 .addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("modulate", true));
238 appenderBuilder.addComponent(triggeringPolicy);
239 appenderBuilder
240 .addComponent(builder.newComponent("DefaultRolloverStrategy").addAttribute("max", Integer.MAX_VALUE));
241 builder.add(appenderBuilder);
242 }
243
244 private void buildRollingFileAppender(final String appenderName) {
245 final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName,
246 RollingFileAppender.PLUGIN_NAME);
247 buildFileAppender(appenderName, appenderBuilder);
248 final String fileName = getLog4jAppenderValue(appenderName, "File");
249 appenderBuilder.addAttribute("filePattern", fileName + ".%i");
250 final String maxFileSizeString = getLog4jAppenderValue(appenderName, "MaxFileSize", "10485760");
251 final String maxBackupIndexString = getLog4jAppenderValue(appenderName, "MaxBackupIndex", "1");
252 final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies").addComponent(
253 builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString));
254 appenderBuilder.addComponent(triggeringPolicy);
255 appenderBuilder.addComponent(
256 builder.newComponent("DefaultRolloverStrategy").addAttribute("max", maxBackupIndexString));
257 builder.add(appenderBuilder);
258 }
259
260 private void buildAttribute(final String componentName, final ComponentBuilder componentBuilder,
261 final String sourceAttributeName, final String targetAttributeName) {
262 final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
263 if (attributeValue != null) {
264 componentBuilder.addAttribute(targetAttributeName, attributeValue);
265 }
266 }
267
268 private void buildAttributeWithDefault(final String componentName, final ComponentBuilder componentBuilder,
269 final String sourceAttributeName, final String targetAttributeName, final String defaultValue) {
270 final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName, defaultValue);
271 componentBuilder.addAttribute(targetAttributeName, attributeValue);
272 }
273
274 private void buildMandatoryAttribute(final String componentName, final ComponentBuilder componentBuilder,
275 final String sourceAttributeName, final String targetAttributeName) {
276 final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
277 if (attributeValue != null) {
278 componentBuilder.addAttribute(targetAttributeName, attributeValue);
279 } else {
280 reportWarning("Missing " + sourceAttributeName + " for " + componentName);
281 }
282 }
283
284 private void buildNullAppender(final String appenderName) {
285 final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, NullAppender.PLUGIN_NAME);
286 builder.add(appenderBuilder);
287 }
288
289 private void buildAppenderLayout(final String name, final AppenderComponentBuilder appenderBuilder) {
290 final String layoutClass = getLog4jAppenderValue(name, "layout", null);
291 if (layoutClass != null) {
292 switch (layoutClass) {
293 case "org.apache.log4j.PatternLayout":
294 case "org.apache.log4j.EnhancedPatternLayout": {
295 final String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null)
296
297
298
299
300
301
302 .replace("%x", "%ndc")
303
304
305
306
307
308
309 .replace("%X", "%properties");
310
311 appenderBuilder.add(newPatternLayout(pattern));
312 break;
313 }
314 case "org.apache.log4j.SimpleLayout": {
315 appenderBuilder.add(newPatternLayout("%level - %m%n"));
316 break;
317 }
318 case "org.apache.log4j.TTCCLayout": {
319 String pattern = "%r ";
320 if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) {
321 pattern += "[%t] ";
322 }
323 pattern += "%p ";
324 if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) {
325 pattern += "%c ";
326 }
327 if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) {
328 pattern += "%notEmpty{%ndc }";
329 }
330 pattern += "- %m%n";
331 appenderBuilder.add(newPatternLayout(pattern));
332 break;
333 }
334 case "org.apache.log4j.HTMLLayout": {
335 final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout");
336 htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages"));
337 htmlLayout.addAttribute("locationInfo",
338 Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
339 appenderBuilder.add(htmlLayout);
340 break;
341 }
342 case "org.apache.log4j.xml.XMLLayout": {
343 final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout");
344 xmlLayout.addAttribute("locationInfo",
345 Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE)));
346 xmlLayout.addAttribute("properties",
347 Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE)));
348 appenderBuilder.add(xmlLayout);
349 break;
350 }
351 default:
352 reportWarning("Unknown layout class: " + layoutClass);
353 }
354 }
355 }
356
357 private LayoutComponentBuilder newPatternLayout(final String pattern) {
358 final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout");
359 if (pattern != null) {
360 layoutBuilder.addAttribute("pattern", pattern);
361 }
362 return layoutBuilder;
363 }
364
365 private void buildRootLogger(final String rootLoggerValue) {
366 if (rootLoggerValue == null) {
367 return;
368 }
369 final String[] rootLoggerParts = rootLoggerValue.split(COMMA_DELIMITED_RE);
370 final String rootLoggerLevel = getLevelString(rootLoggerParts, Level.ERROR.name());
371 final RootLoggerComponentBuilder loggerBuilder = builder.newRootLogger(rootLoggerLevel);
372
373 final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length);
374 Arrays.sort(sortedAppenderNames);
375 for (final String appender : sortedAppenderNames) {
376 loggerBuilder.add(builder.newAppenderRef(appender));
377 }
378 builder.add(loggerBuilder);
379 }
380
381 private String getLevelString(final String[] loggerParts, final String defaultLevel) {
382 return loggerParts.length > 0 ? loggerParts[0] : defaultLevel;
383 }
384
385 private void buildLoggers(final String prefix) {
386 final int preLength = prefix.length();
387 for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
388 final Object keyObj = entry.getKey();
389 if (keyObj != null) {
390 final String key = keyObj.toString();
391 if (key.startsWith(prefix)) {
392 final String name = key.substring(preLength);
393 final Object value = entry.getValue();
394 if (value != null) {
395
396 final String valueStr = value.toString();
397 final String[] split = valueStr.split(COMMA_DELIMITED_RE);
398 final String level = getLevelString(split, null);
399 if (level == null) {
400 warn("Level is missing for entry " + entry);
401 } else {
402 final LoggerComponentBuilder newLogger = builder.newLogger(name, level);
403 if (split.length > 1) {
404
405 final String[] sortedAppenderNames = Arrays.copyOfRange(split, 1, split.length);
406 Arrays.sort(sortedAppenderNames);
407 for (final String appenderName : sortedAppenderNames) {
408 newLogger.add(builder.newAppenderRef(appenderName));
409 }
410 }
411 builder.add(newLogger);
412 }
413 }
414 }
415 }
416 }
417 }
418
419 private String getLog4jAppenderValue(final String appenderName, final String attributeName) {
420 return getProperty("log4j.appender." + appenderName + "." + attributeName);
421 }
422
423 private String getProperty(final String key) {
424 final String value = properties.getProperty(key);
425 final String sysValue = strSubstitutorSystem.replace(value);
426 return strSubstitutorProperties.replace(sysValue);
427 }
428
429 private String getProperty(final String key, final String defaultValue) {
430 final String value = getProperty(key);
431 return value == null ? defaultValue : value;
432 }
433
434 private String getLog4jAppenderValue(final String appenderName, final String attributeName,
435 final String defaultValue) {
436 return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue);
437 }
438
439 private String getLog4jValue(final String key) {
440 return getProperty("log4j." + key);
441 }
442
443 private void reportWarning(final String msg) {
444 StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg);
445 }
446
447 }