文章目录

  • (一)日志框架
    • 1. 日志框架介绍和选择
    • 2. 原理介绍
    • 3. SpringBoot日志框架
  • (二)使用MDC增强日志记录
    • 1. 介绍
    • 2. 普通示例
    • 3. 在Log4j中使用MDC
    • 4. 在SLF4J/LogBack中使用MDC
    • 5. MDC和线程池

(一)日志框架

1. 日志框架介绍和选择


日志门面:是日志实现的抽象层。

日志实现:具体的日志功能的实现

为什么不直接使用日志实现,而是又弄了一个叫日志门面的东西?

因为日志实现,可能会有一些代码的优化和改动,避免影响用户在项目中的使用,使用日志门面这些统一的接口,假设在实现层代码做了更改,用户在项目中使用日志而调用的接口等等都是不会受影响的。

推荐使用:SLF4j+logback组合

2. 原理介绍

slf4j主要是为了给Java日志访问提供一个标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。

3. SpringBoot日志框架


SpringBoot默认使用SLF4j+logback组合

(二)使用MDC增强日志记录

1. 介绍

Mapped Diagnostic Context(MDC)的基本思想是提供一种方法,用一些在日志记录实际发生的范围内不可用的信息来丰富日志消息,但这些信息确实有助于更好地跟踪程序的执行。

2. 普通示例

让我们从一个例子开始。假设我们必须编写一个软件来转移资金。我们设置了一个传输类来表示一些基本信息:唯一的传输id和发送方的名称:

/*** 转账类*/
@Data
public class Transfer {/*** 转账事务唯一标识*/private String transactionId;/*** 收款方*/private String sender;/*** 转账金额*/private BigDecimal amount;
}

要执行传输,我们需要使用API支持的服务:下面提供一个抽象类TransferService ,其中transfer用于实现具体的转账操作,应用了模板方法设计模式,在转账前后分别调用了相关记录方法

/*** 转账服务* @author  zhangyu*/
public  abstract  class TransferService {public boolean transfer(BigDecimal amount) {beforeTransfer(amount);//调用第三方转账接口afterTransfer(amount,true);return true;}abstract protected void beforeTransfer(BigDecimal amount);abstract protected void afterTransfer(BigDecimal amount, boolean outcome);
}

可以重写beforeTransfer()和afterTransfer()方法,以便在传输完成之前和之后运行自定义代码。
我们将利用beforeTransfer()和afterTransfer()来记录有关传输的一些信息。
让我们创建服务实现:

public class LogTransferService extends TransferService {private Logger logger = Logger.getLogger(LogTransferService.class);@Overrideprotected void beforeTransfer(BigDecimal amount) {logger.info("Preparing to transfer " + amount + "$.");}@Overrideprotected void afterTransfer(BigDecimal amount, boolean outcome) {logger.info("Has transfer of " + amount + "$ completed successfully ? " + outcome + ".");}
}

这里要注意的主要问题是,创建日志消息时,无法访问传输对象—只能访问金额,从而无法记录事务id或发送方。
让我们设置通常的log4j.properties文件来登录控制台:

log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=%-4r [%t] %5p %c %x - %m%n
log4j.rootLogger = TRACE, consoleAppender

这里简单创建一个转账事务工厂类,生成测试数据

public class TransactionFactory {private  final Random random=new Random();private final List<String>   senderList= Arrays.asList("Alice","Tom","Bob");public Transfer newInstance() {Transfer transfer=new Transfer();transfer.setSender(senderList.get(random.nextInt(2)));transfer.setAmount(new BigDecimal(random.nextInt(10000)+100));transfer.setTransactionId(UUID.randomUUID().toString());return  transfer;}}

重写处理线程类

public class Log4JRunnable implements Runnable {private Transfer tx;private  TransferService transferService=new LogTransferService();public Log4JRunnable(Transfer tx) {this.tx = tx;}@Overridepublic void run() {transferService.transfer(tx.getAmount());}
}

最后,让我们设置一个小应用程序,它能够通过ExecutorService同时运行多个传输:

public class TransferDemo {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);TransactionFactory transactionFactory = new TransactionFactory();for (int i = 0; i < 10; i++) {Transfer tx = transactionFactory.newInstance();Runnable task = new Log4JRunnable(tx);executor.submit(task);}executor.shutdown();}
}

当我们运行同时管理多个传输的演示应用程序时,我们很快就会发现日志并不像我们希望的那样有用。跟踪每次转移的执行情况很复杂,因为记录的唯一有用的信息是转移的金额和执行特定转移的线程的名称。
此外,无法区分由同一线程执行的相同数量的两个不同事务,因为相关的日志行看起来基本相同:

