springboot使用logback的MDC做日志规范,便于日志系统监控

首先:修改NGINX的配置文件

 #在请求端生成一个全局唯一的Id,根据这个id查看整个日志的调用链,注意NGINX版本要求1.11以上
 proxy_set_header   X-Request-Id     $request_id;
 #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
 proxy_set_header   X-Real-IP        $remote_addr;
 proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

其次:添加spring boot aop的依赖包

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第三步:编写AOP切面代码,对关键信息进行拦截


import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
@Configuration
@Aspect
public class LogMDCAspectConfig {


    @Pointcut("execution(public * com.neusoft.www.imagerecognition.controller..*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void before(JoinPoint joinPoint){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        assert sra != null;
        HttpServletRequest request = sra.getRequest();
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String queryName=getFieldsName(joinPoint,request);
        String x_request_id = request.getHeader("X-Request-Id");
        String x_real_ip = getCliectIp(request);
        //nginx返回的唯一请求Id
        MDC.put("X_REQUEST_ID", x_request_id);
        //本项目自动生成的唯一请求Id
        MDC.put("TRACE_ID",UUID.randomUUID().toString());
        //请求的服务器的真实的IP地址
        MDC.put("X_REAL_IP",x_real_ip);
        //服务请求路径
        MDC.put("REQUEST_URI",url);
        //服务请求的方法,post或者get
        MDC.put("REMOTE_ADDR_METHOD",method);
        //服务的请求的参数
        MDC.put("QUERY_NAME",queryName);
    }

    /**
     * 对于涉及到ThreadLocal相关使用的接口,都需要去考虑在使用完上下文对象时,
     * 清除掉对应的数据,以避免内存泄露问题
     * @param ret
     */
    @AfterReturning(pointcut = "webLog()", returning = "ret")
    public void afterReturning(Object ret){
        MDC.remove("X_REQUEST_ID");
        MDC.remove("TRACE_ID");
        MDC.remove("X_REAL_IP");
        MDC.remove("REQUEST_URI");
        MDC.remove("REMOTE_ADDR_METHOD");
        MDC.remove("QUERY_NAME");
    }

    /**
     * 获取客户段的IP
     * @param request
     * @return
     */
    private static String getCliectIp(HttpServletRequest request)
    {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 多个路由时,取第一个非unknown的ip
        final String[] arr = ip.split(",");
        for (final String str : arr) {
            if (!"unknown".equalsIgnoreCase(str)) {
                ip = str;
                break;
            }
        }
        return ip;
    }

    /**
     * 获取请求的参数
     * @param joinPoint
     * @return
     */
    private static  String getFieldsName(JoinPoint joinPoint,HttpServletRequest request) {
        String method = request.getMethod();
        String params = "";
        Object[] args = joinPoint.getArgs();
        String queryString = request.getQueryString();
        if (args.length > 0) {
            if ("POST".equals(method)) {
                Object object = args[0];
                Map map = getKeyAndValue(object);
                params = JSON.toJSONString(map);
                ;
            } else if ("GET".equals(method)) {
                params = queryString;
            }
        }
        return params;
    }

    /**
     * 获取post对象的参数参数值
     * @param obj
     * @return
     */
    private static Map<String,Object> getKeyAndValue(Object obj){
        Map<String, Object> map = new HashMap<>();
        // 得到类对象
        Class userCla = (Class)obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getDeclaredFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            f.setAccessible(true); // 设置属性是可以访问的
            Object val = new Object();
            try {
                val = f.get(obj);
                // 得到此属性的值
                map.put(f.getName(), val);// 设置键值
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }
}

第四步:编辑logback的MDC,规范日志格式

<?xml version="1.0" encoding="UTF-8"?>
<!-- 每隔一分钟扫描配置文件 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 设置上下文名称为 demo -->
    <contextName>imagerecognition</contextName>
    <!--设置系统日志目录-->
    <property name="APPDIR" value="imagerecognition"/>
    <!--9444_imagerecognition:端口号和对应的服务名称-->
    <property name="SPRING_PROFILES_ACTIVE" value="9444_imagerecognition"/>
    <!-- 定义日志输出格式变量:%d表示时间 花括号内为时间格式 %level表示日志级别 %thread表示线程名 %logger{0}表示输出日志的类名 [%line]表示行号用方括号包裹 %msg表示日志消息 %n换行 -->
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS,+08:00} [%t] ${SPRING_PROFILES_ACTIVE} %p %logger [%mdc{X_REQUEST_ID},%mdc{TRACE_ID},%mdc{X_REAL_IP},%mdc{REQUEST_URI},%mdc{REMOTE_ADDR_METHOD},%mdc{QUERY_NAME}] ${CONTEXT_NAME} - %m%n"/>
    <!-- 定义日志字符集 -->
    <property name="log.charset" value="UTF-8"/>
    <!-- 定义日志级别 -->
    <property name="log.level" value="INFO"/>
    <!-- 定义日志存放路径 -->
    <property name="log.path" value="logs"/>
    <!-- 输出到控制台 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志输出格式 -->
        <encoder>
            <!-- 日志字符集 -->
            <charset>${log.charset}</charset>
            <!-- 日志输出格式 -->
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 时间滚动输出日志 -->
    <appender name="COMMON" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 写入的文件名 -->
        <file>${log.path}/imagerecognition.log</file>
        <!-- 追加到文件结尾 -->
        <append>true</append>
        <!-- 滚动策略:按照每天生成日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径及文件名格式 -->
            <fileNamePattern>${log.path}/imagerecognition.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志文件保留天数 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>${log.charset}</charset>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!-- 日志记录器,日期滚动记录,即系统产生的错误日志信息 -->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--正在记录的日志文件的路径及文件名-->
        <file>${log.path}/error.log</file>
        <append>true</append>
        <!--日志记录器的滚动策略,按照日期,按大小记录-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--归档的日志文件的路径,%d{yyyy-MM-dd} 指定日期格式 %i指定索引-->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单日志文件最大限制100兆 超过则将文件内容归档到按照 fileNamePattern 命名的文件中 源文件则清空 -->
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 级别过滤器匹配 ERROR 级别日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <charset>${log.charset}</charset>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!-- 指定 com.neusoft.www.imagerecognition.service 包要使用的 appender 且不向上级传递 -->
    <logger name="com.neusoft.www.imagerecognition.service" level="DEBUG" additivity="false">
        <appender-ref ref="ERROR"/>
    </logger>

    <!-- 根 logger -->
    <root level="${log.level}">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="COMMON"/>
        <appender-ref ref="ERROR"/>
    </root>

</configuration>

你可能感兴趣的:(日志监控)