spring AbstractRequestLoggingFilter 源码

  • 2022-08-08
  • 浏览 (353)

spring AbstractRequestLoggingFilter 代码

文件路径:/spring-web/src/main/java/org/springframework/web/filter/AbstractRequestLoggingFilter.java

/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.filter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.function.Predicate;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;

/**
 * Base class for {@code Filter}s that perform logging operations before and after a request
 * is processed.
 *
 * <p>Subclasses should override the {@code beforeRequest(HttpServletRequest, String)} and
 * {@code afterRequest(HttpServletRequest, String)} methods to perform the actual logging
 * around the request.
 *
 * <p>Subclasses are passed the message to write to the log in the {@code beforeRequest} and
 * {@code afterRequest} methods. By default, only the URI of the request is logged. However,
 * setting the {@code includeQueryString} property to {@code true} will cause the query string of
 * the request to be included also; this can be further extended through {@code includeClientInfo}
 * and {@code includeHeaders}. The payload (body content) of the request can be logged via the
 * {@code includePayload} flag: Note that this will only log the part of the payload which has
 * actually been read, not necessarily the entire body of the request.
 *
 * <p>Prefixes and suffixes for the before and after messages can be configured using the
 * {@code beforeMessagePrefix}, {@code afterMessagePrefix}, {@code beforeMessageSuffix} and
 * {@code afterMessageSuffix} properties.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 1.2.5
 * @see #beforeRequest
 * @see #afterRequest
 */
