MDCLog4j+ELK完美日志方案

来源:互联网 发布:男士穿衣软件 编辑:程序博客网 时间:2024/05/17 10:03

LoggerMDCFilter设置MDC参数

@WebFilter(filterName="log",urlPatterns={"/*"})public class LoggerMDCFilter extends OncePerRequestFilter implements Filter{    protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain chain)throws ServletException,IOException {        try {              TraceUtils.beginTrace();            MDC.put("req.requestURI", StringUtils.defaultString(request.getRequestURI()));            MDC.put("req.queryString", StringUtils.defaultString(request.getQueryString()));            MDC.put("req.requestURIWithQueryString", request.getRequestURI() + (request.getQueryString() == null ? "" : "?"+request.getQueryString()));            MDC.put("req.remoteAddr", StringUtils.defaultString(request.getRemoteAddr()));            //为每一个请求创建一个ID,方便查找日志时可以根据ID查找出一个http请求所有相关日志            MDC.put("req.id", StringUtils.remove(UUID.randomUUID().toString(),"-"));            chain.doFilter(request, response);        }finally {            clearMDC();        }    }    private void clearMDC() {        TraceUtils.endTrace();    }}

appender配置

<!-- 控制台输出 -->   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">          <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->         <!--<pattern>%X{traceId} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->        <pattern>%X{traceId} %X{req.remoteAddr}/%X{req.id} - %X{req.requestURI}?%X{req.queryString}] %-5p %c{2} - %m%n</pattern>    </encoder></appender>

看一下MDClog4j效果


NDC和MDC

NDC(Nested Diagnostic Context)和MDC(Mapped Diagnostic Context)是log4j种非常有用的两个类,它们用于存储应用程序的上下文信息(context infomation),从而便于在log中使用这些上下文信息。

 

NDC采用了一个类似栈的机制来push和pop上下文信息,每一个线程都独立地储存上下文信息。比如说一个servlet就可以针对每一个request创建对应的NDC,储存客户端地址等等信息。

当使用的时候,我们要尽可能确保在进入一个context的时候,把相关的信息使用NDC.push(message);在离开这个context的时候使用NDC.pop()将信息删除。另外由于设计上的一些问题,还需要保证在当前thread结束的时候使用NDC.remove()清除内存,否则会产生内存泄漏的问题。

存储了上下文信息之后,我们就可以在log的时候将信息输出。在相应的PatternLayout中使用”%x”来输出存储的上下文信息,下面是一个PatternLayout的例子:

%r [%t] %-5p %c{2} %x - %m%n

使用NDC最重要的好处就是,当我们想输出一些上下文的信息的时候,不需要让logger去寻找这些信息,而只需要在适当的位置进行存储,然后再配置文件中修改PatternLayout。在最新的log4j 1.3版本中增加了一个org.apache.log4j.filters.NDCMatchFilter,用来

根据NDC中存储的信息接受或拒绝一条log信息。

 

MDC和NDC非常相似,所不同的是MDC内部使用了类似map的机制来存储信息,上下文信息也是每个线程独立地储存,所不同的是信息都是以它们的key值存储在”map”中。相对应的方法,MDC.put(key, value); MDC.remove(key); MDC.get(key); 在配置PatternLayout的时候使用:%x{key}来输出对应的value。同样地,MDC也有一个org.apache.log4j.filters.MDCMatchFilter。这里需要注意的一点,MDC是线程独立的,但是一个子线程会自动获得一个父线程MDC的copy。

至于选择NDC还是MDC要看需要存储的上下文信息是堆栈式的还是key/value形式的。

 

动态修改日志配置

在开发过程中,我们经常会遇到修改log4j配置的情况,在这种情况下,频繁重启应用显然是不可接受的。幸好log4j提供了自动重新加载配置文件的能力,在配置文件修改后,便会自己重新加载配置。在1.2及以前的版本中DOMConfigurator和PropertyConfigurator都提供了configureAndWatch方法,对指定的配置文件进行监控,并且可以设置检查的间隔时间。

在开发与生产环境中,我们有时候需要对日志的配置进行动态切换,要调试、监控和检查系统的运行时信息。

  一般有两种方法 

  1、通过 Log4j的 Log4jConfigListener 在启动时开启定时器进行定时加载配置文件 
  2、通过 JMX 动态控制 

  简单介绍一下上面两种方法: 

  1、通过 Log4j的 Log4jConfigListener,实现方法比较简单,原理是需要在后台打开一个监听线程,定时扫描,然后来定时工作.

             实现方法如下:

           

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.     * 装载log4j配置文件  
  3.     */    
  4.    public static void load() {    
  5.        String path = Log4jConfig.class.getClass().getResource("/").getPath()+ "log4j.properties";    
  6.        System.out.println("log4j configfile path=" + path);    
  7.          
  8.        // 间隔特定时间,检测文件是否修改,自动重新读取配置     
  9.        PropertyConfigurator.configureAndWatch(path,1000);  
  10.    }    


  2、通过 JMX 动态控制的则必须供一个管理的端口,不仅有可能端口被占用(当然有个 workaround 来解决它),还有存在防火墙等等需要配置这个管理端口进行对外暴露等等。 

  虽然上述两种方法存在着一些不足,但是这两种方法在特定的场合下,都可以很好地来利用它进行完美地工作。 

  现在,利用它进行封装与扩展,我们可以巧妙地进行定制,并通过 Web Console 界面来更方便地进行动态切换配置信息,而且不需要重新启动正在运行中的应用程序。 



海量可视化日志分析平台之ELK搭建

1、elasticsearch-1.7.3

启动:./bin/elasticsearch -d
端口:9200 9300
2、logstash-1.5.1-1
启动:./bin/logstash -f conf/stash-log4j.conf
端口:4560
3、kibana-4.1.2
启动:./kibana &
端口:5601

0 0