3. 在Log4j中使用MDC

Log4j中的MDC允许我们用appender在实际写入日志消息时可以访问的信息片段填充一个类似于map的结构。
MDC结构以与ThreadLocal变量相同的方式在内部连接到正在执行的线程。
【设计思路】
1:将我们需要的信息填充到MDC中,线程独立
2:记录日志信息
3:清除MDC
为了检索存储在MDC中的变量,应该更改appender的模式。

【代码修改】
在自定义线程类中通过MDC添加需要的信息

public class Log4JRunnable implements Runnable {private Transfer tx;private  TransferService transferService=new LogTransferService();public Log4JRunnable(Transfer tx) {this.tx = tx;}@Overridepublic void run() {MDC.put("transaction.id", tx.getTransactionId());MDC.put("transaction.owner", tx.getSender());transferService.transfer(tx.getAmount());MDC.clear();}
}

MDC.put()用于在MDC中添加键和相应的值,而MDC.clear()则清空MDC。

现在让我们更改log4j.properties以打印刚刚存储在MDC中的信息。对于要记录的MDC中包含的每个条目,使用%X{}占位符来更改转换模式就足够了:

log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=  %-4r [%t] %5p %c{1} %x - %m - tx.id=%X{transaction.id} tx.owner=%X{transaction.owner}%n
log4j.rootLogger = TRACE, consoleAppender

现在,如果我们运行应用程序,我们会注意到每一行还包含有关正在处理的事务的信息,这使我们更容易跟踪应用程序的执行:

4. 在SLF4J/LogBack中使用MDC

MDC在SLF4J中也可用,条件是底层日志库支持MDC。Logback和Log4j都支持MDC,所以我们不需要特别的东西就可以在标准设置中使用它。
让我们准备通常的TransferService子类,这次使用Java的简单日志Facade:

【说明】
代码层面没有任何改变,只需要注意下日志配置文件即可logback.xml

<configuration><appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%-4r [%t] %5p %c{1} - %m - tx.id=%X{transaction.id} tx.owner=%X{transaction.owner}%n</pattern></encoder></appender><root level="TRACE"><appender-ref ref="stdout" /></root>
</configuration>

5. MDC和线程池

MDC实现通常使用ThreadLocals来存储上下文信息。这是实现线程安全的简单而合理的方法。但是,我们应该小心地将MDC与线程池一起使用。

下面分析下基于ThreadLocal的mdc和线程池的组合可能存在的一些问题

我们从线程池中得到一个线程。然后我们使用MDC.put()或ThreadContext.put()在MDC中存储一些上下文信息。我们在一些日志中使用了这些信息,但不知怎么的,我们忘记了清除MDC上下文。借用的线程返回到线程池。一段时间后,应用程序从池中获取相同的线程。因为我们上次没有清理MDC,所以这个线程仍然拥有上一次执行的一些数据。

这可能会导致执行之间出现一些意外的不一致。防止这种情况的一种方法是始终记住在每次执行结束时清除MDC上下文。这种方法通常需要严格的人工监督,因此容易出错。

另一种方法是使用自定义线程池ThreadPoolExecutor,并在每次执行之后执行必要的清理。为此,我们可以扩展ThreadPoolExecutor类并重写afterExecute():

public class MdcAwareThreadPoolExecutor extends ThreadPoolExecutor {public MdcAwareThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}/*** 线程执行完毕,清理MDC数据* @param r 线程* @param t 异常*/@Overrideprotected void afterExecute(Runnable r, Throwable t) {MDC.clear();ThreadContext.clearAll();}
}

这样,MDC清理将在每次正常或异常执行之后自动进行。因此,无需手动操作:

现在我们可以用新的executor实现重新编写相同的示例:

public class TransferDemo {public static void main(String[] args) {ExecutorService executor = new MdcAwareThreadPoolExecutor(3, 3, 0, MINUTES,new LinkedBlockingQueue<>(), Thread::new, new ThreadPoolExecutor.AbortPolicy());TransactionFactory transactionFactory = new TransactionFactory();for (int i = 0; i < 10; i++) {Transfer tx = transactionFactory.newInstance();Runnable task = new Log4JRunnable(tx);executor.submit(task);}executor.shutdown();}
}

