View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.tools.logging.logback;
12  
13  import java.lang.reflect.InvocationTargetException;
14  import java.lang.reflect.Method;
15  import java.util.ArrayList;
16  import java.util.List;
17  
18  import org.apache.commons.lang3.reflect.MethodUtils;
19  
20  import psiprobe.tools.logging.DefaultAccessor;
21  
22  /**
23   * Wraps a Logback logger factory from a given web application class loader.
24   *
25   * <p>
26   * All Logback classes are loaded via the given class loader and not via psi-probe's own class
27   * loader. For this reasons, all methods on Logback objects are invoked via reflection.
28   * </p>
29   * <p>
30   * This way, we can even handle different versions of Logback embedded in different WARs.
31   * </p>
32   */
33  public class LogbackFactoryAccessor extends DefaultAccessor {
34  
35    /**
36     * Attempts to initialize a Logback logger factory via the given class loader.
37     *
38     * @param cl the ClassLoader to use when fetching the factory
39     *
40     * @throws ClassNotFoundException the class not found exception
41     * @throws IllegalAccessException the illegal access exception
42     * @throws InvocationTargetException the invocation target exception
43     */
44    public LogbackFactoryAccessor(ClassLoader cl)
45        throws ClassNotFoundException, IllegalAccessException, InvocationTargetException {
46  
47      // Get the singleton SLF4J binding, which may or may not be Logback, depending on the binding.
48      Class<?> clazz = cl.loadClass("org.slf4j.impl.StaticLoggerBinder");
49      Method getSingleton = MethodUtils.getAccessibleMethod(clazz, "getSingleton");
50      Object singleton = getSingleton.invoke(null);
51      Method getLoggerFactory = MethodUtils.getAccessibleMethod(clazz, "getLoggerFactory");
52  
53      Object loggerFactory = getLoggerFactory.invoke(singleton);
54  
55      // Check if the binding is indeed Logback
56      Class<?> loggerFactoryClass = cl.loadClass("ch.qos.logback.classic.LoggerContext");
57      if (!loggerFactoryClass.isInstance(loggerFactory)) {
58        throw new RuntimeException("The singleton SLF4J binding was not Logback");
59      }
60      setTarget(loggerFactory);
61    }
62  
63    /**
64     * Returns the Logback root logger.
65     *
66     * @return the root logger
67     */
68    public LogbackLoggerAccessor getRootLogger() {
69      // Logback has no dedicated getRootLogger() method, so we simply access the root logger
70      // by its well-defined name.
71      return getLogger("ROOT");
72    }
73  
74    /**
75     * Returns the Logback logger with a given name.
76     *
77     * @param name the name
78     *
79     * @return the Logger with the given name
80     */
81    public LogbackLoggerAccessor getLogger(String name) {
82      try {
83        Class<? extends Object> clazz = getTarget().getClass();
84        Method getLogger = MethodUtils.getAccessibleMethod(clazz, "getLogger", String.class);
85  
86        Object logger = getLogger.invoke(getTarget(), name);
87        if (logger == null) {
88          throw new NullPointerException(getTarget() + ".getLogger(\"" + name + "\") returned null");
89        }
90        LogbackLoggerAccessor accessor = new LogbackLoggerAccessor();
91        accessor.setTarget(logger);
92        accessor.setApplication(getApplication());
93        return accessor;
94  
95      } catch (Exception e) {
96        logger.error("{}.getLogger('{}') failed", getTarget(), name, e);
97      }
98      return null;
99    }
100 
101   /**
102    * Returns a list of wrappers for all Logback appenders that have an associated logger.
103    *
104    * @return a list of {@link LogbackAppenderAccessor}s representing all appenders that are in use
105    */
106   @SuppressWarnings("unchecked")
107   public List<LogbackAppenderAccessor> getAppenders() {
108     List<LogbackAppenderAccessor> appenders = new ArrayList<>();
109     try {
110       Class<? extends Object> clazz = getTarget().getClass();
111       Method getLoggerList = MethodUtils.getAccessibleMethod(clazz, "getLoggerList");
112 
113       List<Object> loggers = (List<Object>) getLoggerList.invoke(getTarget());
114       for (Object logger : loggers) {
115         LogbackLoggerAccessor accessor = new LogbackLoggerAccessor();
116         accessor.setTarget(logger);
117         accessor.setApplication(getApplication());
118 
119         appenders.addAll(accessor.getAppenders());
120       }
121     } catch (Exception e) {
122       logger.error("{}.getLoggerList() failed", getTarget(), e);
123     }
124     return appenders;
125   }
126 
127 }