001/* 002 * Copyright 2002-2017 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 javax.servlet.FilterChain; 022import javax.servlet.ServletException; 023import javax.servlet.http.HttpServletRequest; 024import javax.servlet.http.HttpServletResponse; 025import javax.servlet.http.HttpSession; 026 027import org.springframework.http.server.ServletServerHttpRequest; 028import org.springframework.util.Assert; 029import org.springframework.util.StringUtils; 030import org.springframework.web.util.ContentCachingRequestWrapper; 031import org.springframework.web.util.WebUtils; 032 033/** 034 * Base class for {@code Filter}s that perform logging operations before and after a request 035 * is processed. 036 * 037 * <p>Subclasses should override the {@code beforeRequest(HttpServletRequest, String)} and 038 * {@code afterRequest(HttpServletRequest, String)} methods to perform the actual logging 039 * around the request. 040 * 041 * <p>Subclasses are passed the message to write to the log in the {@code beforeRequest} and 042 * {@code afterRequest} methods. By default, only the URI of the request is logged. However, 043 * setting the {@code includeQueryString} property to {@code true} will cause the query string 044 * of the request to be included also. The payload (body) of the request can be logged via the 045 * {@code includePayload} flag. Note that this will only log that which is read, which might 046 * not be the entire payload. 047 * 048 * <p>Prefixes and suffixes for the before and after messages can be configured using the 049 * {@code beforeMessagePrefix}, {@code afterMessagePrefix}, {@code beforeMessageSuffix} and 050 * {@code afterMessageSuffix} properties. 051 * 052 * @author Rob Harrop 053 * @author Juergen Hoeller 054 * @author Rossen Stoyanchev 055 * @since 1.2.5 056 * @see #beforeRequest 057 * @see #afterRequest 058 */ 059public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter { 060 061 public static final String DEFAULT_BEFORE_MESSAGE_PREFIX = "Before request ["; 062 063 public static final String DEFAULT_BEFORE_MESSAGE_SUFFIX = "]"; 064 065 public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request ["; 066 067 public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]"; 068 069 private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 50; 070 071 072 private boolean includeQueryString = false; 073 074 private boolean includeClientInfo = false; 075 076 private boolean includeHeaders = false; 077 078 private boolean includePayload = false; 079 080 private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH; 081 082 private String beforeMessagePrefix = DEFAULT_BEFORE_MESSAGE_PREFIX; 083 084 private String beforeMessageSuffix = DEFAULT_BEFORE_MESSAGE_SUFFIX; 085 086 private String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX; 087 088 private String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX; 089 090 091 /** 092 * Set whether the query string should be included in the log message. 093 * <p>Should be configured using an {@code <init-param>} for parameter name 094 * "includeQueryString" in the filter definition in {@code web.xml}. 095 */ 096 public void setIncludeQueryString(boolean includeQueryString) { 097 this.includeQueryString = includeQueryString; 098 } 099 100 /** 101 * Return whether the query string should be included in the log message. 102 */ 103 protected boolean isIncludeQueryString() { 104 return this.includeQueryString; 105 } 106 107 /** 108 * Set whether the client address and session id should be included in the 109 * log message. 110 * <p>Should be configured using an {@code <init-param>} for parameter name 111 * "includeClientInfo" in the filter definition in {@code web.xml}. 112 */ 113 public void setIncludeClientInfo(boolean includeClientInfo) { 114 this.includeClientInfo = includeClientInfo; 115 } 116 117 /** 118 * Return whether the client address and session id should be included in the 119 * log message. 120 */ 121 protected boolean isIncludeClientInfo() { 122 return this.includeClientInfo; 123 } 124 125 /** 126 * Set whether the request headers should be included in the log message. 127 * <p>Should be configured using an {@code <init-param>} for parameter name 128 * "includeHeaders" in the filter definition in {@code web.xml}. 129 * @since 4.3 130 */ 131 public void setIncludeHeaders(boolean includeHeaders) { 132 this.includeHeaders = includeHeaders; 133 } 134 135 /** 136 * Return whether the request headers should be included in the log message. 137 * @since 4.3 138 */ 139 protected boolean isIncludeHeaders() { 140 return this.includeHeaders; 141 } 142 143 /** 144 * Set whether the request payload (body) should be included in the log message. 145 * <p>Should be configured using an {@code <init-param>} for parameter name 146 * "includePayload" in the filter definition in {@code web.xml}. 147 * @since 3.0 148 */ 149 public void setIncludePayload(boolean includePayload) { 150 this.includePayload = includePayload; 151 } 152 153 /** 154 * Return whether the request payload (body) should be included in the log message. 155 * @since 3.0 156 */ 157 protected boolean isIncludePayload() { 158 return this.includePayload; 159 } 160 161 /** 162 * Set the maximum length of the payload body to be included in the log message. 163 * Default is 50 characters. 164 * @since 3.0 165 */ 166 public void setMaxPayloadLength(int maxPayloadLength) { 167 Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' should be larger than or equal to 0"); 168 this.maxPayloadLength = maxPayloadLength; 169 } 170 171 /** 172 * Return the maximum length of the payload body to be included in the log message. 173 * @since 3.0 174 */ 175 protected int getMaxPayloadLength() { 176 return this.maxPayloadLength; 177 } 178 179 /** 180 * Set the value that should be prepended to the log message written 181 * <i>before</i> a request is processed. 182 */ 183 public void setBeforeMessagePrefix(String beforeMessagePrefix) { 184 this.beforeMessagePrefix = beforeMessagePrefix; 185 } 186 187 /** 188 * Set the value that should be appended to the log message written 189 * <i>before</i> a request is processed. 190 */ 191 public void setBeforeMessageSuffix(String beforeMessageSuffix) { 192 this.beforeMessageSuffix = beforeMessageSuffix; 193 } 194 195 /** 196 * Set the value that should be prepended to the log message written 197 * <i>after</i> a request is processed. 198 */ 199 public void setAfterMessagePrefix(String afterMessagePrefix) { 200 this.afterMessagePrefix = afterMessagePrefix; 201 } 202 203 /** 204 * Set the value that should be appended to the log message written 205 * <i>after</i> a request is processed. 206 */ 207 public void setAfterMessageSuffix(String afterMessageSuffix) { 208 this.afterMessageSuffix = afterMessageSuffix; 209 } 210 211 212 /** 213 * The default value is "false" so that the filter may log a "before" message 214 * at the start of request processing and an "after" message at the end from 215 * when the last asynchronously dispatched thread is exiting. 216 */ 217 @Override 218 protected boolean shouldNotFilterAsyncDispatch() { 219 return false; 220 } 221 222 /** 223 * Forwards the request to the next filter in the chain and delegates down to the subclasses 224 * to perform the actual request logging both before and after the request is processed. 225 * @see #beforeRequest 226 * @see #afterRequest 227 */ 228 @Override 229 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 230 throws ServletException, IOException { 231 232 boolean isFirstRequest = !isAsyncDispatch(request); 233 HttpServletRequest requestToUse = request; 234 235 if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) { 236 requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength()); 237 } 238 239 boolean shouldLog = shouldLog(requestToUse); 240 if (shouldLog && isFirstRequest) { 241 beforeRequest(requestToUse, getBeforeMessage(requestToUse)); 242 } 243 try { 244 filterChain.doFilter(requestToUse, response); 245 } 246 finally { 247 if (shouldLog && !isAsyncStarted(requestToUse)) { 248 afterRequest(requestToUse, getAfterMessage(requestToUse)); 249 } 250 } 251 } 252 253 /** 254 * Get the message to write to the log before the request. 255 * @see #createMessage 256 */ 257 private String getBeforeMessage(HttpServletRequest request) { 258 return createMessage(request, this.beforeMessagePrefix, this.beforeMessageSuffix); 259 } 260 261 /** 262 * Get the message to write to the log after the request. 263 * @see #createMessage 264 */ 265 private String getAfterMessage(HttpServletRequest request) { 266 return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix); 267 } 268 269 /** 270 * Create a log message for the given request, prefix and suffix. 271 * <p>If {@code includeQueryString} is {@code true}, then the inner part 272 * of the log message will take the form {@code request_uri?query_string}; 273 * otherwise the message will simply be of the form {@code request_uri}. 274 * <p>The final message is composed of the inner part as described and 275 * the supplied prefix and suffix. 276 */ 277 protected String createMessage(HttpServletRequest request, String prefix, String suffix) { 278 StringBuilder msg = new StringBuilder(); 279 msg.append(prefix); 280 msg.append("uri=").append(request.getRequestURI()); 281 282 if (isIncludeQueryString()) { 283 String queryString = request.getQueryString(); 284 if (queryString != null) { 285 msg.append('?').append(queryString); 286 } 287 } 288 289 if (isIncludeClientInfo()) { 290 String client = request.getRemoteAddr(); 291 if (StringUtils.hasLength(client)) { 292 msg.append(";client=").append(client); 293 } 294 HttpSession session = request.getSession(false); 295 if (session != null) { 296 msg.append(";session=").append(session.getId()); 297 } 298 String user = request.getRemoteUser(); 299 if (user != null) { 300 msg.append(";user=").append(user); 301 } 302 } 303 304 if (isIncludeHeaders()) { 305 msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders()); 306 } 307 308 if (isIncludePayload()) { 309 ContentCachingRequestWrapper wrapper = 310 WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); 311 if (wrapper != null) { 312 byte[] buf = wrapper.getContentAsByteArray(); 313 if (buf.length > 0) { 314 int length = Math.min(buf.length, getMaxPayloadLength()); 315 String payload; 316 try { 317 payload = new String(buf, 0, length, wrapper.getCharacterEncoding()); 318 } 319 catch (UnsupportedEncodingException ex) { 320 payload = "[unknown]"; 321 } 322 msg.append(";payload=").append(payload); 323 } 324 } 325 } 326 327 msg.append(suffix); 328 return msg.toString(); 329 } 330 331 332 /** 333 * Determine whether to call the {@link #beforeRequest}/{@link #afterRequest} 334 * methods for the current request, i.e. whether logging is currently active 335 * (and the log message is worth building). 336 * <p>The default implementation always returns {@code true}. Subclasses may 337 * override this with a log level check. 338 * @param request current HTTP request 339 * @return {@code true} if the before/after method should get called; 340 * {@code false} otherwise 341 * @since 4.1.5 342 */ 343 protected boolean shouldLog(HttpServletRequest request) { 344 return true; 345 } 346 347 /** 348 * Concrete subclasses should implement this method to write a log message 349 * <i>before</i> the request is processed. 350 * @param request current HTTP request 351 * @param message the message to log 352 */ 353 protected abstract void beforeRequest(HttpServletRequest request, String message); 354 355 /** 356 * Concrete subclasses should implement this method to write a log message 357 * <i>after</i> the request is processed. 358 * @param request current HTTP request 359 * @param message the message to log 360 */ 361 protected abstract void afterRequest(HttpServletRequest request, String message); 362 363}