001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.filter; 018 019import java.io.IOException; 020import java.io.UnsupportedEncodingException; 021import java.util.Enumeration; 022import java.util.function.Predicate; 023 024import javax.servlet.FilterChain; 025import javax.servlet.ServletException; 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletResponse; 028import javax.servlet.http.HttpSession; 029 030import org.springframework.http.HttpHeaders; 031import org.springframework.http.server.ServletServerHttpRequest; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034import org.springframework.util.StringUtils; 035import org.springframework.web.util.ContentCachingRequestWrapper; 036import org.springframework.web.util.WebUtils; 037 038/** 039 * Base class for {@code Filter}s that perform logging operations before and after a request 040 * is processed. 041 * 042 * <p>Subclasses should override the {@code beforeRequest(HttpServletRequest, String)} and 043 * {@code afterRequest(HttpServletRequest, String)} methods to perform the actual logging 044 * around the request. 045 * 046 * <p>Subclasses are passed the message to write to the log in the {@code beforeRequest} and 047 * {@code afterRequest} methods. By default, only the URI of the request is logged. However, 048 * setting the {@code includeQueryString} property to {@code true} will cause the query string of 049 * the request to be included also; this can be further extended through {@code includeClientInfo} 050 * and {@code includeHeaders}. The payload (body content) of the request can be logged via the 051 * {@code includePayload} flag: Note that this will only log the part of the payload which has 052 * actually been read, not necessarily the entire body of the request. 053 * 054 * <p>Prefixes and suffixes for the before and after messages can be configured using the 055 * {@code beforeMessagePrefix}, {@code afterMessagePrefix}, {@code beforeMessageSuffix} and 056 * {@code afterMessageSuffix} properties. 057 * 058 * @author Rob Harrop 059 * @author Juergen Hoeller 060 * @author Rossen Stoyanchev 061 * @since 1.2.5 062 * @see #beforeRequest 063 * @see #afterRequest 064 */ 065public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter { 066 067 /** 068 * The default value prepended to the log message written <i>before</i> a request is 069 * processed. 070 */ 071 public static final String DEFAULT_BEFORE_MESSAGE_PREFIX = "Before request ["; 072 073 /** 074 * The default value appended to the log message written <i>before</i> a request is 075 * processed. 076 */ 077 public static final String DEFAULT_BEFORE_MESSAGE_SUFFIX = "]"; 078 079 /** 080 * The default value prepended to the log message written <i>after</i> a request is 081 * processed. 082 */ 083 public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request ["; 084 085 /** 086 * The default value appended to the log message written <i>after</i> a request is 087 * processed. 088 */ 089 public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]"; 090 091 private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 50; 092 093 094 private boolean includeQueryString = false; 095 096 private boolean includeClientInfo = false; 097 098 private boolean includeHeaders = false; 099 100 private boolean includePayload = false; 101 102 @Nullable 103 private Predicate<String> headerPredicate; 104 105 private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH; 106 107 private String beforeMessagePrefix = DEFAULT_BEFORE_MESSAGE_PREFIX; 108 109 private String beforeMessageSuffix = DEFAULT_BEFORE_MESSAGE_SUFFIX; 110 111 private String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX; 112 113 private String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX; 114 115 116 /** 117 * Set whether the query string should be included in the log message. 118 * <p>Should be configured using an {@code <init-param>} for parameter name 119 * "includeQueryString" in the filter definition in {@code web.xml}. 120 */ 121 public void setIncludeQueryString(boolean includeQueryString) { 122 this.includeQueryString = includeQueryString; 123 } 124 125 /** 126 * Return whether the query string should be included in the log message. 127 */ 128 protected boolean isIncludeQueryString() { 129 return this.includeQueryString; 130 } 131 132 /** 133 * Set whether the client address and session id should be included in the 134 * log message. 135 * <p>Should be configured using an {@code <init-param>} for parameter name 136 * "includeClientInfo" in the filter definition in {@code web.xml}. 137 */ 138 public void setIncludeClientInfo(boolean includeClientInfo) { 139 this.includeClientInfo = includeClientInfo; 140 } 141 142 /** 143 * Return whether the client address and session id should be included in the 144 * log message. 145 */ 146 protected boolean isIncludeClientInfo() { 147 return this.includeClientInfo; 148 } 149 150 /** 151 * Set whether the request headers should be included in the log message. 152 * <p>Should be configured using an {@code <init-param>} for parameter name 153 * "includeHeaders" in the filter definition in {@code web.xml}. 154 * @since 4.3 155 */ 156 public void setIncludeHeaders(boolean includeHeaders) { 157 this.includeHeaders = includeHeaders; 158 } 159 160 /** 161 * Return whether the request headers should be included in the log message. 162 * @since 4.3 163 */ 164 protected boolean isIncludeHeaders() { 165 return this.includeHeaders; 166 } 167 168 /** 169 * Set whether the request payload (body) should be included in the log message. 170 * <p>Should be configured using an {@code <init-param>} for parameter name 171 * "includePayload" in the filter definition in {@code web.xml}. 172 * @since 3.0 173 */ 174 public void setIncludePayload(boolean includePayload) { 175 this.includePayload = includePayload; 176 } 177 178 /** 179 * Return whether the request payload (body) should be included in the log message. 180 * @since 3.0 181 */ 182 protected boolean isIncludePayload() { 183 return this.includePayload; 184 } 185 186 /** 187 * Configure a predicate for selecting which headers should be logged if 188 * {@link #setIncludeHeaders(boolean)} is set to {@code true}. 189 * <p>By default this is not set in which case all headers are logged. 190 * @param headerPredicate the predicate to use 191 * @since 5.2 192 */ 193 public void setHeaderPredicate(@Nullable Predicate<String> headerPredicate) { 194 this.headerPredicate = headerPredicate; 195 } 196 197 /** 198 * The configured {@link #setHeaderPredicate(Predicate) headerPredicate}. 199 * @since 5.2 200 */ 201 @Nullable 202 protected Predicate<String> getHeaderPredicate() { 203 return this.headerPredicate; 204 } 205 206 /** 207 * Set the maximum length of the payload body to be included in the log message. 208 * Default is 50 characters. 209 * @since 3.0 210 */ 211 public void setMaxPayloadLength(int maxPayloadLength) { 212 Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' should be larger than or equal to 0"); 213 this.maxPayloadLength = maxPayloadLength; 214 } 215 216 /** 217 * Return the maximum length of the payload body to be included in the log message. 218 * @since 3.0 219 */ 220 protected int getMaxPayloadLength() { 221 return this.maxPayloadLength; 222 } 223 224 /** 225 * Set the value that should be prepended to the log message written 226 * <i>before</i> a request is processed. 227 */ 228 public void setBeforeMessagePrefix(String beforeMessagePrefix) { 229 this.beforeMessagePrefix = beforeMessagePrefix; 230 } 231 232 /** 233 * Set the value that should be appended to the log message written 234 * <i>before</i> a request is processed. 235 */ 236 public void setBeforeMessageSuffix(String beforeMessageSuffix) { 237 this.beforeMessageSuffix = beforeMessageSuffix; 238 } 239 240 /** 241 * Set the value that should be prepended to the log message written 242 * <i>after</i> a request is processed. 243 */ 244 public void setAfterMessagePrefix(String afterMessagePrefix) { 245 this.afterMessagePrefix = afterMessagePrefix; 246 } 247 248 /** 249 * Set the value that should be appended to the log message written 250 * <i>after</i> a request is processed. 251 */ 252 public void setAfterMessageSuffix(String afterMessageSuffix) { 253 this.afterMessageSuffix = afterMessageSuffix; 254 } 255 256 257 /** 258 * The default value is "false" so that the filter may log a "before" message 259 * at the start of request processing and an "after" message at the end from 260 * when the last asynchronously dispatched thread is exiting. 261 */ 262 @Override 263 protected boolean shouldNotFilterAsyncDispatch() { 264 return false; 265 } 266 267 /** 268 * Forwards the request to the next filter in the chain and delegates down to the subclasses 269 * to perform the actual request logging both before and after the request is processed. 270 * @see #beforeRequest 271 * @see #afterRequest 272 */ 273 @Override 274 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 275 throws ServletException, IOException { 276 277 boolean isFirstRequest = !isAsyncDispatch(request); 278 HttpServletRequest requestToUse = request; 279 280 if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) { 281 requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength()); 282 } 283 284 boolean shouldLog = shouldLog(requestToUse); 285 if (shouldLog && isFirstRequest) { 286 beforeRequest(requestToUse, getBeforeMessage(requestToUse)); 287 } 288 try { 289 filterChain.doFilter(requestToUse, response); 290 } 291 finally { 292 if (shouldLog && !isAsyncStarted(requestToUse)) { 293 afterRequest(requestToUse, getAfterMessage(requestToUse)); 294 } 295 } 296 } 297 298 /** 299 * Get the message to write to the log before the request. 300 * @see #createMessage 301 */ 302 private String getBeforeMessage(HttpServletRequest request) { 303 return createMessage(request, this.beforeMessagePrefix, this.beforeMessageSuffix); 304 } 305 306 /** 307 * Get the message to write to the log after the request. 308 * @see #createMessage 309 */ 310 private String getAfterMessage(HttpServletRequest request) { 311 return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix); 312 } 313 314 /** 315 * Create a log message for the given request, prefix and suffix. 316 * <p>If {@code includeQueryString} is {@code true}, then the inner part 317 * of the log message will take the form {@code request_uri?query_string}; 318 * otherwise the message will simply be of the form {@code request_uri}. 319 * <p>The final message is composed of the inner part as described and 320 * the supplied prefix and suffix. 321 */ 322 protected String createMessage(HttpServletRequest request, String prefix, String suffix) { 323 StringBuilder msg = new StringBuilder(); 324 msg.append(prefix); 325 msg.append(request.getMethod()).append(" "); 326 msg.append(request.getRequestURI()); 327 328 if (isIncludeQueryString()) { 329 String queryString = request.getQueryString(); 330 if (queryString != null) { 331 msg.append('?').append(queryString); 332 } 333 } 334 335 if (isIncludeClientInfo()) { 336 String client = request.getRemoteAddr(); 337 if (StringUtils.hasLength(client)) { 338 msg.append(", client=").append(client); 339 } 340 HttpSession session = request.getSession(false); 341 if (session != null) { 342 msg.append(", session=").append(session.getId()); 343 } 344 String user = request.getRemoteUser(); 345 if (user != null) { 346 msg.append(", user=").append(user); 347 } 348 } 349 350 if (isIncludeHeaders()) { 351 HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders(); 352 if (getHeaderPredicate() != null) { 353 Enumeration<String> names = request.getHeaderNames(); 354 while (names.hasMoreElements()) { 355 String header = names.nextElement(); 356 if (!getHeaderPredicate().test(header)) { 357 headers.set(header, "masked"); 358 } 359 } 360 } 361 msg.append(", headers=").append(headers); 362 } 363 364 if (isIncludePayload()) { 365 String payload = getMessagePayload(request); 366 if (payload != null) { 367 msg.append(", payload=").append(payload); 368 } 369 } 370 371 msg.append(suffix); 372 return msg.toString(); 373 } 374 375 /** 376 * Extracts the message payload portion of the message created by 377 * {@link #createMessage(HttpServletRequest, String, String)} when 378 * {@link #isIncludePayload()} returns true. 379 * @since 5.0.3 380 */ 381 @Nullable 382 protected String getMessagePayload(HttpServletRequest request) { 383 ContentCachingRequestWrapper wrapper = 384 WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); 385 if (wrapper != null) { 386 byte[] buf = wrapper.getContentAsByteArray(); 387 if (buf.length > 0) { 388 int length = Math.min(buf.length, getMaxPayloadLength()); 389 try { 390 return new String(buf, 0, length, wrapper.getCharacterEncoding()); 391 } 392 catch (UnsupportedEncodingException ex) { 393 return "[unknown]"; 394 } 395 } 396 } 397 return null; 398 } 399 400 401 /** 402 * Determine whether to call the {@link #beforeRequest}/{@link #afterRequest} 403 * methods for the current request, i.e. whether logging is currently active 404 * (and the log message is worth building). 405 * <p>The default implementation always returns {@code true}. Subclasses may 406 * override this with a log level check. 407 * @param request current HTTP request 408 * @return {@code true} if the before/after method should get called; 409 * {@code false} otherwise 410 * @since 4.1.5 411 */ 412 protected boolean shouldLog(HttpServletRequest request) { 413 return true; 414 } 415 416 /** 417 * Concrete subclasses should implement this method to write a log message 418 * <i>before</i> the request is processed. 419 * @param request current HTTP request 420 * @param message the message to log 421 */ 422 protected abstract void beforeRequest(HttpServletRequest request, String message); 423 424 /** 425 * Concrete subclasses should implement this method to write a log message 426 * <i>after</i> the request is processed. 427 * @param request current HTTP request 428 * @param message the message to log 429 */ 430 protected abstract void afterRequest(HttpServletRequest request, String message); 431 432}