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.lang.reflect.InvocationTargetException;
22 import java.util.ArrayList;
23 import java.util.Enumeration;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Properties;
29 import java.util.SortedMap;
30 import java.util.StringTokenizer;
31 import java.util.TreeMap;
32
33 import org.apache.log4j.Appender;
34 import org.apache.log4j.Layout;
35 import org.apache.log4j.LogManager;
36 import org.apache.log4j.PatternLayout;
37 import org.apache.log4j.bridge.AppenderAdapter;
38 import org.apache.log4j.bridge.AppenderWrapper;
39 import org.apache.log4j.helpers.OptionConverter;
40 import org.apache.log4j.spi.ErrorHandler;
41 import org.apache.log4j.spi.Filter;
42 import org.apache.logging.log4j.core.LoggerContext;
43 import org.apache.logging.log4j.core.config.Configuration;
44 import org.apache.logging.log4j.core.config.ConfigurationSource;
45 import org.apache.logging.log4j.core.config.LoggerConfig;
46 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
47 import org.apache.logging.log4j.util.LoaderUtil;
48
49
50
51
52 public class PropertiesConfiguration extends Log4j1Configuration {
53
54 private static final String CATEGORY_PREFIX = "log4j.category.";
55 private static final String LOGGER_PREFIX = "log4j.logger.";
56 private static final String ADDITIVITY_PREFIX = "log4j.additivity.";
57 private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
58 private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
59 private static final String APPENDER_PREFIX = "log4j.appender.";
60 private static final String LOGGER_REF = "logger-ref";
61 private static final String ROOT_REF = "root-ref";
62 private static final String APPENDER_REF_TAG = "appender-ref";
63 public static final long DEFAULT_DELAY = 60000;
64 public static final String DEBUG_KEY="log4j.debug";
65
66 private static final String INTERNAL_ROOT_NAME = "root";
67
68 private final Map<String, Appender> registry;
69
70
71
72
73
74
75
76 public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
77 int monitorIntervalSeconds) {
78 super(loggerContext, source, monitorIntervalSeconds);
79 registry = new HashMap<>();
80 }
81
82 @Override
83 public void doConfigure() {
84 InputStream is = getConfigurationSource().getInputStream();
85 Properties props = new Properties();
86 try {
87 props.load(is);
88 } catch (Exception e) {
89 LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
90 return;
91 }
92
93 doConfigure(props);
94 }
95
96 @Override
97 public Configuration reconfigure() {
98 try {
99 final ConfigurationSource source = getConfigurationSource().resetInputStream();
100 if (source == null) {
101 return null;
102 }
103 final PropertiesConfigurationFactory factory = new PropertiesConfigurationFactory();
104 final PropertiesConfiguration config =
105 (PropertiesConfiguration) factory.getConfiguration(getLoggerContext(), source);
106 return config == null || config.getState() != State.INITIALIZING ? null : config;
107 } catch (final IOException ex) {
108 LOGGER.error("Cannot locate file {}: {}", getConfigurationSource(), ex);
109 }
110 return null;
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 private void doConfigure(Properties properties) {
287 String status = "error";
288 String value = properties.getProperty(DEBUG_KEY);
289 if (value == null) {
290 value = properties.getProperty("log4j.configDebug");
291 if (value != null) {
292 LOGGER.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
293 }
294 }
295
296 if (value != null) {
297 status = OptionConverter.toBoolean(value, false) ? "debug" : "error";
298 }
299
300 final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
301 statusConfig.initialize();
302
303 configureRoot(properties);
304 parseLoggers(properties);
305
306 LOGGER.debug("Finished configuring.");
307 }
308
309
310
311
312
313 private void configureRoot(Properties props) {
314 String effectiveFrefix = ROOT_LOGGER_PREFIX;
315 String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
316
317 if (value == null) {
318 value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
319 effectiveFrefix = ROOT_CATEGORY_PREFIX;
320 }
321
322 if (value == null) {
323 LOGGER.debug("Could not find root logger information. Is this OK?");
324 } else {
325 LoggerConfig root = getRootLogger();
326 parseLogger(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
327 }
328 }
329
330
331
332
333 private void parseLoggers(Properties props) {
334 Enumeration<?> enumeration = props.propertyNames();
335 while (enumeration.hasMoreElements()) {
336 String key = Objects.toString(enumeration.nextElement(), null);
337 if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
338 String loggerName = null;
339 if (key.startsWith(CATEGORY_PREFIX)) {
340 loggerName = key.substring(CATEGORY_PREFIX.length());
341 } else if (key.startsWith(LOGGER_PREFIX)) {
342 loggerName = key.substring(LOGGER_PREFIX.length());
343 }
344 String value = OptionConverter.findAndSubst(key, props);
345 LoggerConfig loggerConfig = getLogger(loggerName);
346 if (loggerConfig == null) {
347 boolean additivity = getAdditivityForLogger(props, loggerName);
348 loggerConfig = new LoggerConfig(loggerName, org.apache.logging.log4j.Level.ERROR, additivity);
349 addLogger(loggerName, loggerConfig);
350 }
351 parseLogger(props, loggerConfig, key, loggerName, value);
352 }
353 }
354 }
355
356
357
358
359 private boolean getAdditivityForLogger(Properties props, String loggerName) {
360 boolean additivity = true;
361 String key = ADDITIVITY_PREFIX + loggerName;
362 String value = OptionConverter.findAndSubst(key, props);
363 LOGGER.debug("Handling {}=[{}]", key, value);
364
365 if ((value != null) && (!value.equals(""))) {
366 additivity = OptionConverter.toBoolean(value, true);
367 }
368 return additivity;
369 }
370
371
372
373
374 private void parseLogger(Properties props, LoggerConfig logger, String optionKey, String loggerName, String value) {
375
376 LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value);
377
378 StringTokenizer st = new StringTokenizer(value, ",");
379
380
381
382 if (!(value.startsWith(",") || value.equals(""))) {
383
384
385 if (!st.hasMoreTokens()) {
386 return;
387 }
388
389 String levelStr = st.nextToken();
390 LOGGER.debug("Level token is [{}].", levelStr);
391
392 org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR :
393 OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
394 logger.setLevel(level);
395 LOGGER.debug("Logger {} level set to {}", loggerName, level);
396 }
397
398 Appender appender;
399 String appenderName;
400 while (st.hasMoreTokens()) {
401 appenderName = st.nextToken().trim();
402 if (appenderName == null || appenderName.equals(",")) {
403 continue;
404 }
405 LOGGER.debug("Parsing appender named \"{}\".", appenderName);
406 appender = parseAppender(props, appenderName);
407 if (appender != null) {
408 LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName,
409 logger.getName());
410 logger.addAppender(getAppender(appenderName), null, null);
411 } else {
412 LOGGER.debug("Appender named [{}] not found.", appenderName);
413 }
414 }
415 }
416
417 public Appender parseAppender(Properties props, String appenderName) {
418 Appender appender = registry.get(appenderName);
419 if ((appender != null)) {
420 LOGGER.debug("Appender \"" + appenderName + "\" was already parsed.");
421 return appender;
422 }
423
424 final String prefix = APPENDER_PREFIX + appenderName;
425 final String layoutPrefix = prefix + ".layout";
426 final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
427 String className = OptionConverter.findAndSubst(prefix, props);
428 appender = manager.parseAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props, this);
429 if (appender == null) {
430 appender = buildAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props);
431 } else {
432 registry.put(appenderName, appender);
433 if (appender instanceof AppenderWrapper) {
434 addAppender(((AppenderWrapper) appender).getAppender());
435 } else {
436 addAppender(new AppenderAdapter(appender).getAdapter());
437 }
438 }
439 return appender;
440 }
441
442 private Appender buildAppender(final String appenderName, final String className, final String prefix,
443 final String layoutPrefix, final String filterPrefix, final Properties props) {
444 Appender appender = newInstanceOf(className, "Appender");
445 if (appender == null) {
446 return null;
447 }
448 appender.setName(appenderName);
449 appender.setLayout(parseLayout(layoutPrefix, appenderName, props));
450 final String errorHandlerPrefix = prefix + ".errorhandler";
451 String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
452 if (errorHandlerClass != null) {
453 ErrorHandler eh = parseErrorHandler(props, errorHandlerPrefix, errorHandlerClass, appender);
454 if (eh != null) {
455 appender.setErrorHandler(eh);
456 }
457 }
458 parseAppenderFilters(props, filterPrefix, appenderName);
459 String[] keys = new String[] {
460 layoutPrefix,
461 };
462 addProperties(appender, keys, props, prefix);
463 if (appender instanceof AppenderWrapper) {
464 addAppender(((AppenderWrapper) appender).getAppender());
465 } else {
466 addAppender(new AppenderAdapter(appender).getAdapter());
467 }
468 registry.put(appenderName, appender);
469 return appender;
470 }
471
472 public Layout parseLayout(String layoutPrefix, String appenderName, Properties props) {
473 String layoutClass = OptionConverter.findAndSubst(layoutPrefix, props);
474 if (layoutClass == null) {
475 return null;
476 }
477 Layout layout = manager.parseLayout(layoutClass, layoutPrefix, props, this);
478 if (layout == null) {
479 layout = buildLayout(layoutPrefix, layoutClass, appenderName, props);
480 }
481 return layout;
482 }
483
484 private Layout buildLayout(String layoutPrefix, String className, String appenderName, Properties props) {
485 Layout layout = newInstanceOf(className, "Layout");
486 if (layout == null) {
487 return null;
488 }
489 LOGGER.debug("Parsing layout options for \"{}\".", appenderName);
490 PropertySetter.setProperties(layout, props, layoutPrefix + ".");
491 LOGGER.debug("End of parsing for \"{}\".", appenderName);
492 return layout;
493 }
494
495 public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix,
496 final String errorHandlerClass, final Appender appender) {
497 ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler");
498 final String[] keys = new String[] {
499 errorHandlerPrefix + "." + ROOT_REF,
500 errorHandlerPrefix + "." + LOGGER_REF,
501 errorHandlerPrefix + "." + APPENDER_REF_TAG
502 };
503 addProperties(eh, keys, props, errorHandlerPrefix);
504 return eh;
505 }
506
507 public void addProperties(final Object obj, final String[] keys, final Properties props, final String prefix) {
508 final Properties edited = new Properties();
509 props.stringPropertyNames().stream().filter(name -> {
510 if (name.startsWith(prefix)) {
511 for (String key : keys) {
512 if (name.equals(key)) {
513 return false;
514 }
515 }
516 return true;
517 }
518 return false;
519 }).forEach(name -> edited.put(name, props.getProperty(name)));
520 PropertySetter.setProperties(obj, edited, prefix + ".");
521 }
522
523
524 public Filter parseAppenderFilters(Properties props, String filterPrefix, String appenderName) {
525
526
527
528 int fIdx = filterPrefix.length();
529 SortedMap<String, List<NameValue>> filters = new TreeMap<>();
530 Enumeration<?> e = props.keys();
531 String name = "";
532 while (e.hasMoreElements()) {
533 String key = (String) e.nextElement();
534 if (key.startsWith(filterPrefix)) {
535 int dotIdx = key.indexOf('.', fIdx);
536 String filterKey = key;
537 if (dotIdx != -1) {
538 filterKey = key.substring(0, dotIdx);
539 name = key.substring(dotIdx + 1);
540 }
541 List<NameValue> filterOpts = filters.computeIfAbsent(filterKey, k -> new ArrayList<>());
542 if (dotIdx != -1) {
543 String value = OptionConverter.findAndSubst(key, props);
544 filterOpts.add(new NameValue(name, value));
545 }
546 }
547 }
548
549 Filter head = null;
550 Filter next = null;
551 for (Map.Entry<String, List<NameValue>> entry : filters.entrySet()) {
552 String clazz = props.getProperty(entry.getKey());
553 Filter filter = null;
554 if (clazz != null) {
555 filter = manager.parseFilter(clazz, filterPrefix, props, this);
556 if (filter == null) {
557 LOGGER.debug("Filter key: [{}] class: [{}] props: {}", entry.getKey(), clazz, entry.getValue());
558 filter = buildFilter(clazz, appenderName, entry.getValue());
559 }
560 }
561 if (filter != null) {
562 if (head == null) {
563 head = filter;
564 } else {
565 next.setNext(filter);
566 }
567 next = filter;
568 }
569 }
570 return head;
571 }
572
573 private Filter buildFilter(String className, String appenderName, List<NameValue> props) {
574 Filter filter = newInstanceOf(className, "Filter");
575 if (filter != null) {
576 PropertySetter propSetter = new PropertySetter(filter);
577 for (NameValue property : props) {
578 propSetter.setProperty(property.key, property.value);
579 }
580 propSetter.activate();
581 }
582 return filter;
583 }
584
585
586 private static <T> T newInstanceOf(String className, String type) {
587 try {
588 return LoaderUtil.newInstanceOf(className);
589 } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException |
590 InstantiationException | InvocationTargetException ex) {
591 LOGGER.error("Unable to create {} {} due to {}:{}", type, className,
592 ex.getClass().getSimpleName(), ex.getMessage());
593 return null;
594 }
595 }
596
597 private static class NameValue {
598 String key, value;
599
600 NameValue(String key, String value) {
601 this.key = key;
602 this.value = value;
603 }
604
605 @Override
606 public String toString() {
607 return key + "=" + value;
608 }
609 }
610
611 }