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;
1
2
3
4
5
其次:添加spring boot aop的依赖包

org.springframework.boot spring-boot-starter-aop 1 2 3 4 第三步:编写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"?> imagerecognition ${log.charset} ${log.pattern}
<!-- 时间滚动输出日志 -->
<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>

原文链接:https://blog.csdn.net/sunyuhua_keyboard/article/details/107357948

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值