SpringBoot系統日誌設計及實現方案

#吐槽和說明 寫以前我得吐槽一下開源的產品汪,博客下面的「是否對全部人可見:」麻煩改爲是否我的可見!!!,我覺得那個是隻能關注的人才可見的,結果是私人可見。java

爲了把個人開源中國技能改回Java我這也是拼了....若是大家上一篇收藏了,這篇沒收藏我會傷心的。web

上一篇SpringBoot中的日誌配置,多環境日誌配置 裏面配置了多環境日誌記錄,這一篇是純架構乾貨,記得收藏哦,純代碼,在上一篇的基礎上修改spring

#設計方案及緣由 一個龐大的系統,或者一個訪問量高的系統,在發生異常的時候,你們定位問題是怎麼定位的呢?要求5分鐘以內知道問題引發的緣由並給出初步結論?多臺服務器經過F5分發的狀況下呢?sql

其實你們都是去看日誌,不過不少人用的都是默認日誌記錄,最可能是日誌裏記錄了執行sql,雖然SpringBoot的自己日誌很詳細了,可是定位不快速,要想快速定位須要一個請求串號,我須要知道這個請求串號惟一標識,直接找到這個串號範圍以內的日誌內容,這比你打開10MB,30MB裏面尋找快多了吧。api

#修改Logback日誌記錄格式緩存

先看一下上一節的日誌輸出格式,以下,使用的是PatternLayoutEncoder這個類,服務器

<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>

PatternLayoutEncoder類裏面用了PatternLayout這個類,這個類裏面就是聲明的格式字符串,架構

public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
    public PatternLayoutEncoder() {
    }

    public void start() {
        PatternLayout patternLayout = new PatternLayout();
        patternLayout.setContext(this.context);
        patternLayout.setPattern(this.getPattern());
        patternLayout.setOutputPatternAsHeader(this.outputPatternAsHeader);
        patternLayout.start();
        this.layout = patternLayout;
        super.start();
    }
}

PatternLayout類裏面的格式app

public class PatternLayout extends PatternLayoutBase<ILoggingEvent> {
    public static final Map<String, String> defaultConverterMap = new HashMap();
    public static final String HEADER_PREFIX = "#logback.classic pattern: ";

    static {
        defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
        defaultConverterMap.put("d", DateConverter.class.getName());
        defaultConverterMap.put("date", DateConverter.class.getName());
        defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
        defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());
        defaultConverterMap.put("level", LevelConverter.class.getName());
		....

那咱們來創建一個格式化的類,IpConverter,這個類用來記錄是那臺服務器IP及機器名產生的日誌。dom

/**
 * [@ClassName](https://my.oschina.net/u/3112573): BussinesLogger
 * @Description: 業務日誌記錄類
 * [@author](https://my.oschina.net/arthor) dengzongyu
 * [@date](https://my.oschina.net/u/2504391) 2016年12月5日 下午4:29:48
 *
 */
public class IpConverter extends ClassicConverter {
	
	/**
	 * 業務系統機器名稱
	 */
	private static String hostName = "";

	/**
	 * 業務系統機器的IP
	 */
	private static String hostIp = "";
	
