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
相关文章
相关标签/搜索