1
18 package net.bull.javamelody;
19
20 import java.io.IOException;
21 import java.io.PrintWriter;
22 import java.io.StringWriter;
23 import java.net.URL;
24 import java.net.URLEncoder;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.regex.Pattern;
29
30 import javax.servlet.Filter;
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletException;
34 import javax.servlet.ServletRequest;
35 import javax.servlet.ServletResponse;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38 import javax.servlet.http.HttpSession;
39
40 import net.bull.javamelody.internal.common.HttpParameter;
41 import net.bull.javamelody.internal.common.HttpPart;
42 import net.bull.javamelody.internal.common.LOG;
43 import net.bull.javamelody.internal.common.Parameters;
44 import net.bull.javamelody.internal.model.Collector;
45 import net.bull.javamelody.internal.model.Counter;
46 import net.bull.javamelody.internal.model.CounterError;
47 import net.bull.javamelody.internal.model.CounterRequestContext;
48 import net.bull.javamelody.internal.model.LabradorRetriever;
49 import net.bull.javamelody.internal.model.ThreadInformations;
50 import net.bull.javamelody.internal.web.CounterServletResponseWrapper;
51 import net.bull.javamelody.internal.web.HttpAuth;
52 import net.bull.javamelody.internal.web.MonitoringController;
53 import net.bull.javamelody.internal.web.RumInjector;
54
55
60 public class MonitoringFilter implements Filter {
61
62 private static boolean instanceCreated;
63
64 private static final List<String> CONTEXT_PATHS = new ArrayList<>();
65
66 private static URL unregisterApplicationNodeInCollectServerUrl;
67
68 private boolean instanceEnabled;
69
70
71
72 private String applicationType = "Classic";
73
74
75
76 private Counter httpCounter;
77 private Counter errorCounter;
78
79 private boolean monitoringDisabled;
80 private boolean logEnabled;
81 private boolean rumEnabled;
82 private Pattern urlExcludePattern;
83 private FilterContext filterContext;
84 private HttpAuth httpAuth;
85 private FilterConfig filterConfig;
86 private String monitoringUrl;
87 private boolean servletApi2;
88
89
92 public MonitoringFilter() {
93 super();
94 if (instanceCreated) {
95
96
97
98
99 instanceEnabled = false;
100 } else {
101 instanceEnabled = true;
102 setInstanceCreated(true);
103 }
104 }
105
106 private static void setInstanceCreated(boolean newInstanceCreated) {
107 instanceCreated = newInstanceCreated;
108 }
109
110
113 public String getApplicationType() {
114 return applicationType;
115 }
116
117
120 public void setApplicationType(final String applicationType) {
121 this.applicationType = applicationType;
122 }
123
124
125 @Override
126 public void init(FilterConfig config) throws ServletException {
127 final long start = System.currentTimeMillis();
128 final String contextPath = Parameters.getContextPath(config.getServletContext());
129 if (!instanceEnabled) {
130 if (!CONTEXT_PATHS.contains(contextPath)) {
131
132 instanceEnabled = true;
133 } else {
134 return;
135 }
136 }
137 CONTEXT_PATHS.add(contextPath);
138 this.filterConfig = config;
139 this.servletApi2 = config.getServletContext().getMajorVersion() < 3;
140 Parameters.initialize(config);
141 monitoringDisabled = Parameter.DISABLED.getValueAsBoolean();
142 if (monitoringDisabled) {
143 return;
144 }
145
146 LOG.debug("JavaMelody filter init started");
147
148 this.filterContext = new FilterContext(getApplicationType());
149 this.httpAuth = new HttpAuth();
150 config.getServletContext().setAttribute(ReportServlet.FILTER_CONTEXT_KEY, filterContext);
151 final Collector collector = filterContext.getCollector();
152 this.httpCounter = collector.getCounterByName(Counter.HTTP_COUNTER_NAME);
153 this.errorCounter = collector.getCounterByName(Counter.ERROR_COUNTER_NAME);
154
155 logEnabled = Parameter.LOG.getValueAsBoolean();
156 rumEnabled = Parameter.RUM_ENABLED.getValueAsBoolean();
157 if (Parameter.URL_EXCLUDE_PATTERN.getValue() != null) {
158
159 urlExcludePattern = Pattern.compile(Parameter.URL_EXCLUDE_PATTERN.getValue());
160 }
161
162 final long duration = System.currentTimeMillis() - start;
163 LOG.debug("JavaMelody filter init done in " + duration + " ms");
164 }
165
166
167 @Override
168 public void destroy() {
169 if (monitoringDisabled || !instanceEnabled) {
170 return;
171 }
172 final long start = System.currentTimeMillis();
173
174 try {
175 if (filterContext != null) {
176 filterContext.destroy();
177 }
178 } finally {
179 final String contextPath = Parameters.getContextPath(filterConfig.getServletContext());
180 CONTEXT_PATHS.remove(contextPath);
181
182 httpCounter = null;
183 errorCounter = null;
184 urlExcludePattern = null;
185 filterConfig = null;
186 filterContext = null;
187 }
188 final long duration = System.currentTimeMillis() - start;
189 LOG.debug("JavaMelody filter destroy done in " + duration + " ms");
190 }
191
192
193 @Override
194 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
195 throws IOException, ServletException {
196 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)
197 || monitoringDisabled || !instanceEnabled) {
198
199 chain.doFilter(request, response);
200 return;
201 }
202 final HttpServletRequest httpRequest = (HttpServletRequest) request;
203 final HttpServletResponse httpResponse = (HttpServletResponse) response;
204
205 if (httpRequest.getRequestURI().equals(getMonitoringUrl(httpRequest))) {
206 doMonitoring(httpRequest, httpResponse);
207 return;
208 }
209 if (!httpCounter.isDisplayed() || isRequestExcluded((HttpServletRequest) request)) {
210
211 chain.doFilter(request, response);
212 return;
213 }
214
215 doFilter(chain, httpRequest, httpResponse);
216 }
217
218 private void doFilter(FilterChain chain, HttpServletRequest httpRequest,
219 HttpServletResponse httpResponse) throws IOException, ServletException {
220 final long start = System.currentTimeMillis();
221 final long startCpuTime = ThreadInformations.getCurrentThreadCpuTime();
222 final long startAllocatedBytes = ThreadInformations.getCurrentThreadAllocatedBytes();
223 final CounterServletResponseWrapper wrappedResponse = createResponseWrapper(httpRequest,
224 httpResponse);
225 final HttpServletRequest wrappedRequest = createRequestWrapper(httpRequest,
226 wrappedResponse);
227 boolean systemError = false;
228 Throwable systemException = null;
229 String requestName = getRequestName(wrappedRequest);
230 final String completeRequestName = getCompleteRequestName(wrappedRequest, true);
231 try {
232 JdbcWrapper.ACTIVE_THREAD_COUNT.incrementAndGet();
233
234 httpCounter.bindContext(requestName, completeRequestName, httpRequest, startCpuTime,
235 startAllocatedBytes);
236
237 httpRequest.setAttribute(CounterError.REQUEST_KEY, completeRequestName);
238 CounterError.bindRequest(httpRequest);
239 chain.doFilter(wrappedRequest, wrappedResponse);
240 if (servletApi2 || !httpRequest.isAsyncStarted()) {
241 wrappedResponse.flushStream();
242 }
243 } catch (final Throwable t) {
244
245 systemException = t;
246 throwException(t);
247 } finally {
248 if (httpCounter == null) {
249
250
251
252 return;
253 }
254 try {
255
256
257
258
259
260
261
262
263 final long duration = Math.max(System.currentTimeMillis() - start, 0);
264 final int cpuUsedMillis = (int) ((ThreadInformations.getCurrentThreadCpuTime()
265 - startCpuTime) / 1000000L);
266 final int allocatedKBytes;
267 if (startAllocatedBytes >= 0) {
268 allocatedKBytes = (int) ((ThreadInformations.getCurrentThreadAllocatedBytes()
269 - startAllocatedBytes) / 1024L);
270 } else {
271 allocatedKBytes = -1;
272 }
273 JdbcWrapper.ACTIVE_THREAD_COUNT.decrementAndGet();
274 putUserInfoInSession(httpRequest);
275 if (systemException != null) {
276 systemError = true;
277 final StringWriter stackTrace = new StringWriter(200);
278 systemException.printStackTrace(new PrintWriter(stackTrace));
279 errorCounter.addRequestForSystemError(systemException.toString(), duration,
280 cpuUsedMillis, allocatedKBytes, stackTrace.toString());
281 } else if (wrappedResponse.getCurrentStatus() >= HttpServletResponse.SC_BAD_REQUEST
282 && wrappedResponse
283 .getCurrentStatus() != HttpServletResponse.SC_UNAUTHORIZED) {
284
285 systemError = true;
286 errorCounter.addRequestForSystemError(
287 "Error" + wrappedResponse.getCurrentStatus(), duration, cpuUsedMillis,
288 allocatedKBytes, null);
289 }
290
291 requestName = CounterRequestContext.getHttpRequestName(httpRequest, requestName);
292
293 final long responseSize = wrappedResponse.getDataLength();
294
295 if (wrappedResponse.getCurrentStatus() == HttpServletResponse.SC_NOT_FOUND) {
296
297
298 requestName = "Error404";
299 }
300
301
302 httpCounter.addRequest(requestName, duration, cpuUsedMillis, allocatedKBytes,
303 systemError, responseSize);
304
305 log(httpRequest, requestName, duration, systemError,
306 wrappedResponse.getCurrentStatus(), responseSize);
307 } finally {
308
309
310
311
312 httpCounter.unbindContext();
313
314 CounterError.unbindRequest();
315 }
316 }
317 }
318
319 protected CounterServletResponseWrapper createResponseWrapper(HttpServletRequest httpRequest,
320 HttpServletResponse httpResponse) {
321 HttpServletResponse httpResponse2 = httpResponse;
322 if (rumEnabled) {
323 httpResponse2 = RumInjector.createRumResponseWrapper(httpRequest, httpResponse,
324 getRequestName(httpRequest));
325 }
326 return new CounterServletResponseWrapper(httpResponse2);
327 }
328
329 protected HttpServletRequest createRequestWrapper(HttpServletRequest request,
330 HttpServletResponse response) throws IOException {
331 HttpServletRequest wrappedRequest = JspWrapper.createHttpRequestWrapper(request, response);
332 final PayloadNameRequestWrapper payloadNameRequestWrapper = new PayloadNameRequestWrapper(
333 wrappedRequest);
334 payloadNameRequestWrapper.initialize();
335 if (payloadNameRequestWrapper.getPayloadRequestType() != null) {
336 wrappedRequest = payloadNameRequestWrapper;
337 }
338 return wrappedRequest;
339 }
340
341 protected String getRequestName(HttpServletRequest request) {
342 return getCompleteRequestName(request, false);
343 }
344
345 protected String getMonitoringUrl(HttpServletRequest httpRequest) {
346 if (monitoringUrl == null) {
347 monitoringUrl = httpRequest.getContextPath() + Parameters.getMonitoringPath();
348 }
349 return monitoringUrl;
350 }
351
352 private void putUserInfoInSession(HttpServletRequest httpRequest) {
353 final HttpSession session = httpRequest.getSession(false);
354 if (session == null) {
355
356 return;
357 }
358
359
360
361
362
363 if (session.getAttribute(SessionListener.SESSION_COUNTRY_KEY) == null) {
364
365 final Locale locale = httpRequest.getLocale();
366 if (!locale.getCountry().isEmpty()) {
367 session.setAttribute(SessionListener.SESSION_COUNTRY_KEY, locale.getCountry());
368 } else {
369 session.setAttribute(SessionListener.SESSION_COUNTRY_KEY, locale.getLanguage());
370 }
371 }
372 if (session.getAttribute(SessionListener.SESSION_REMOTE_ADDR) == null) {
373
374 final String forwardedFor = httpRequest.getHeader("X-Forwarded-For");
375 final String remoteAddr;
376 if (forwardedFor == null) {
377 remoteAddr = httpRequest.getRemoteAddr();
378 } else {
379 remoteAddr = httpRequest.getRemoteAddr() + " forwarded for " + forwardedFor;
380 }
381 session.setAttribute(SessionListener.SESSION_REMOTE_ADDR, remoteAddr);
382 }
383 if (session.getAttribute(SessionListener.SESSION_REMOTE_USER) == null) {
384
385 final String remoteUser = httpRequest.getRemoteUser();
386 if (remoteUser != null) {
387 session.setAttribute(SessionListener.SESSION_REMOTE_USER, remoteUser);
388 }
389 }
390 if (session.getAttribute(SessionListener.SESSION_USER_AGENT) == null) {
391 final String userAgent = httpRequest.getHeader("User-Agent");
392 session.setAttribute(SessionListener.SESSION_USER_AGENT, userAgent);
393 }
394 }
395
396 private void doMonitoring(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
397 throws IOException, ServletException {
398 if (isRumMonitoring(httpRequest, httpResponse)) {
399 return;
400 }
401
402 if (!isAllowed(httpRequest, httpResponse)) {
403 return;
404 }
405
406 final Collector collector = filterContext.getCollector();
407 final MonitoringController monitoringController = new MonitoringController(collector, null);
408 monitoringController.doActionIfNeededAndReport(httpRequest, httpResponse,
409 filterConfig.getServletContext());
410
411 if ("stop".equalsIgnoreCase(HttpParameter.COLLECTOR.getParameterFrom(httpRequest))) {
412
413
414
415 for (final Counter counter : collector.getCounters()) {
416 counter.clear();
417 }
418
419 if (!collector.isStopped()) {
420 LOG.debug(
421 "Stopping the javamelody collector in this webapp, because a collector server from "
422 + httpRequest.getRemoteAddr()
423 + " wants to collect the data itself");
424 filterContext.stopCollector();
425 }
426 }
427 }
428
429 protected final boolean isRumMonitoring(HttpServletRequest httpRequest,
430 HttpServletResponse httpResponse) throws IOException {
431 if (rumEnabled) {
432
433 if (RumInjector.isRumResource(HttpParameter.RESOURCE.getParameterFrom(httpRequest))) {
434
435 MonitoringController.doResource(httpResponse,
436 HttpParameter.RESOURCE.getParameterFrom(httpRequest));
437 return true;
438 } else if (HttpPart.RUM.isPart(httpRequest)) {
439
440 MonitoringController.noCache(httpResponse);
441 httpResponse.setContentType("image/png");
442 RumInjector.addRumHit(httpRequest, httpCounter);
443 return true;
444 }
445 }
446 return false;
447 }
448
449 private static String getCompleteRequestName(HttpServletRequest httpRequest,
450 boolean includeQueryString) {
451
452
453
454
455 String tmp = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
456
457
458
459 final int lastIndexOfSemiColon = tmp.lastIndexOf(';');
460 if (lastIndexOfSemiColon != -1) {
461 tmp = tmp.substring(0, lastIndexOfSemiColon);
462 }
463 final String method;
464 if ("XMLHttpRequest".equals(httpRequest.getHeader("X-Requested-With"))) {
465 method = "ajax " + httpRequest.getMethod();
466 } else {
467 method = httpRequest.getMethod();
468 }
469 if (!includeQueryString) {
470
471 if (httpRequest instanceof PayloadNameRequestWrapper) {
472 final PayloadNameRequestWrapper wrapper = (PayloadNameRequestWrapper) httpRequest;
473 return tmp + wrapper.getPayloadRequestName() + ' '
474 + wrapper.getPayloadRequestType();
475 }
476
477 return tmp + ' ' + method;
478 }
479 final String queryString = httpRequest.getQueryString();
480 if (queryString == null) {
481 return tmp + ' ' + method;
482 }
483 return tmp + '?' + queryString + ' ' + method;
484 }
485
486 private boolean isRequestExcluded(HttpServletRequest httpRequest) {
487 return urlExcludePattern != null && urlExcludePattern.matcher(
488 httpRequest.getRequestURI().substring(httpRequest.getContextPath().length()))
489 .matches();
490 }
491
492
493 protected boolean isAllowed(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
494 throws IOException {
495 return httpAuth.isAllowed(httpRequest, httpResponse);
496 }
497
498
499 protected void log(HttpServletRequest httpRequest, String requestName, long duration,
500 boolean systemError, int responseStatus, long responseSize) {
501 if (!logEnabled) {
502 return;
503 }
504 final String filterName = filterConfig.getFilterName();
505 LOG.logHttpRequest(httpRequest, requestName, duration, systemError, responseStatus,
506 responseSize, filterName);
507 }
508
509 private static void throwException(Throwable t) throws IOException, ServletException {
510 if (t instanceof Error) {
511 throw (Error) t;
512 } else if (t instanceof RuntimeException) {
513 throw (RuntimeException) t;
514 } else if (t instanceof IOException) {
515 throw (IOException) t;
516 } else if (t instanceof ServletException) {
517 throw (ServletException) t;
518 } else {
519
520
521 throw new ServletException(t.getMessage(), t);
522 }
523 }
524
525 FilterContext getFilterContext() {
526 return filterContext;
527 }
528
529
538 public static void registerApplicationNodeInCollectServer(String applicationName,
539 URL collectServerUrl, URL applicationNodeUrl) {
540 if (collectServerUrl == null || applicationNodeUrl == null) {
541 throw new IllegalArgumentException(
542 "collectServerUrl and applicationNodeUrl must not be null");
543 }
544 final String appName;
545 if (applicationName == null) {
546 appName = Parameters.getCurrentApplication();
547 } else {
548 appName = applicationName;
549 }
550 final URL registerUrl;
551 try {
552 registerUrl = new URL(collectServerUrl.toExternalForm() + "?appName="
553 + URLEncoder.encode(appName, "UTF-8") + "&appUrls="
554
555 + URLEncoder.encode(applicationNodeUrl.toExternalForm(), "UTF-8")
556 + "&action=registerNode");
557 unregisterApplicationNodeInCollectServerUrl = new URL(
558 registerUrl.toExternalForm().replace("registerNode", "unregisterNode"));
559 } catch (final IOException e) {
560
561 throw new IllegalArgumentException(e);
562 }
563
564
565
566 final Thread thread = new Thread("javamelody registerApplicationNodeInCollectServer") {
567 @Override
568 public void run() {
569 try {
570 Thread.sleep(10000);
571 } catch (final InterruptedException e) {
572 throw new IllegalStateException(e);
573 }
574 try {
575 new LabradorRetriever(registerUrl).post(null);
576 LOG.info("application node added to the collect server");
577 } catch (final IOException e) {
578 LOG.warn("Unable to register application's node in the collect server ( " + e
579 + ')', e);
580 }
581 }
582 };
583 thread.setDaemon(true);
584 thread.start();
585 }
586
587
591 public static void unregisterApplicationNodeInCollectServer() throws IOException {
592 if (unregisterApplicationNodeInCollectServerUrl != null) {
593 new LabradorRetriever(unregisterApplicationNodeInCollectServerUrl).post(null);
594 LOG.info("application node removed from the collect server");
595 }
596 }
597 }
598