1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.common;
19
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Date;
23 import java.util.LinkedList;
24 import java.util.List;
25
26 import javax.servlet.http.HttpServletRequest;
27
28 import net.bull.javamelody.JavaMelodyLogger;
29 import net.bull.javamelody.Parameter;
30
31 /**
32  * Logs des requêtes http exécutées et logs internes.
33  * @author Emeric Vernat
34  */

35 public final class LOG {
36     public static final boolean LOG4J_ENABLED = isLog4jEnabled();
37     public static final boolean LOG4J2_ENABLED = isLog4j2Enabled();
38     public static final boolean LOGBACK_ENABLED = isLogbackEnabled();
39
40     public static final int MAX_DEBUGGING_LOGS_COUNT = 50;
41
42     private static final JavaMelodyLogger JAVA_MELODY_LOGGER = getJavaMelodyLogger();
43
44     private static final LinkedList<String> DEBUGGING_LOGS = new LinkedList<>(); // NOPMD
45
46     private LOG() {
47         super();
48     }
49
50     public static void logHttpRequest(HttpServletRequest httpRequest, String requestName,
51             long duration, boolean systemError, int responseStatus, long responseSize,
52             String filterName) {
53         // dans les 3 implémentations, on ne construit le message de log
54         // que si le logger est configuré pour écrire le niveau INFO
55         JAVA_MELODY_LOGGER.logHttpRequest(httpRequest, requestName, duration, systemError,
56                 responseStatus, responseSize, filterName);
57     }
58
59     public static String buildLogMessage(HttpServletRequest httpRequest, long duration,
60             boolean systemError, int responseStatus, long responseSize) {
61         final StringBuilder msg = new StringBuilder();
62         msg.append("remoteAddr = ").append(httpRequest.getRemoteAddr());
63         final String forwardedFor = httpRequest.getHeader("X-Forwarded-For");
64         if (forwardedFor != null) {
65             msg.append(", forwardedFor = ").append(forwardedFor);
66         }
67         msg.append(", request = ").append(
68                 httpRequest.getRequestURI().substring(httpRequest.getContextPath().length()));
69         if (httpRequest.getQueryString() != null) {
70             msg.append('?').append(httpRequest.getQueryString());
71         }
72         msg.append(' ').append(httpRequest.getMethod());
73         msg.append(": ").append(duration).append(" ms");
74         if (systemError) {
75             msg.append(", error ").append(responseStatus);
76         }
77         msg.append(", ").append(responseSize / 1024L).append(" Kb");
78         return msg.toString();
79     }
80
81     public static void debug(String msg) {
82         JAVA_MELODY_LOGGER.debug(msg);
83         addDebuggingLog("DEBUG", msg);
84     }
85
86     public static void debug(String msg, Throwable throwable) {
87         JAVA_MELODY_LOGGER.debug(msg, throwable);
88         addDebuggingLog("DEBUG", msg);
89     }
90
91     public static void info(String msg, Throwable throwable) {
92         JAVA_MELODY_LOGGER.info(msg, throwable);
93         addDebuggingLog("INFO", msg);
94     }
95
96     public static void info(String msg) {
97         JAVA_MELODY_LOGGER.info(msg);
98         addDebuggingLog("INFO", msg);
99     }
100
101     public static void warn(String msg, Throwable throwable) {
102         try {
103             JAVA_MELODY_LOGGER.warn(msg, throwable);
104             addDebuggingLog("WARN", msg);
105         } catch (final Throwable t) { // NOPMD
106             // au pire (cette méthode ne doit pas lancer d'erreur vu où elle est appelée)
107             t.printStackTrace(System.err);
108         }
109     }
110
111     public static List<String> getDebuggingLogs() {
112         synchronized (DEBUGGING_LOGS) {
113             return new ArrayList<>(DEBUGGING_LOGS);
114         }
115     }
116
117     private static void addDebuggingLog(String level, String msg) {
118         synchronized (DEBUGGING_LOGS) {
119             DEBUGGING_LOGS.addLast(new Date().toString() + '\t' + level + '\t' + msg);
120             while (DEBUGGING_LOGS.size() > MAX_DEBUGGING_LOGS_COUNT) {
121                 DEBUGGING_LOGS.removeFirst();
122             }
123         }
124     }
125
126     private static boolean isLog4jEnabled() {
127         try {
128             Class.forName("org.apache.log4j.Logger");
129             // test avec AppenderSkeleton nécessaire car log4j-over-slf4j contient la classe
130             // org.apache.log4j.Logger mais pas org.apache.log4j.AppenderSkeleton
131             Class.forName("org.apache.log4j.AppenderSkeleton");
132             // test avec LocationInfo nécessaire car log4j-over-slf4j v1.7.6 inclut désormais l'AppenderSkeleton
133             Class.forName("org.apache.log4j.spi.LocationInfo");
134             return true;
135         } catch (final Throwable e) { // NOPMD
136             // catch Throwable et non catch ClassNotFoundException
137             // pour certaines configurations de JBoss qui inclut Log4J (cf issue 166)
138             return false;
139         }
140     }
141
142     private static boolean isLog4j2Enabled() {
143         try {
144             Class.forName("org.apache.logging.log4j.Logger");
145             // v2.4.1 is needed, so check ConfigurationBuilder which exists since v2.4
146             Class.forName("org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder");
147             return true;
148         } catch (final Throwable e) { // NOPMD
149             return false;
150         }
151     }
152
153     private static boolean isLogbackEnabled() {
154         try {
155             Class.forName("ch.qos.logback.classic.Logger");
156             final Class<?> loggerFactoryClass = Class.forName("org.slf4j.LoggerFactory");
157             final Method method = loggerFactoryClass.getMethod("getILoggerFactory");
158             final Object obj = method.invoke(null);
159
160             // on vérifie aussi LoggerContext car il peut arriver que getILoggerFactory ne soit pas ok (jonas)
161             return Class.forName("ch.qos.logback.classic.LoggerContext")
162                     .isAssignableFrom(obj.getClass());
163         } catch (final Throwable e) { // NOPMD
164             return false;
165         }
166     }
167
168     private static JavaMelodyLogger getJavaMelodyLogger() {
169         // si le paramètre logger-class est défini, on en prend une instance comme JavaMelodyLogger
170         final String loggerClass = Parameter.LOGGER_CLASS.getValue();
171         if (loggerClass != null) {
172             final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
173             try {
174                 return JavaMelodyLogger.class.cast(tccl.loadClass(loggerClass).newInstance());
175             } catch (final Exception e) {
176                 throw new IllegalStateException(e);
177             }
178         }
179
180         // sinon, on prend selon ce qui est présent Logback ou Log4J ou java.util.logging
181         if (LOGBACK_ENABLED) {
182             return new LogbackLogger();
183         } else if (LOG4J2_ENABLED) {
184             return new Log4J2Logger();
185         } else if (LOG4J_ENABLED) {
186             return new Log4JLogger();
187         } else {
188             return new JavaLogger();
189         }
190     }
191 }
192