001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights 003 * reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License 006 * v1.0 as published by the Eclipse Foundation 007 * 008 * or (per the licensee's choosing) 009 * 010 * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. 011 */ 012package ch.qos.logback.access.common.spi; 013 014import ch.qos.logback.access.common.AccessConstants; 015import ch.qos.logback.access.common.pattern.AccessConverter; 016import ch.qos.logback.access.common.servlet.Util; 017import ch.qos.logback.core.Context; 018import ch.qos.logback.core.spi.SequenceNumberGenerator; 019 020import jakarta.servlet.http.Cookie; 021import jakarta.servlet.http.HttpServletRequest; 022import jakarta.servlet.http.HttpServletResponse; 023import jakarta.servlet.http.HttpSession; 024 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Enumeration; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.TreeMap; 033import java.util.Vector; 034 035// Contributors: Joern Huxhorn (see also bug #110) 036 037/** 038 * <p>This class is the implementation of the {@link IAccessEvent} interface.</p> 039 * 040 * <p>Much of the processing done by the logack-access project revolves around this class.</p> 041 * 042 * @author Ceki Gülcü 043 * @author Sébastien Pennec 044 */ 045public class AccessEvent implements Serializable, IAccessEvent { 046 047 private static final String[] NA_STRING_ARRAY = new String[] { NA }; 048 049 private static final long serialVersionUID = 866718993618836343L; 050 051 private static final String EMPTY = ""; 052 053 private transient final HttpServletRequest httpRequest; 054 private transient final HttpServletResponse httpResponse; 055 056 String queryString; 057 String requestURI; 058 String requestURL; 059 String remoteHost; 060 String remoteUser; 061 String remoteAddr; 062 String threadName; 063 String protocol; 064 String method; 065 String serverName; 066 String requestContent; 067 String responseContent; 068 String sessionID; 069 long elapsedTime; 070 071 Map<String, String> requestHeaderMap; 072 Map<String, String[]> requestParameterMap; 073 Map<String, String> responseHeaderMap; 074 Map<String, Object> attributeMap; 075 076 List<Cookie> cookieList; 077 078 long contentLength = SENTINEL; 079 int statusCode = SENTINEL; 080 int localPort = SENTINEL; 081 082 transient ServerAdapter serverAdapter; 083 084 /** 085 * The number of milliseconds elapsed from 1/1/1970 until logging event was created. 086 */ 087 private long timeStamp = 0; 088 089 private long sequenceNumber = 0; 090 091 public AccessEvent(Context context, HttpServletRequest httpRequest, HttpServletResponse httpResponse, 092 ServerAdapter adapter) { 093 this.httpRequest = httpRequest; 094 this.httpResponse = httpResponse; 095 this.timeStamp = System.currentTimeMillis(); 096 097 SequenceNumberGenerator sng = context.getSequenceNumberGenerator(); 098 if (sng != null) { 099 this.sequenceNumber = sng.nextSequenceNumber(); 100 } 101 this.serverAdapter = adapter; 102 this.elapsedTime = calculateElapsedTime(); 103 } 104 105 /** 106 * Returns the underlying HttpServletRequest. After serialization the returned value will be null. 107 * 108 * @return 109 */ 110 @Override 111 public HttpServletRequest getRequest() { 112 return httpRequest; 113 } 114 115 /** 116 * Returns the underlying HttpServletResponse. After serialization the returned value will be null. 117 * 118 * @return 119 */ 120 @Override 121 public HttpServletResponse getResponse() { 122 return httpResponse; 123 } 124 125 @Override 126 public long getTimeStamp() { 127 return timeStamp; 128 } 129 130 public void setTimeStamp(long timeStamp) { 131 this.timeStamp = timeStamp; 132 } 133 134 public long getSequenceNumber() { 135 return sequenceNumber; 136 } 137 138 public void setSequenceNumber(long sequenceNumber) { 139 this.sequenceNumber = sequenceNumber; 140 } 141 142 /** 143 * @param threadName The threadName to set. 144 */ 145 public void setThreadName(String threadName) { 146 this.threadName = threadName; 147 } 148 149 @Override 150 public String getThreadName() { 151 return threadName == null ? NA : threadName; 152 } 153 154 @Override 155 public String getRequestURI() { 156 if (requestURI == null) { 157 if (httpRequest != null) { 158 requestURI = httpRequest.getRequestURI(); 159 } else { 160 requestURI = NA; 161 } 162 } 163 return requestURI; 164 } 165 166 @Override 167 public String getQueryString() { 168 if (queryString == null) { 169 if (httpRequest != null) { 170 StringBuilder buf = new StringBuilder(); 171 final String qStr = httpRequest.getQueryString(); 172 if (qStr != null) { 173 buf.append(AccessConverter.QUESTION_CHAR); 174 buf.append(qStr); 175 } 176 queryString = buf.toString(); 177 } else { 178 queryString = NA; 179 } 180 } 181 return queryString; 182 } 183 184 /** 185 * The first line of the request. 186 */ 187 @Override 188 public String getRequestURL() { 189 if(requestURL != null) 190 return requestURL; 191 192 if (httpRequest != null) { 193 StringBuilder buf = new StringBuilder(); 194 buf.append(getMethod()); 195 buf.append(AccessConverter.SPACE_CHAR); 196 buf.append(getRequestURI()); 197 buf.append(getQueryString()); 198 buf.append(AccessConverter.SPACE_CHAR); 199 buf.append(getProtocol()); 200 requestURL = buf.toString(); 201 } else { 202 requestURL = NA; 203 } 204 return requestURL; 205 } 206 207 @Override 208 public String getRemoteHost() { 209 if (remoteHost == null) { 210 if (httpRequest != null) { 211 // the underlying implementation of HttpServletRequest will 212 // determine if remote lookup will be performed 213 remoteHost = httpRequest.getRemoteHost(); 214 } else { 215 remoteHost = NA; 216 } 217 } 218 return remoteHost; 219 } 220 221 @Override 222 public String getRemoteUser() { 223 if (remoteUser == null) { 224 if (httpRequest != null) { 225 remoteUser = httpRequest.getRemoteUser(); 226 } else { 227 remoteUser = NA; 228 } 229 } 230 return remoteUser; 231 } 232 233 @Override 234 public String getProtocol() { 235 if (protocol == null) { 236 if (httpRequest != null) { 237 protocol = httpRequest.getProtocol(); 238 } else { 239 protocol = NA; 240 } 241 } 242 return protocol; 243 } 244 245 @Override 246 public String getMethod() { 247 if (method == null) { 248 if (httpRequest != null) { 249 method = httpRequest.getMethod(); 250 } else { 251 method = NA; 252 } 253 } 254 return method; 255 } 256 257 @Override 258 public String getSessionID() { 259 if (sessionID == null) { 260 if (httpRequest != null) { 261 if (httpRequest instanceof WrappedHttpRequest) { 262 WrappedHttpRequest wrappedHttpRequest = (WrappedHttpRequest) httpRequest; 263 sessionID = wrappedHttpRequest.getSessionID(); 264 } else { 265 final HttpSession session = httpRequest.getSession(false); 266 if (session != null) { 267 sessionID = session.getId(); 268 } 269 } 270 } else { 271 sessionID = NA; 272 } 273 } 274 return sessionID; 275 } 276 277 @Override 278 public String getServerName() { 279 if (serverName == null) { 280 if (httpRequest != null) { 281 serverName = httpRequest.getServerName(); 282 } else { 283 serverName = NA; 284 } 285 } 286 return serverName; 287 } 288 289 @Override 290 public String getRemoteAddr() { 291 if (remoteAddr == null) { 292 if (httpRequest != null) { 293 remoteAddr = httpRequest.getRemoteAddr(); 294 } else { 295 remoteAddr = NA; 296 } 297 } 298 return remoteAddr; 299 } 300 301 @Override 302 public String getRequestHeader(String key) { 303 String result = null; 304 key = key.toLowerCase(); 305 if (requestHeaderMap == null) { 306 if (httpRequest != null) { 307 buildRequestHeaderMap(); 308 result = requestHeaderMap.get(key); 309 } 310 } else { 311 result = requestHeaderMap.get(key); 312 } 313 314 if (result != null) { 315 return result; 316 } else { 317 return NA; 318 } 319 } 320 321 @Override 322 public Enumeration<String> getRequestHeaderNames() { 323 // post-serialization 324 if (httpRequest == null) { 325 Vector<String> list = new Vector<String>(getRequestHeaderMap().keySet()); 326 return list.elements(); 327 } 328 return httpRequest.getHeaderNames(); 329 } 330 331 @Override 332 public Map<String, String> getRequestHeaderMap() { 333 if (requestHeaderMap == null) { 334 buildRequestHeaderMap(); 335 } 336 return requestHeaderMap; 337 } 338 339 public void buildRequestHeaderMap() { 340 if (httpRequest instanceof WrappedHttpRequest) { 341 WrappedHttpRequest whr = (WrappedHttpRequest) httpRequest; 342 requestHeaderMap = whr.buildRequestHeaderMap(); 343 } else { 344 345 // according to RFC 2616 header names are case-insensitive 346 // latest versions of Tomcat return header names in lower-case 347 requestHeaderMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); 348 Enumeration<String> e = httpRequest.getHeaderNames(); 349 if (e == null) { 350 return; 351 } 352 while (e.hasMoreElements()) { 353 String key = e.nextElement(); 354 requestHeaderMap.put(key, httpRequest.getHeader(key)); 355 } 356 } 357 } 358 359 public void buildRequestParameterMap() { 360 if (httpRequest instanceof WrappedHttpRequest) { 361 WrappedHttpRequest whr = (WrappedHttpRequest) httpRequest; 362 requestParameterMap = whr.buildRequestParameterMap(); 363 } else { 364 365 requestParameterMap = new HashMap<String, String[]>(); 366 try { 367 Enumeration<String> e = httpRequest.getParameterNames(); 368 if (e == null) { 369 return; 370 } 371 while (e.hasMoreElements()) { 372 String key = e.nextElement(); 373 requestParameterMap.put(key, httpRequest.getParameterValues(key)); 374 } 375 } catch (Throwable t) { 376 // The use of HttpServletRequest.getParameterNames() can cause 377 // a READ of the Request body content. This can fail with various 378 // Throwable failures depending on the state of the Request 379 // at the time this method is called. 380 // We don't want to fail the logging due to these types of requests 381 t.printStackTrace(); 382 } 383 } 384 } 385 386 @Override 387 public Map<String, String[]> getRequestParameterMap() { 388 if (requestParameterMap == null) { 389 buildRequestParameterMap(); 390 } 391 return requestParameterMap; 392 } 393 394 @Override 395 public String getAttribute(String key) { 396 Object value = null; 397 if (attributeMap != null) { 398 // Event was prepared for deferred processing so we have a copy of attribute map 399 // and must use that copy 400 value = attributeMap.get(key); 401 } else if (httpRequest != null) { 402 // We have original request so take attribute from it 403 value = httpRequest.getAttribute(key); 404 } 405 406 return value != null ? value.toString() : NA; 407 } 408 409 private void copyAttributeMap() { 410 411 if (httpRequest == null) { 412 return; 413 } 414 415 // attributeMap has been copied already. See also LOGBACK-1189 416 if (attributeMap != null) { 417 return; 418 } 419 420 attributeMap = new HashMap<String, Object>(); 421 422 Enumeration<String> names = httpRequest.getAttributeNames(); 423 while (names.hasMoreElements()) { 424 String name = names.nextElement(); 425 426 Object value = httpRequest.getAttribute(name); 427 if (shouldCopyAttribute(name, value)) { 428 attributeMap.put(name, value); 429 } 430 } 431 } 432 433 private boolean shouldCopyAttribute(String name, Object value) { 434 if (AccessConstants.LB_INPUT_BUFFER.equals(name) || AccessConstants.LB_OUTPUT_BUFFER.equals(name)) { 435 // Do not copy attributes used by logback internally - these are available via 436 // other getters anyway 437 return false; 438 } else if (value == null) { 439 // No reasons to copy nulls - Map.get() will return null for missing keys and 440 // the list of attribute 441 // names is not available through IAccessEvent 442 return false; 443 } else { 444 // Only copy what is serializable 445 return value instanceof Serializable; 446 } 447 } 448 449 @Override 450 public String[] getRequestParameter(String key) { 451 String[] value = null; 452 453 if (requestParameterMap != null) { 454 value = requestParameterMap.get(key); 455 } else if (httpRequest != null) { 456 value = httpRequest.getParameterValues(key); 457 } 458 459 return (value != null) ? value : NA_STRING_ARRAY; 460 } 461 462 /** 463 * Return the list of cookies in the httpRequest. The list is created if it did not exist previously. 464 * 465 * @return a list of cookies in the httpRequest, the returned list can be empty but not null 466 * @since version 2.0.2 467 */ 468 public List<Cookie> getCookies() { 469 470 if (cookieList != null) 471 return cookieList; 472 473 if (httpRequest == null) { 474 cookieList = List.of(); 475 return cookieList; 476 } 477 478 Cookie[] cookieArray = httpRequest.getCookies(); 479 this.cookieList = (cookieArray != null ? List.of(cookieArray) : List.of()); 480 return this.cookieList; 481 } 482 483 @Override 484 public String getCookie(String key) { 485 List<Cookie> cookies = getCookies(); 486 Cookie matchingCookie = cookies.stream().filter(c -> c.getName().equals(key)).findFirst().orElse(null); 487 return matchingCookie != null ? matchingCookie.getValue() : NA; 488 } 489 490 @Override 491 public long getContentLength() { 492 if (contentLength == SENTINEL) { 493 if (httpResponse != null) { 494 contentLength = serverAdapter.getContentLength(); 495 return contentLength; 496 } 497 } 498 return contentLength; 499 } 500 501 public int getStatusCode() { 502 if (statusCode == SENTINEL) { 503 if (httpResponse != null) { 504 statusCode = serverAdapter.getStatusCode(); 505 } 506 } 507 return statusCode; 508 } 509 510 public long getElapsedSeconds() { 511 return elapsedTime < 0 ? elapsedTime : elapsedTime / 1000; 512 } 513 514 public long getElapsedTime() { 515 return elapsedTime; 516 } 517 518 private long calculateElapsedTime() { 519 if (serverAdapter.getRequestTimestamp() < 0) { 520 return -1; 521 } 522 return getTimeStamp() - serverAdapter.getRequestTimestamp(); 523 } 524 525 public String getRequestContent() { 526 if (requestContent != null) { 527 return requestContent; 528 } 529 530 if (Util.isFormUrlEncoded(httpRequest)) { 531 StringBuilder buf = new StringBuilder(); 532 533 try { 534 Enumeration<String> pramEnumeration = httpRequest.getParameterNames(); 535 536 // example: id=1234&user=cgu 537 // number=1233&x=1 538 int count = 0; 539 while (pramEnumeration.hasMoreElements()) { 540 541 String key = pramEnumeration.nextElement(); 542 if (count++ != 0) { 543 buf.append("&"); 544 } 545 buf.append(key); 546 buf.append("="); 547 String val = httpRequest.getParameter(key); 548 if (val != null) { 549 buf.append(val); 550 } else { 551 buf.append(""); 552 } 553 } 554 } catch (Throwable t) { 555 // The use of HttpServletRequest.getParameterNames() and 556 // HttpServletRequest.getParameter(String) can cause 557 // a READ of the Request body content. This can fail with various 558 // Throwable failures depending on the state of the Request 559 // at the time this method is called. 560 // We don't want to fail the logging due to these types of requests 561 t.printStackTrace(); 562 } 563 requestContent = buf.toString(); 564 } else { 565 // retrieve the byte array placed by TeeFilter 566 byte[] inputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_INPUT_BUFFER); 567 568 if (inputBuffer != null) { 569 requestContent = new String(inputBuffer); 570 } 571 572 if (requestContent == null || requestContent.length() == 0) { 573 requestContent = EMPTY; 574 } 575 } 576 577 return requestContent; 578 } 579 580 public String getResponseContent() { 581 if (responseContent != null) { 582 return responseContent; 583 } 584 585 if (Util.isImageResponse(httpResponse)) { 586 responseContent = "[IMAGE CONTENTS SUPPRESSED]"; 587 } else { 588 589 // retrieve the byte array previously placed by TeeFilter 590 byte[] outputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_OUTPUT_BUFFER); 591 592 if (outputBuffer != null) { 593 responseContent = new String(outputBuffer); 594 } 595 if (responseContent == null || responseContent.length() == 0) { 596 responseContent = EMPTY; 597 } 598 } 599 600 return responseContent; 601 } 602 603 public int getLocalPort() { 604 if (localPort == SENTINEL) { 605 if (httpRequest != null) { 606 localPort = httpRequest.getLocalPort(); 607 } 608 609 } 610 return localPort; 611 } 612 613 public ServerAdapter getServerAdapter() { 614 return serverAdapter; 615 } 616 617 public String getResponseHeader(String key) { 618 buildResponseHeaderMap(); 619 return responseHeaderMap.get(key); 620 } 621 622 void buildResponseHeaderMap() { 623 if (responseHeaderMap == null) { 624 responseHeaderMap = serverAdapter.buildResponseHeaderMap(); 625 } 626 } 627 628 public Map<String, String> getResponseHeaderMap() { 629 buildResponseHeaderMap(); 630 return responseHeaderMap; 631 } 632 633 public List<String> getResponseHeaderNameList() { 634 buildResponseHeaderMap(); 635 return new ArrayList<String>(responseHeaderMap.keySet()); 636 } 637 638 public void prepareForDeferredProcessing() { 639 getRequestHeaderMap(); 640 getRequestParameterMap(); 641 getResponseHeaderMap(); 642 getLocalPort(); 643 getMethod(); 644 getProtocol(); 645 getRemoteAddr(); 646 getRemoteHost(); 647 getRemoteUser(); 648 getRequestURI(); 649 getRequestURL(); 650 getServerName(); 651 getTimeStamp(); 652 getElapsedTime(); 653 654 getCookies(); 655 656 getStatusCode(); 657 getContentLength(); 658 getRequestContent(); 659 getResponseContent(); 660 661 copyAttributeMap(); 662 } 663}