	static {
		InetAddress ia = null;
		try {
			ia = InetAddress.getLocalHost();
			hostIp = ia.getHostAddress();
			hostName = ia.getHostName();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public String convert(ILoggingEvent event) {
		return new StringBuilder(hostIp).append(":").append(hostName).toString();
	}

}

而後寫一個類集成咱們上面提到的PatternLayout類,用來注入日誌記錄格式,格式多了個ip。

/**
 * @ClassName: GAPatternlayout
 * @Description: Logback配置文件注入
 * @author dengzongyu
 * @date 2016年12月5日 下午5:07:18
 *
 */
public class GAPatternlayout extends PatternLayout {

	static{
		defaultConverterMap.put("ip", IpConverter.class.getName());
	}
	
}

接下來修改logback-spring.xml配置文件,修改的位置就是修改那兩個格式輸出的位置,類改成咱們本身寫的,這樣子輸出出來的格式就是:服務器IP:服務器名字:日期,線程名,級別從左顯示5個字符寬度:日誌消息等...如今咱們日誌裏面容易定位出是哪臺服務器產生的異常了。

<layout class="jldp.portal.framework.logback.GAPatternlayout">
		<Pattern>%ip:%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -- %msg
			%n
		</Pattern>
	</layout>

#Web請求日誌記錄,日誌記錄串 上面那些基本夠用了,可是咱們想更細緻一些,好比用戶是請求哪一個API接口,請求參數是什麼,請求接口響應時間等等.... 這個講解都在代碼註釋裏面了,上面就不重複了,直接上代碼。裏面有個IPUtil這個是就是獲取客戶端IP的工具類,網上一大堆,就不貼了,各位根據本身的須要來,這個是我本身寫的,我以爲夠了,大家能夠設計的更加完美一些,爲了未來大數據分析考慮,寫成JSON格式的更好是不。

/**
 * @ClassName: WebRequestLogRecording
 * @Description: Web請求日誌記錄
 * @author dengzongyu
 * @date 2016年12月6日 下午3:40:25
 *
 */
@Aspect
@Component
@Order(-5)
public class WebRequestLogRecording {

	private static Logger logger = LoggerFactory.getLogger(WebRequestLogRecording.class);
	
	/**
	 * 每次請求的串
	 */
	private ThreadLocal<String> requestUUID = new ThreadLocal<String>();
	
	private ThreadLocal<Long> startTime = new ThreadLocal<Long>();
	
	/**
	 * 
	 * WebRequestLog: 定義攔截點
	 * @author dengzongyu
	 * @since JDK 1.8
	 */
	@Pointcut("execution(public * com.dengzy..controller..*.*(..))")
	public void webRequestLog(){}
	
	/**
	 * 
	 * doBefore:進入切入點前進行操做.
	 *
	 * @author dengzongyu
	 * @since JDK 1.8
	 */
	@Before("webRequestLog()")
	public void doBefore(JoinPoint joinPoint){
		//獲取UUID標識
		String REQUEST_UUID = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
		requestUUID.set(REQUEST_UUID);
		startTime.set(System.currentTimeMillis());
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		request.setAttribute("REQUEST_UUID", REQUEST_UUID);
		StringBuffer sbStr = new StringBuffer("ClentRequestInfoStar===REQUEST_UUID:")
				.append(REQUEST_UUID).append(",URL:")//監控參數
				.append(request.getRequestURL()).append(",")
				.append("HTTP_TYPE:").append(request.getMethod()).append(",")
				.append("CLASS_METHOD:").append(joinPoint.getSignature().getName()).append(",")
				.append("CLIENT_IP:").append(IPUtils.getClientIpAddress(request)).append(",")
				.append("PARAMS:");
		Enumeration<String> enu=request.getParameterNames(); 
        while(enu.hasMoreElements()){
        	String elementType = enu.nextElement();
            sbStr.append(elementType).append("=").append(request.getParameter(elementType)).append("&"); 
        }
        logger.info(sbStr.deleteCharAt(sbStr.length()-1).toString());
	}
	
    @AfterReturning(returning = "result", pointcut = "webRequestLog()")
    public void doAfterReturning(Object result) {
    	StringBuffer sbStr = new StringBuffer("ClentRequestInfoEnd===REQUEST_UUID:")
    			.append(requestUUID.get()).append(",REQUEST_TIME:"+(System.currentTimeMillis()-startTime.get())).append("ms");
        logger.info(sbStr.toString());
    }
}

我測試了一下,咱們上面整合完後的日誌打印狀況,注意日誌裏面的REQUEST_UUID,這個就是一個請求串,咱們只須要找REQUEST_UUID這個對應的開始於結束之間的日誌內容就能夠看出問題了哦。

192.168.135.1:LAPTOP-796KKEL9:10:38:11.667 [http-nio-8080-exec-1] INFO  j.p.f.logback.WebRequestLogRecording -- ClentRequestInfoStar===REQUEST_UUID:1E3B70307FB443B4A5710382F2B8C225,URL:http://localhost:8080/api/investment/fsdt/baseTypeController/getBaseTypeByKey,HTTP_TYPE:GET,CLASS_METHOD:getBaseTypeByKey,CLIENT_IP:0:0:0:0:0:0:0:1,PARAMS:dtbaseid=char
				
>>>>1212066477842432
819010491752910848
819010493019590656
819010494290464768
819010495569727488
819010496840601600
沒有緩存的時候,進入方法進行查詢啦
192.168.135.1:LAPTOP-796KKEL9:10:38:13.657 [http-nio-8080-exec-1] ERROR c.g.i.i.c.InvestBaseTypeController -- 1E3B70307FB443B4A5710382F2B8C225**********查詢條件不能爲空
				
192.168.135.1:LAPTOP-796KKEL9:10:38:14.007 [http-nio-8080-exec-1] ERROR c.g.i.i.c.InvestBaseTypeController -- >>>>>>>>>>>>>2
				
192.168.135.1:LAPTOP-796KKEL9:10:38:14.020 [http-nio-8080-exec-1] INFO  j.p.f.logback.WebRequestLogRecording -- ClentRequestInfoEnd===REQUEST_UUID:1E3B70307FB443B4A5710382F2B8C225,REQUEST_TIME:2395ms
相關文章
相關標籤/搜索