View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.access.spi;
15  
16  import ch.qos.logback.access.AccessConstants;
17  import ch.qos.logback.access.pattern.AccessConverter;
18  import ch.qos.logback.access.servlet.Util;
19  import ch.qos.logback.core.Context;
20  import ch.qos.logback.core.spi.SequenceNumberGenerator;
21  
22  import javax.servlet.http.Cookie;
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  import javax.servlet.http.HttpSession;
26  
27  import java.io.Serializable;
28  import java.util.ArrayList;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.TreeMap;
34  import java.util.Vector;
35  
36  // Contributors:  Joern Huxhorn (see also bug #110)
37  
38  /**
39   * The Access module's internal representation of logging events. When the
40   * logging component instance is called in the container to log then a
41   * <code>AccessEvent</code> instance is created. This instance is passed around
42   * to the different logback components.
43   *
44   * @author Ceki G&uuml;lc&uuml;
45   * @author S&eacute;bastien Pennec
46   */
47  public class AccessEvent implements Serializable, IAccessEvent {
48  
49      private static final String[] NA_STRING_ARRAY = new String[] { NA };
50  
51      private static final long serialVersionUID = 866718993618836343L;
52  
53      private static final String EMPTY = "";
54  
55      private transient final HttpServletRequest httpRequest;
56      private transient final HttpServletResponse httpResponse;
57  
58      String queryString;
59      String requestURI;
60      String requestURL;
61      String remoteHost;
62      String remoteUser;
63      String remoteAddr;
64      String threadName;
65      String protocol;
66      String method;
67      String serverName;
68      String requestContent;
69      String responseContent;
70      String sessionID;
71      long elapsedTime;
72  
73      Map<String, String> requestHeaderMap;
74      Map<String, String[]> requestParameterMap;
75      Map<String, String> responseHeaderMap;
76      Map<String, Object> attributeMap;
77  
78      long contentLength = SENTINEL;
79      int statusCode = SENTINEL;
80      int localPort = SENTINEL;
81  
82      transient ServerAdapter serverAdapter;
83  
84      /**
85       * The number of milliseconds elapsed from 1/1/1970 until logging event was
86       * created.
87       */
88      private long timeStamp = 0;
89  
90      private long sequenceNumber = 0;
91  
92      public AccessEvent(Context context, HttpServletRequest httpRequest, HttpServletResponse httpResponse,
93              ServerAdapter adapter) {
94          this.httpRequest = httpRequest;
95          this.httpResponse = httpResponse;
96          this.timeStamp = System.currentTimeMillis();
97  
98          SequenceNumberGenerator sng = context.getSequenceNumberGenerator();
99          if (sng != null) {
100             this.sequenceNumber = sng.nextSequenceNumber();
101         }
102         this.serverAdapter = adapter;
103         this.elapsedTime = calculateElapsedTime();
104     }
105 
106     /**
107      * Returns the underlying HttpServletRequest. After serialization the returned
108      * value will be null.
109      *
110      * @return
111      */
112     @Override
113     public HttpServletRequest getRequest() {
114         return httpRequest;
115     }
116 
117     /**
118      * Returns the underlying HttpServletResponse. After serialization the returned
119      * value will be null.
120      *
121      * @return
122      */
123     @Override
124     public HttpServletResponse getResponse() {
125         return httpResponse;
126     }
127 
128     @Override
129     public long getTimeStamp() {
130         return timeStamp;
131     }
132 
133     public void setTimeStamp(long timeStamp) {
134         if (this.timeStamp != 0) {
135             throw new IllegalStateException("timeStamp has been already set for this event.");
136         } else {
137             this.timeStamp = timeStamp;
138         }
139     }
140 
141     public long getSequenceNumber() {
142         return sequenceNumber;
143     }
144 
145     public void setSequenceNumber(long sequenceNumber) {
146         this.sequenceNumber = sequenceNumber;
147     }
148 
149     /**
150      * @param threadName The threadName to set.
151      */
152     public void setThreadName(String threadName) {
153         this.threadName = threadName;
154     }
155 
156     @Override
157     public String getThreadName() {
158         return threadName == null ? NA : threadName;
159     }
160 
161     @Override
162     public String getRequestURI() {
163         if (requestURI == null) {
164             if (httpRequest != null) {
165                 requestURI = httpRequest.getRequestURI();
166             } else {
167                 requestURI = NA;
168             }
169         }
170         return requestURI;
171     }
172 
173     @Override
174     public String getQueryString() {
175         if (queryString == null) {
176             if (httpRequest != null) {
177                 StringBuilder buf = new StringBuilder();
178                 final String qStr = httpRequest.getQueryString();
179                 if (qStr != null) {
180                     buf.append(AccessConverter.QUESTION_CHAR);
181                     buf.append(qStr);
182                 }
183                 queryString = buf.toString();
184             } else {
185                 queryString = NA;
186             }
187         }
188         return queryString;
189     }
190 
191     /**
192      * The first line of the request.
193      */
194     @Override
195     public String getRequestURL() {
196         if (requestURL == null) {
197             if (httpRequest != null) {
198                 StringBuilder buf = new StringBuilder();
199                 buf.append(httpRequest.getMethod());
200                 buf.append(AccessConverter.SPACE_CHAR);
201                 buf.append(httpRequest.getRequestURI());
202                 buf.append(getQueryString());
203                 buf.append(AccessConverter.SPACE_CHAR);
204                 buf.append(httpRequest.getProtocol());
205                 requestURL = buf.toString();
206             } else {
207                 requestURL = NA;
208             }
209         }
210         return requestURL;
211     }
212 
213     @Override
214     public String getRemoteHost() {
215         if (remoteHost == null) {
216             if (httpRequest != null) {
217                 // the underlying implementation of HttpServletRequest will
218                 // determine if remote lookup will be performed
219                 remoteHost = httpRequest.getRemoteHost();
220             } else {
221                 remoteHost = NA;
222             }
223         }
224         return remoteHost;
225     }
226 
227     @Override
228     public String getRemoteUser() {
229         if (remoteUser == null) {
230             if (httpRequest != null) {
231                 remoteUser = httpRequest.getRemoteUser();
232             } else {
233                 remoteUser = NA;
234             }
235         }
236         return remoteUser;
237     }
238 
239     @Override
240     public String getProtocol() {
241         if (protocol == null) {
242             if (httpRequest != null) {
243                 protocol = httpRequest.getProtocol();
244             } else {
245                 protocol = NA;
246             }
247         }
248         return protocol;
249     }
250 
251     @Override
252     public String getMethod() {
253         if (method == null) {
254             if (httpRequest != null) {
255                 method = httpRequest.getMethod();
256             } else {
257                 method = NA;
258             }
259         }
260         return method;
261     }
262 
263     @Override
264     public String getSessionID() {
265         if (sessionID == null) {
266             if (httpRequest != null) {
267                 final HttpSession session = httpRequest.getSession(false);
268                 if (session != null) {
269                     sessionID = session.getId();
270                 }
271             } else {
272                 sessionID = NA;
273             }
274         }
275         return sessionID;
276     }
277 
278     @Override
279     public String getServerName() {
280         if (serverName == null) {
281             if (httpRequest != null) {
282                 serverName = httpRequest.getServerName();
283             } else {
284                 serverName = NA;
285             }
286         }
287         return serverName;
288     }
289 
290     @Override
291     public String getRemoteAddr() {
292         if (remoteAddr == null) {
293             if (httpRequest != null) {
294                 remoteAddr = httpRequest.getRemoteAddr();
295             } else {
296                 remoteAddr = NA;
297             }
298         }
299         return remoteAddr;
300     }
301 
302     @Override
303     public String getRequestHeader(String key) {
304         String result = null;
305         key = key.toLowerCase();
306         if (requestHeaderMap == null) {
307             if (httpRequest != null) {
308                 buildRequestHeaderMap();
309                 result = requestHeaderMap.get(key);
310             }
311         } else {
312             result = requestHeaderMap.get(key);
313         }
314 
315         if (result != null) {
316             return result;
317         } else {
318             return NA;
319         }
320     }
321 
322     @Override
323     public Enumeration<String> getRequestHeaderNames() {
324         // post-serialization
325         if (httpRequest == null) {
326             Vector<String> list = new Vector<String>(getRequestHeaderMap().keySet());
327             return list.elements();
328         }
329         return httpRequest.getHeaderNames();
330     }
331 
332     @Override
333     public Map<String, String> getRequestHeaderMap() {
334         if (requestHeaderMap == null) {
335             buildRequestHeaderMap();
336         }
337         return requestHeaderMap;
338     }
339 
340     public void buildRequestHeaderMap() {
341         // according to RFC 2616 header names are case-insensitive
342         // latest versions of Tomcat return header names in lower-case
343         requestHeaderMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
344         Enumeration<String> e = httpRequest.getHeaderNames();
345         if (e == null) {
346             return;
347         }
348         while (e.hasMoreElements()) {
349             String key = e.nextElement();
350             requestHeaderMap.put(key, httpRequest.getHeader(key));
351         }
352     }
353 
354     public void buildRequestParameterMap() {
355         requestParameterMap = new HashMap<String, String[]>();
356         try {
357             Enumeration<String> e = httpRequest.getParameterNames();
358             if (e == null) {
359                 return;
360             }
361             while (e.hasMoreElements()) {
362                 String key = e.nextElement();
363                 requestParameterMap.put(key, httpRequest.getParameterValues(key));
364             }
365         } catch(Throwable t) {
366             // The use of HttpServletRequest.getParameterNames() can cause
367             // a READ of the Request body content.  This can fail with various
368             // Throwable failures depending on the state of the Request
369             // at the time this method is called.
370             // We don't want to fail the logging due to these types of requests
371             t.printStackTrace();
372         }
373     }
374 
375     @Override
376     public Map<String, String[]> getRequestParameterMap() {
377         if (requestParameterMap == null) {
378             buildRequestParameterMap();
379         }
380         return requestParameterMap;
381     }
382 
383     @Override
384     public String getAttribute(String key) {
385         Object value = null;
386         if (attributeMap != null) {
387             // Event was prepared for deferred processing so we have a copy of attribute map
388             // and must use that copy
389             value = attributeMap.get(key);
390         } else if (httpRequest != null) {
391             // We have original request so take attribute from it
392             value = httpRequest.getAttribute(key);
393         }
394 
395         return value != null ? value.toString() : NA;
396     }
397 
398     private void copyAttributeMap() {
399 
400         if (httpRequest == null) {
401             return;
402         }
403 
404         // attributeMap has been copied already. See also LOGBACK-1189
405         if (attributeMap != null) {
406             return;
407         }
408 
409         attributeMap = new HashMap<String, Object>();
410 
411         Enumeration<String> names = httpRequest.getAttributeNames();
412         while (names.hasMoreElements()) {
413             String name = names.nextElement();
414 
415             Object value = httpRequest.getAttribute(name);
416             if (shouldCopyAttribute(name, value)) {
417                 attributeMap.put(name, value);
418             }
419         }
420     }
421 
422     private boolean shouldCopyAttribute(String name, Object value) {
423         if (AccessConstants.LB_INPUT_BUFFER.equals(name) || AccessConstants.LB_OUTPUT_BUFFER.equals(name)) {
424             // Do not copy attributes used by logback internally - these are available via
425             // other getters anyway
426             return false;
427         } else if (value == null) {
428             // No reasons to copy nulls - Map.get() will return null for missing keys and
429             // the list of attribute
430             // names is not available through IAccessEvent
431             return false;
432         } else {
433             // Only copy what is serializable
434             return value instanceof Serializable;
435         }
436     }
437 
438     @Override
439     public String[] getRequestParameter(String key) {
440         String[] value = null;
441 
442         if (requestParameterMap != null) {
443             value = requestParameterMap.get(key);
444         } else if (httpRequest != null) {
445             value = httpRequest.getParameterValues(key);
446         }
447 
448         return (value != null) ? value : NA_STRING_ARRAY;
449     }
450 
451     @Override
452     public String getCookie(String key) {
453 
454         if (httpRequest != null) {
455             Cookie[] cookieArray = httpRequest.getCookies();
456             if (cookieArray == null) {
457                 return NA;
458             }
459 
460             for (Cookie cookie : cookieArray) {
461                 if (key.equals(cookie.getName())) {
462                     return cookie.getValue();
463                 }
464             }
465         }
466         return NA;
467     }
468 
469     @Override
470     public long getContentLength() {
471         if (contentLength == SENTINEL) {
472             if (httpResponse != null) {
473                 contentLength = serverAdapter.getContentLength();
474                 return contentLength;
475             }
476         }
477         return contentLength;
478     }
479 
480     public int getStatusCode() {
481         if (statusCode == SENTINEL) {
482             if (httpResponse != null) {
483                 statusCode = serverAdapter.getStatusCode();
484             }
485         }
486         return statusCode;
487     }
488 
489     public long getElapsedSeconds() {
490         return elapsedTime < 0 ? elapsedTime : elapsedTime / 1000;
491     }
492 
493     public long getElapsedTime() {
494         return elapsedTime;
495     }
496 
497     private long calculateElapsedTime() {
498         if (serverAdapter.getRequestTimestamp() < 0) {
499             return -1;
500         }
501         return getTimeStamp() - serverAdapter.getRequestTimestamp();
502     }
503 
504     public String getRequestContent() {
505         if (requestContent != null) {
506             return requestContent;
507         }
508 
509         if (Util.isFormUrlEncoded(httpRequest)) {
510             StringBuilder buf = new StringBuilder();
511 
512             try {
513                 Enumeration<String> pramEnumeration = httpRequest.getParameterNames();
514 
515                 // example: id=1234&user=cgu
516                 // number=1233&x=1
517                 int count = 0;
518                 while (pramEnumeration.hasMoreElements()) {
519 
520                     String key = pramEnumeration.nextElement();
521                     if (count++ != 0) {
522                         buf.append("&");
523                     }
524                     buf.append(key);
525                     buf.append("=");
526                     String val = httpRequest.getParameter(key);
527                     if (val != null) {
528                         buf.append(val);
529                     } else {
530                         buf.append("");
531                     }
532                 }
533             } catch (Throwable t) {
534                 // The use of HttpServletRequest.getParameterNames() and
535                 // HttpServletRequest.getParameter(String) can cause
536                 // a READ of the Request body content.  This can fail with various
537                 // Throwable failures depending on the state of the Request
538                 // at the time this method is called.
539                 // We don't want to fail the logging due to these types of requests
540                 t.printStackTrace();
541             }
542             requestContent = buf.toString();
543         } else {
544             // retrieve the byte array placed by TeeFilter
545             byte[] inputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_INPUT_BUFFER);
546 
547             if (inputBuffer != null) {
548                 requestContent = new String(inputBuffer);
549             }
550 
551             if (requestContent == null || requestContent.length() == 0) {
552                 requestContent = EMPTY;
553             }
554         }
555 
556         return requestContent;
557     }
558 
559     public String getResponseContent() {
560         if (responseContent != null) {
561             return responseContent;
562         }
563 
564         if (Util.isImageResponse(httpResponse)) {
565             responseContent = "[IMAGE CONTENTS SUPPRESSED]";
566         } else {
567 
568             // retrieve the byte array previously placed by TeeFilter
569             byte[] outputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_OUTPUT_BUFFER);
570 
571             if (outputBuffer != null) {
572                 responseContent = new String(outputBuffer);
573             }
574             if (responseContent == null || responseContent.length() == 0) {
575                 responseContent = EMPTY;
576             }
577         }
578 
579         return responseContent;
580     }
581 
582     public int getLocalPort() {
583         if (localPort == SENTINEL) {
584             if (httpRequest != null) {
585                 localPort = httpRequest.getLocalPort();
586             }
587 
588         }
589         return localPort;
590     }
591 
592     public ServerAdapter getServerAdapter() {
593         return serverAdapter;
594     }
595 
596     public String getResponseHeader(String key) {
597         buildResponseHeaderMap();
598         return responseHeaderMap.get(key);
599     }
600 
601     void buildResponseHeaderMap() {
602         if (responseHeaderMap == null) {
603             responseHeaderMap = serverAdapter.buildResponseHeaderMap();
604         }
605     }
606 
607     public Map<String, String> getResponseHeaderMap() {
608         buildResponseHeaderMap();
609         return responseHeaderMap;
610     }
611 
612     public List<String> getResponseHeaderNameList() {
613         buildResponseHeaderMap();
614         return new ArrayList<String>(responseHeaderMap.keySet());
615     }
616 
617     public void prepareForDeferredProcessing() {
618         getRequestHeaderMap();
619         getRequestParameterMap();
620         getResponseHeaderMap();
621         getLocalPort();
622         getMethod();
623         getProtocol();
624         getRemoteAddr();
625         getRemoteHost();
626         getRemoteUser();
627         getRequestURI();
628         getRequestURL();
629         getServerName();
630         getTimeStamp();
631         getElapsedTime();
632 
633         getStatusCode();
634         getContentLength();
635         getRequestContent();
636         getResponseContent();
637 
638         copyAttributeMap();
639     }
640 }