public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter {

	/**
	 * The default value prepended to the log message written <i>before</i> a request is
	 * processed.
	 */
	public static final String DEFAULT_BEFORE_MESSAGE_PREFIX = "Before request [";

	/**
	 * The default value appended to the log message written <i>before</i> a request is
	 * processed.
	 */
	public static final String DEFAULT_BEFORE_MESSAGE_SUFFIX = "]";

	/**
	 * The default value prepended to the log message written <i>after</i> a request is
	 * processed.
	 */
	public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";

	/**
	 * The default value appended to the log message written <i>after</i> a request is
	 * processed.
	 */
	public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";

	private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 50;


	private boolean includeQueryString = false;

	private boolean includeClientInfo = false;

	private boolean includeHeaders = false;

	private boolean includePayload = false;

	@Nullable
	private Predicate<String> headerPredicate;

	private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH;

	private String beforeMessagePrefix = DEFAULT_BEFORE_MESSAGE_PREFIX;

	private String beforeMessageSuffix = DEFAULT_BEFORE_MESSAGE_SUFFIX;

	private String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX;

	private String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX;


	/**
	 * Set whether the query string should be included in the log message.
	 * <p>Should be configured using an {@code <init-param>} for parameter name
	 * "includeQueryString" in the filter definition in {@code web.xml}.
	 */
	public void setIncludeQueryString(boolean includeQueryString) {
		this.includeQueryString = includeQueryString;
	}

	/**
	 * Return whether the query string should be included in the log message.
	 */
	protected boolean isIncludeQueryString() {
		return this.includeQueryString;
	}

	/**
	 * Set whether the client address and session id should be included in the
	 * log message.
	 * <p>Should be configured using an {@code <init-param>} for parameter name
	 * "includeClientInfo" in the filter definition in {@code web.xml}.
	 */
	public void setIncludeClientInfo(boolean includeClientInfo) {
		this.includeClientInfo = includeClientInfo;
	}

	/**
	 * Return whether the client address and session id should be included in the
	 * log message.
	 */
	protected boolean isIncludeClientInfo() {
		return this.includeClientInfo;
	}

	/**
	 * Set whether the request headers should be included in the log message.
	 * <p>Should be configured using an {@code <init-param>} for parameter name
	 * "includeHeaders" in the filter definition in {@code web.xml}.
	 * @since 4.3
	 */
	public void setIncludeHeaders(boolean includeHeaders) {
		this.includeHeaders = includeHeaders;
	}

	/**
	 * Return whether the request headers should be included in the log message.
	 * @since 4.3
	 */
	protected boolean isIncludeHeaders() {
		return this.includeHeaders;
	}

	/**
	 * Set whether the request payload (body) should be included in the log message.
	 * <p>Should be configured using an {@code <init-param>} for parameter name
	 * "includePayload" in the filter definition in {@code web.xml}.
	 * @since 3.0
	 */
	public void setIncludePayload(boolean includePayload) {
		this.includePayload = includePayload;
	}

	/**
	 * Return whether the request payload (body) should be included in the log message.
	 * @since 3.0
	 */
	protected boolean isIncludePayload() {
		return this.includePayload;
	}

	/**
	 * Configure a predicate for selecting which headers should be logged if
	 * {@link #setIncludeHeaders(boolean)} is set to {@code true}.
	 * <p>By default this is not set in which case all headers are logged.
	 * @param headerPredicate the predicate to use
	 * @since 5.2
	 */
	public void setHeaderPredicate(@Nullable Predicate<String> headerPredicate) {
		this.headerPredicate = headerPredicate;
	}

	/**
	 * The configured {@link #setHeaderPredicate(Predicate) headerPredicate}.
	 * @since 5.2
	 */
	@Nullable
	protected Predicate<String> getHeaderPredicate() {
		return this.headerPredicate;
	}

	/**
	 * Set the maximum length of the payload body to be included in the log message.
	 * Default is 50 characters.
	 * @since 3.0
	 */
	public void setMaxPayloadLength(int maxPayloadLength) {
		Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' should be larger than or equal to 0");
		this.maxPayloadLength = maxPayloadLength;
	}

	/**
	 * Return the maximum length of the payload body to be included in the log message.
	 * @since 3.0
	 */
	protected int getMaxPayloadLength() {
		return this.maxPayloadLength;
	}

	/**
	 * Set the value that should be prepended to the log message written
	 * <i>before</i> a request is processed.
	 */
	public void setBeforeMessagePrefix(String beforeMessagePrefix) {
		this.beforeMessagePrefix = beforeMessagePrefix;
	}

	/**
	 * Set the value that should be appended to the log message written
	 * <i>before</i> a request is processed.
	 */
	public void setBeforeMessageSuffix(String beforeMessageSuffix) {
		this.beforeMessageSuffix = beforeMessageSuffix;
	}

	/**
	 * Set the value that should be prepended to the log message written
	 * <i>after</i> a request is processed.
	 */
	public void setAfterMessagePrefix(String afterMessagePrefix) {
		this.afterMessagePrefix = afterMessagePrefix;
	}

	/**
	 * Set the value that should be appended to the log message written
	 * <i>after</i> a request is processed.
	 */
	public void setAfterMessageSuffix(String afterMessageSuffix) {
		this.afterMessageSuffix = afterMessageSuffix;
	}


	/**
	 * The default value is "false" so that the filter may log a "before" message
	 * at the start of request processing and an "after" message at the end from
	 * when the last asynchronously dispatched thread is exiting.
	 */
	@Override
	protected boolean shouldNotFilterAsyncDispatch() {
		return false;
	}

	/**
	 * Forwards the request to the next filter in the chain and delegates down to the subclasses
	 * to perform the actual request logging both before and after the request is processed.
	 * @see #beforeRequest
	 * @see #afterRequest
	 */
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		boolean isFirstRequest = !isAsyncDispatch(request);
		HttpServletRequest requestToUse = request;

		if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
			requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength());
		}

		boolean shouldLog = shouldLog(requestToUse);
		if (shouldLog && isFirstRequest) {
			beforeRequest(requestToUse, getBeforeMessage(requestToUse));
		}
		try {
			filterChain.doFilter(requestToUse, response);
		}
		finally {
			if (shouldLog && !isAsyncStarted(requestToUse)) {
				afterRequest(requestToUse, getAfterMessage(requestToUse));
			}
		}
	}

	/**
	 * Get the message to write to the log before the request.
	 * @see #createMessage
	 */
	private String getBeforeMessage(HttpServletRequest request) {
		return createMessage(request, this.beforeMessagePrefix, this.beforeMessageSuffix);
	}

	/**
	 * Get the message to write to the log after the request.
	 * @see #createMessage
	 */
	private String getAfterMessage(HttpServletRequest request) {
		return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix);
	}

	/**
	 * Create a log message for the given request, prefix and suffix.
	 * <p>If {@code includeQueryString} is {@code true}, then the inner part
	 * of the log message will take the form {@code request_uri?query_string};
	 * otherwise the message will simply be of the form {@code request_uri}.
	 * <p>The final message is composed of the inner part as described and
	 * the supplied prefix and suffix.
	 */
	protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
		StringBuilder msg = new StringBuilder();
		msg.append(prefix);
		msg.append(request.getMethod()).append(' ');
		msg.append(request.getRequestURI());

		if (isIncludeQueryString()) {
			String queryString = request.getQueryString();
			if (queryString != null) {
				msg.append('?').append(queryString);
			}
		}

		if (isIncludeClientInfo()) {
			String client = request.getRemoteAddr();
			if (StringUtils.hasLength(client)) {
				msg.append(", client=").append(client);
			}
			HttpSession session = request.getSession(false);
			if (session != null) {
				msg.append(", session=").append(session.getId());
			}
			String user = request.getRemoteUser();
			if (user != null) {
				msg.append(", user=").append(user);
			}
		}

		if (isIncludeHeaders()) {
			HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
			if (getHeaderPredicate() != null) {
				Enumeration<String> names = request.getHeaderNames();
				while (names.hasMoreElements()) {
					String header = names.nextElement();
					if (!getHeaderPredicate().test(header)) {
						headers.set(header, "masked");
					}
				}
			}
			msg.append(", headers=").append(headers);
		}

		if (isIncludePayload()) {
			String payload = getMessagePayload(request);
			if (payload != null) {
				msg.append(", payload=").append(payload);
			}
		}

		msg.append(suffix);
		return msg.toString();
	}

	/**
	 * Extracts the message payload portion of the message created by
	 * {@link #createMessage(HttpServletRequest, String, String)} when
	 * {@link #isIncludePayload()} returns true.
	 * @since 5.0.3
	 */
	@Nullable
	protected String getMessagePayload(HttpServletRequest request) {
		ContentCachingRequestWrapper wrapper =
				WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
		if (wrapper != null) {
			byte[] buf = wrapper.getContentAsByteArray();
			if (buf.length > 0) {
				int length = Math.min(buf.length, getMaxPayloadLength());
				try {
					return new String(buf, 0, length, wrapper.getCharacterEncoding());
				}
				catch (UnsupportedEncodingException ex) {
					return "[unknown]";
				}
			}
		}
		return null;
	}


	/**
	 * Determine whether to call the {@link #beforeRequest}/{@link #afterRequest}
	 * methods for the current request, i.e. whether logging is currently active
	 * (and the log message is worth building).
	 * <p>The default implementation always returns {@code true}. Subclasses may
	 * override this with a log level check.
	 * @param request current HTTP request
	 * @return {@code true} if the before/after method should get called;
	 * {@code false} otherwise
	 * @since 4.1.5
	 */
	protected boolean shouldLog(HttpServletRequest request) {
		return true;
	}

	/**
	 * Concrete subclasses should implement this method to write a log message
	 * <i>before</i> the request is processed.
	 * @param request current HTTP request
	 * @param message the message to log
	 */
	protected abstract void beforeRequest(HttpServletRequest request, String message);

	/**
	 * Concrete subclasses should implement this method to write a log message
	 * <i>after</i> the request is processed.
	 * @param request current HTTP request
	 * @param message the message to log
	 */
	protected abstract void afterRequest(HttpServletRequest request, String message);

}

相关信息

spring 源码目录

相关文章

spring CharacterEncodingFilter 源码

spring CommonsRequestLoggingFilter 源码

spring CompositeFilter 源码

spring CorsFilter 源码

spring DelegatingFilterProxy 源码

spring FormContentFilter 源码

spring ForwardedHeaderFilter 源码

spring GenericFilterBean 源码

spring HiddenHttpMethodFilter 源码

spring OncePerRequestFilter 源码

0  赞