如何处理多线程并发时的日志追踪

MDC

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。

一种解决的办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的。MDC 的作用是解决这个问题。

MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。清单 5 中给出了 MDC 的使用示例。

清单 5. MDC 使用示例
 public class MdcSample { 
    private static final Logger LOGGER = Logger.getLogger("mdc"); 
    public void log() { 
        MDC.put("username", "Alex"); 
        if (LOGGER.isInfoEnabled()) { 
            LOGGER.info("This is a message."); 
        } 
    } 
 }

清单 5 中,在记录日志前,首先在 MDC 中保存了名称为“username”的数据。其中包含的数据可以在格式化日志记录时直接引用,如清单 6 所示,“%X{username}”表示引用 MDC 中“username”的值。

清单 6. 使用 MDC 中记录的数据
 log4j.appender.stdout.layout.ConversionPattern=%X{username} %d{yyyy-MM-dd HH:mm:ss} [%p] %c - %m%n

 

 

使用半结构化的日志消息

在介绍日志记录 API 中的格式化器时提到过,日志记录中除了基本的日志消息之外,还包括由日志框架提供的其他元数据。这些数据按照给定的格式出现在日志记录中。这些半结构化的格式使得可以通过工具提取日志记录中的相关信息进行分析。在使用日志 API 进行记录时,对于日志消息本身,也推荐使用半结构化的方式来组织。

比如一个电子商务的网站,当用户登录之后,该用户所产生的不同操作所对应的日志记录中都可以包含该用户的用户名,并以固定的格式出现在日志记录中,如清单 8 所示。

清单 8. 使用半结构化的日志消息
 [user1] 用户登录成功。
 [user1] 用户成功购买产品 A。
 [user2] 订单 003 付款失败。

当需要通过日志记录来排查某个用户所遇到的问题时,只需要通过正则表达就可以很快地查询到用户相关的日志记录。

 

 

日志聚合与分析

在程序中正确的地方输出合适的日志消息,只是合理使用日志的第一步。日志记录的真正作用在于当有问题发生时,能够帮助开发人员很快的定位问题所在。不过一个实用的系统通常由很多个不同的部分组成。这其中包括所开发的程序本身,也包括所依赖的第三方应用程序。以一个典型的电子商务网站为例,除了程序本身,还包括所依赖的底层操作系统、应用服务器、数据库、HTTP 服务器和代理服务器和缓存等。当一个问题发生时,真正的原因可能来自程序本身,也可能来自所依赖的第三方程序。这就意味着开发人员可能需要检查不同服务器上不同应用程序的日志来确定真正的原因。

日志聚合的作用就在于可以把来自不同服务器上不同应用程序产生的日志聚合起来,存放在单一的服务器上,方便进行搜索和分析。在日志聚合方面,已经有不少成熟的开源软件可以很好的满足需求。本文中要介绍的是 logstash,一个流行的事件和日志管理开源软件。logstash 采用了一种简单的处理模式:输入 -> 过滤器 -> 输出。logstash 可以作为代理程序安装到每台需要收集日志的机器上。logstash 提供了非常多的插件来处理不同类型的数据输入。典型的包括控制台、文件和 syslog 等;对于输入的数据,可以使用过滤器来进行处理。典型的处理方式是把日志消息转换成结构化的字段;过滤之后的结果可以被输出到不同的目的地,比如 ElasticSearch、文件、电子邮件和数据库等。

Logstash 在使用起来很简单。从官方网站下载 jar 包并运行即可。在运行时需要指定一个配置文件。配置文件中定义了输入、过滤器和输出的相关配置。清单 9 给出了一个简单的 logstash 配置文件的示例。

清单 9. logstash 配置文件示例
 input { 
  file { 
    path => [ "/var/log/*.log", "/var/log/messages", "/var/log/syslog" ] 
    type => 'syslog'
  } 
 } 

 output { 
  stdout { 
 debug => true 
 debug_format => "json" 
  } 
 }

清单 9 中定义了 logstash 收集日志时的输入(input)和输出(output)的相关配置。输入类型是文件(file)。每种类型输入都有相应的配置。对于文件来说,需要配置的是文件的路径。对每种类型的输入,都需要指定一个类型(type)。该类型用来区分来自不同输入的记录。代码中使用的输出是控制台。配置文件完成之后,通过“java -jar logstash-1.1.13-flatjar.jar agent -f logstash-simple.conf”就可以启动 logstash。

 

 

 

 

你可能感兴趣的:(软件架构)