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 }