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.logback13;
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 Logback13FactoryAccessor 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     * @throws NoSuchMethodException the no such method exception
44     * @throws SecurityException the security exception
45     * @throws IllegalArgumentException the illegal argument exception
46     */
47    public Logback13FactoryAccessor(ClassLoader cl)
48        throws ClassNotFoundException, IllegalAccessException, InvocationTargetException,
49        NoSuchMethodException, SecurityException, IllegalArgumentException {
50  
51      // Get the SLF4J provider binding, which may or may not be Logback, depending on the binding.
52      final List<?> providers = findServiceProviders(cl);
53      if (providers.isEmpty()) {
54        throw new RuntimeException("The SLF4J provider binding was not Logback");
55      }
56  
57      // Get the service provider
58      Object provider = providers.get(0);
59  
60      // Initialize the service provider
61      Method initialize = MethodUtils.getAccessibleMethod(provider.getClass(), "initialize");
62      initialize.invoke(provider);
63  
64      // Call the logger factory
65      Method getLoggerFactory =
66          MethodUtils.getAccessibleMethod(provider.getClass(), "getLoggerFactory");
67      Object loggerFactory = getLoggerFactory.invoke(provider);
68  
69      // Check if the binding is indeed Logback
70      Class<?> loggerFactoryClass = cl.loadClass("ch.qos.logback.classic.LoggerContext");
71      if (!loggerFactoryClass.isInstance(loggerFactory)) {
72        throw new RuntimeException("The SLF4J provider binding was not Logback");
73      }
74      setTarget(loggerFactory);
75    }
76  
77    /**
78     * Returns the Logback root logger.
79     *
80     * @return the root logger
81     */
82    public Logback13LoggerAccessor getRootLogger() {
83      // Logback has no dedicated getRootLogger() method, so we simply access the root logger
84      // by its well-defined name.
85      return getLogger("ROOT");
86    }
87  
88    /**
89     * Returns the Logback logger with a given name.
90     *
91     * @param name the name
92     *
93     * @return the Logger with the given name
94     */
95    public Logback13LoggerAccessor getLogger(String name) {
96      try {
97        Class<? extends Object> clazz = getTarget().getClass();
98        Method getLogger = MethodUtils.getAccessibleMethod(clazz, "getLogger", String.class);
99  
100       Object logger = getLogger.invoke(getTarget(), name);
101       if (logger == null) {
102         throw new NullPointerException(getTarget() + ".getLogger(\"" + name + "\") returned null");
103       }
104       Logback13LoggerAccessor accessor = new Logback13LoggerAccessor();
105       accessor.setTarget(logger);
106       accessor.setApplication(getApplication());
107       return accessor;
108 
109     } catch (Exception e) {
110       logger.error("{}.getLogger('{}') failed", getTarget(), name, e);
111     }
112     return null;
113   }
114 
115   /**
116    * Returns a list of wrappers for all Logback appenders that have an associated logger.
117    *
118    * @return a list of {@link Logback13AppenderAccessor}s representing all appenders that are in use
119    */
120   @SuppressWarnings("unchecked")
121   public List<Logback13AppenderAccessor> getAppenders() {
122     List<Logback13AppenderAccessor> appenders = new ArrayList<>();
123     try {
124       Class<? extends Object> clazz = getTarget().getClass();
125       Method getLoggerList = MethodUtils.getAccessibleMethod(clazz, "getLoggerList");
126 
127       List<Object> loggers = (List<Object>) getLoggerList.invoke(getTarget());
128       for (Object logger : loggers) {
129         Logback13LoggerAccessor accessor = new Logback13LoggerAccessor();
130         accessor.setTarget(logger);
131         accessor.setApplication(getApplication());
132 
133         appenders.addAll(accessor.getAppenders());
134       }
135     } catch (Exception e) {
136       logger.error("{}.getLoggerList() failed", getTarget(), e);
137     }
138     return appenders;
139   }
140 
141   /**
142    * Find service providers.
143    *
144    * @param cl the cl
145    *
146    * @return the list
147    *
148    * @throws NoSuchMethodException the no such method exception
149    * @throws SecurityException the security exception
150    * @throws ClassNotFoundException the class not found exception
151    * @throws IllegalAccessException the illegal access exception
152    * @throws IllegalArgumentException the illegal argument exception
153    * @throws InvocationTargetException the invocation target exception
154    */
155   private static List<?> findServiceProviders(final ClassLoader cl)
156       throws NoSuchMethodException, SecurityException, ClassNotFoundException,
157       IllegalAccessException, IllegalArgumentException, InvocationTargetException {
158     final Class<?> loggerFactory = cl.loadClass("org.slf4j.LoggerFactory");
159     final Method findServiceProviders = loggerFactory.getDeclaredMethod("findServiceProviders");
160     // Make package protected accessible
161     findServiceProviders.setAccessible(true);
162     final List<?> providers = (List<?>) findServiceProviders.invoke(null);
163     findServiceProviders.setAccessible(false);
164     return providers;
165   }
166 
167 }