在一个服务内的业务处理通常需要不同类的共同协作来完成,比如controller接收请求,校验参数,然后调用service类的业务处理方法,而service又需要调用持久层的方法来读取和写入数据,为了在日志中,将各个部分的调用参数关联起来,特别是在出现错误或失败的时候,能够快速地定位出现错误的业务信息,可以借助于mdc机制。
1. 设置mdc信息
package com.demo.order.controller;
import com.demo.order.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Random;
@RestController
@RequestMapping("/order")
public class OrderController {
Logger log = LoggerFactory.getLogger(getClass());
@Autowired
OrderService orderService;
@RequestMapping("/add")
public String add()
{
HashMap<String, String> map = new HashMap<String, String>();
Random rnd = new Random();
int i = rnd.nextInt(100000);
map.put("sessionId", "12" + i);
i = rnd.nextInt(100);
map.put("userId", "test" + i);
i = rnd.nextInt(1000);
map.put("mobile", "138000" + i);
MDC.setContextMap(map);
log.info("控制层接收到请求");
log.info("控制层参数校验");
log.debug("控制层调用服务层");
orderService.getOrder();
log.info("控制层处理完成");
MDC.clear();
return "Hello world!";
}
}
这里只是为了演示方便,实际项目中,可以将参数获取和写入mdc的功能通过拦截器实现。
MDC.setContextMap(),将参数名和参数值以map的形式保存到mdc中。
MDC.put(),可以每次设置一个参数名和参数值。
这里设置的属性名称应与logback.xml文件中的日志pattern中的属性名称一致。
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] [%X{userId}] [%X{sessionId}] [%X{mobile}] %msg - %logger{20}[%method,%line]%n" />
2. 使用mdc中的信息
package com.demo.order.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
Logger log = LoggerFactory.getLogger(getClass());
public String getOrder()
{
log.info("服务层接收到请求");
log.info("服务层调用持久层");
log.info("服务层执行业务处理");
log.debug("服务器层输出调试信息");
log.info("服务层完成业务处理");
return "123456789";
}
}
3. 执行测试
通过浏览器访问,查看日志输出。
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 控制层接收到请求 - c.f.o.c.OrderController[add,168]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 控制层参数校验 - c.f.o.c.OrderController[add,170]
2022-03-04 14:57:31 DEBUG [http-nio-8010-exec-1] [test19] [1251387] [138000270] 控制层调用服务层 - c.f.o.c.OrderController[add,172]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 服务层接收到请求 - c.f.o.s.OrderService[getOrder,17]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 服务层调用持久层 - c.f.o.s.OrderService[getOrder,19]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 服务层执行业务处理 - c.f.o.s.OrderService[getOrder,21]
2022-03-04 14:57:31 DEBUG [http-nio-8010-exec-1] [test19] [1251387] [138000270] 服务器层输出调试信息 - c.f.o.s.OrderService[getOrder,23]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 服务层完成业务处理 - c.f.o.s.OrderService[getOrder,25]
2022-03-04 14:57:31 INFO [http-nio-8010-exec-1] [test19] [1251387] [138000270] 控制层处理完成 - c.f.o.c.OrderController[add,176]
可以看到在controller中设置的mdc参数,不管是在controller,还是在service中,输出日志信息时,都自动输出了对应的参数信息。
4. logback.xml配置
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] [%X{userId}] [%X{sessionId}] [%X{mobile}] %msg - %logger{20}[%method,%line]%n" />
输出mdc参数的写法为:%X{mobile},其中的字符串为设置的mdc属性名。
由于springboot默认支持logback,所以并不需要单独添加maven依赖。