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