/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * 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 * * http://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 com.alibaba.citrus.webx.util; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import static java.util.Collections.*; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.slf4j.MDC; /** * 设置或清除logging MDC的工具类。 * <p> * 该工具类可被用于valve和filter中。在请求开始的时候,调用<code>setLoggingContext()</code>,结束时调用 * <code>clearLoggingContext()</code>。 如在<code>clearLoggingContext()</code> * 之前,多次调用<code>setLoggingContext()</code>,不会增加额外的开销。 * </p> * <p> * 调用<code>setLoggingContext()</code>之后,SLF4j * MDC中会创建如下值,这些值可在logback或log4j配置文件中直接引用。 * </p> * <table border="1" cellpadding="5"> * <tr> * <td colspan="2"><strong>请求信息</strong></td> * </tr> * <tr> * <td>%X{method}</td> * <td>请求类型:GET、POST</td> * </tr> * <tr> * <td>%X{requestURL}</td> * <td>完整的URL</td> * </tr> * <tr> * <td>%X{requestURLWithQueryString}</td> * <td>完整的URL,含querydata</td> * </tr> * <tr> * <td>%X{requestURI}</td> * <td>不包括host信息的URL</td> * </tr> * <tr> * <td>%X{requestURIWithQueryString}</td> * <td>不包括host信息的URL,含querydata</td> * </tr> * <tr> * <td>%X{queryString}</td> * <td>Querydata</td> * </tr> * <tr> * <td>%X{cookies}</td> * <td>所有cookie的名称,以逗号分隔</td> * </tr> * <tr> * <td>%X{cookie.*}</td> * <td>指定cookie的值,例如:cookie.JSESSIONID</td> * </tr> * <tr> * <td colspan="2"><strong>客户端信息</strong></td> * </tr> * <tr> * <td>%X{remoteAddr}</td> * <td>用户IP地址</td> * </tr> * <tr> * <td>%X{remoteHost}</td> * <td>用户域名(也可能是IP地址)</td> * </tr> * <tr> * <td>%X{userAgent}</td> * <td>用户浏览器</td> * </tr> * <tr> * <td>%X{referrer}</td> * <td>上一个链接</td> * </tr> * </table> * * @author Michael Zhou */ public class SetLoggingContextHelper { public static final String MDC_METHOD = "method"; public static final String MDC_REQUEST_URL = "requestURL"; public static final String MDC_REQUEST_URL_WITH_QUERY_STRING = "requestURLWithQueryString"; public static final String MDC_REQUEST_URI = "requestURI"; public static final String MDC_REQUEST_URI_WITH_QUERY_STRING = "requestURIWithQueryString"; public static final String MDC_QUERY_STRING = "queryString"; public static final String MDC_REMOTE_ADDR = "remoteAddr"; public static final String MDC_REMOTE_HOST = "remoteHost"; public static final String MDC_USER_AGENT = "userAgent"; public static final String MDC_REFERRER = "referrer"; public static final String MDC_COOKIES = "cookies"; public static final String MDC_COOKIE_PREFIX = "cookie."; private final static ThreadLocal<Integer> mdcRequestInfoHasAlreadyBeenSet = new ThreadLocal<Integer>(); private final HttpServletRequest request; public SetLoggingContextHelper(HttpServletRequest request) { this.request = assertNotNull(request, "request"); } /** 设置request信息到mdc。 */ public void setLoggingContext() { setLoggingContext(null); } /** 设置request信息和其它信息(如果有的话)到mdc。 */ public void setLoggingContext(Map<String, String> extra) { boolean setRequestInfo = testAndSetRequestInfo(); boolean setExtra = extra != null && !extra.isEmpty(); if (setRequestInfo || setExtra) { Map<String, String> mdc = getMDCCopy(); if (setRequestInfo) { setRequestInfo(mdc); } if (setExtra) { mdc.putAll(extra); } setMDC(mdc); } } /** * 清除MDC。 * <p> * 只有当前对象自己设置的MDC才能被清除。 * </p> */ public void clearLoggingContext() { Integer flag = mdcRequestInfoHasAlreadyBeenSet.get(); if (flag != null && flag.intValue() == hashCode()) { mdcRequestInfoHasAlreadyBeenSet.remove(); clearMDC(); } } protected void setRequestInfo(Map<String, String> mdc) { // GET or POST putMDC(mdc, MDC_METHOD, request.getMethod()); // request URL:完整的URL StringBuffer requestURL = request.getRequestURL(); String queryString = trimToNull(request.getQueryString()); putMDC(mdc, MDC_REQUEST_URL, getRequestURL(requestURL, null)); putMDC(mdc, MDC_REQUEST_URL_WITH_QUERY_STRING, getRequestURL(requestURL, queryString)); // request URI:不包括host信息的URL String requestURI = request.getRequestURI(); String requestURIWithQueryString = queryString == null ? requestURI : requestURI + "?" + queryString; putMDC(mdc, MDC_REQUEST_URI, requestURI); putMDC(mdc, MDC_REQUEST_URI_WITH_QUERY_STRING, requestURIWithQueryString); putMDC(mdc, MDC_QUERY_STRING, queryString); // client info putMDC(mdc, MDC_REMOTE_HOST, request.getRemoteHost()); putMDC(mdc, MDC_REMOTE_ADDR, request.getRemoteAddr()); // user agent putMDC(mdc, MDC_USER_AGENT, request.getHeader("User-Agent")); // referrer putMDC(mdc, MDC_REFERRER, request.getHeader("Referer")); // cookies Cookie[] cookies = request.getCookies(); List<String> names = emptyList(); if (cookies != null) { names = createArrayList(cookies.length); for (Cookie cookie : cookies) { names.add(cookie.getName()); putMDC(mdc, MDC_COOKIE_PREFIX + cookie.getName(), cookie.getValue()); } sort(names); } putMDC(mdc, MDC_COOKIES, names.toString()); } private boolean testAndSetRequestInfo() { if (mdcRequestInfoHasAlreadyBeenSet.get() == null) { mdcRequestInfoHasAlreadyBeenSet.set(hashCode()); return true; } return false; } /** * 取得当前的request URL,包括query string。 * * @return 当前请求的request URL */ private String getRequestURL(StringBuffer requestURL, String queryString) { int length = requestURL.length(); try { if (queryString != null) { requestURL.append('?').append(queryString); } return requestURL.toString(); } finally { requestURL.setLength(length); } } /** 设置mdc,如果value为空,则不置入。 */ private void putMDC(Map<String, String> mdc, String key, String value) { if (value != null) { mdc.put(key, value); } } /** 取得当前MDC map的复本。 */ @SuppressWarnings("unchecked") protected Map<String, String> getMDCCopy() { Map<String, String> mdc = MDC.getCopyOfContextMap(); if (mdc == null) { mdc = createHashMap(); } return mdc; } /** 将map中的值设置到MDC中。 */ protected void setMDC(Map<String, String> mdc) { MDC.setContextMap(mdc); } /** 清理MDC。 */ protected void clearMDC() { MDC.clear(); } }