使用MDC增强日志记录相关推荐

  1. LogBack sl4j 通过MDC实现日志记录区分用户Session[以Spring mvc为例]

    1.首先实现一个interceptor,在请求开始的时候MDC put一个Session标志,interceptor结束的时候remove掉 import javax.servlet.http.Htt ...

  2. jboss7 关闭日志打印_使用自定义日志记录处理程序在JBoss AS 7中跟踪SQL语句

    jboss7 关闭日志打印 使用ORM从您的特定数据库中提取数据并让其创建和发布您必须亲自编写的所有SQL语句似乎很方便. 这就是使ORM解决方案受欢迎的原因. 但是它也有一个缺点:由于ORM为您做了 ...

  3. 使用自定义日志记录处理程序在JBoss AS 7中跟踪SQL语句

    使用ORM从您的特定数据库中提取数据,并让它创建和发布您必须亲自编写的所有SQL语句似乎很方便. 这就是使ORM解决方案受欢迎的原因. 但是它也有一个缺点:由于ORM为您做了很多工作,因此您在某种程度 ...

  4. SpringBoot 项目使用 SLF4J+logback 进行日志记录,来增强可维护性

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 作者:云深不知处 blog. ...

  5. 自定义注解妙用,一行代码搞定用户操作日志记录,你学会了吗?

    来源:https://blog.csdn.net/yjt520557/article/details/85099115 | 简介 我在使用spring完成项目的时候需要完成记录日志,我开始以为Spri ...

  6. 自定义注解妙用,一行代码搞定用户操作日志记录

    1.简介 在使用spring完成项目的时候需要完成记录日志,开始以为Spring 的AOP功能,就可以轻松解决,半个小时都不用,可是经过一番了解过后,发现一般的日志记录,只能记录一些简单的操作,例如表 ...

  7. springmvc+log4j操作日志记录,详细配置

    没有接触过的,先了解一下:log4j教程 部分内容来:log4j教程 感谢! 需要导入包: log包:log4j-12.17.jar 第一步:web.xml配置 <!-- log4j配置,文件路 ...

  8. 三种方式实现日志记录

    对于日志和事件的记录在每个项目中都会用到,如果在每个manager层中触发时间记录的话,会比较难以扩展和维护,所以可配置的日 志和事件记录在项目中会用到! 一.拦截器实现日志记录 (一)首先配置一个自 ...

  9. 如何自行给指定的SAP OData服务添加自定义日志记录功能

    有的时候,SAP标准的OData实现或者相关的工具没有提供我们想记录的日志功能,此时可以利用SAP系统强大的扩展特性,进行自定义日志功能的二次开发. 以SAP CRM Fiori应用"My ...

最新文章

  1. 朴素贝叶斯法(二)——基本方法
  2. webview加载html跳转,WebView加载网页(二)
  3. 【桌面虚拟化】之五PCoIP
  4. Ubuntu 20.04 搜索引擎环境搭建 (PostgreSQL 12.3, Redis 6, ELK[Elasticsearch 7.8, Logstash 7.8, Kibana 7.8])
  5. 天气预测频繁2项集_986天气| 今年冬天比往年更冷?官方回应来了
  6. Python使用多进程批量判断素数
  7. 全栈开发永远成不了高级程序员?!
  8. 推荐一款思维在线思维导图,为什么?
  9. BlogEngine
  10. 记一次spirngMVC整合HttpPrinter的过程
  11. 华为交换机冗余链路(VRRP)和vlan负载均衡
  12. NanoPi R2S 专用软件源
  13. 计算2个GPS坐标的距离
  14. 泰国80亿互联网记录数据库泄漏,疑遭黑客攻击
  15. 使用七牛云存储解决app部署问题,免申请https认证
  16. Dell Inspiron15-7567 拆机插放内存条步骤
  17. Android 好看的登录界面
  18. 华为android加固,app安全加固学习记录
  19. MMA-mathematica数值求解非线性偏微分方程组
  20. 常用设计模式-策略模式+工厂模式+模板模式(使用场景、解决方案)

热门文章

  1. 最新shsh备份详细教程(现在只能备份最新的固件)
  2. 首都师范 博弈论 4 3 1公共物品的供给博弈
  3. 【PTA】解密英文藏头诗
  4. 如何增加新浪微博粉丝数
  5. 在Unity使用PureMVC
  6. python爬虫项目经验_爬虫项目经验总结
  7. 【吴恩达机器学习】第二周课程精简笔记——多元线性回归和计算参数分析
  8. 走进MSTP -- 7. 软硬管道
  9. 超详细MapReduce程序实现WordCount案例
  10. 前端报错如何在服务器中显示,详解Vue项目中出现Loading chunk {n} failed问题的解决方法...