关于日志的一些总结

一 日志发展历史

    java使用了一种自定义的、可扩展的方法来输出日志。虽然Java通过java.util.logging包提供了一套基本的日志处理API,但你可以很轻松的使用一种或者多种其它日志解决方案。这些解决方案尽管使用不同的方法来创建日志数据,但它们的最终目标是一样的,即将日志从你的应用程序输出到目标地址。

    上面说的JUL是SUN的亲儿子,可惜啊亲儿子也没干过apache开源社区的log4j,log4j 是应用最泛的日志工具,已然成为了java日志的实际上的标准。还有其它一些开发包,例如SLF4JApache Commons Logging,它们提供了一些抽象层,对你的代码和日志框架进行解耦,从而允许你在不同的日志框架中进行切换。也就是说JCL 提供了统一的接口,当然SLF4J认为它设计的不好,又做了统一的抽象。这是出现接口层,具体的实现有出现了logback,log4j2(这两个框架都是日志大神Ceki Gülcü开发的,区别如下).Logback基于Log4j之前的版本开发(版本1),因此它们的功能集合都非常类似。然而,Log4j在最新版本(版本2)中引用了一些改进,例如支持多API,并提升了在用Disruptor库的性能。而tinylog,由于缺少了一些功能,运行特别快,非常适合小项目。

下面是个小对比。

日志框架

介绍

log4j – 最受欢迎的Java日志组件

Log4j是一款基于Java的开源日志组件,Log4j功能非常强大,我们可以将日志信息输出到控制台、文件、用户界面,也可以输出到操作系统的事件记录器和一些系统常驻进程。更值得一提的是,Log4j可以允许你非常便捷地自定义日志格式和日志等级,可以帮助开发人员全方位地掌控日志信息。

JUL – 日志小刀

JUL(java util logging)是JDK官方提供的一个记录日志的方式,直接在JDK中就可以使用。JUL 的有点是使用非常简单,直接在 JDK 中就可以使用。但 JUL 功能比较太过于简单,不支持占位符显示,拓展性比较差,所以现在用的人也很少。

JCL-适配器

JCL(Apache Commons Logging) 既是一个日志接口规范,也有一个简单的实现。有很多框架使用:如spring组件

SLF4J – 适配器

SLF4J(Simple Logging Facade for Java,即Java简单日志记录接口集)是一个日志的接口规范,它对用户提供了统一的日志接口,屏蔽了不同日志组件的差异。这样我们在编写代码的时候只需要看 SLF4J 这个接口文档即可,不需要去理会不同日之框架的区别。而当我们需要更换日志组件的时候,我们只需要更换一个具体的日志组件Jar包就可以了。

LogBack – 日志火箭

LogBack 其实可以说是 Log4J 的进化版,因为它们两个都是同一个人(Ceki Gülcü)设计的开源日志组件。LogBack 除了具备 Log4j 的所有优点之外,还解决了 Log4J 不能使用占位符的问题。

Log4j2

是log4j 1.x和logback的改进版,采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活

简单来说,就是接口层:slf4j使用的多,实现层:log4j,log4j2,logback个人选择。

二 日志基础

Java日志API由以下三个核心组件组成:

  • Loggers:Logger负责捕捉事件并将其发送给合适的Appender。
  • Appenders:也被称为Handlers,负责将日志事件记录到目标位置。在将日志事件输出之前,Appenders使用Layouts来对事件进行格式化处理。
  • Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。

2.1日志配置

    尽管所有的Java日志框架都可以通过代码进行配置,但是大部分配置还是通过外部配置文件完成的。这些文件决定了日志消息在何时通过什么方式进行处理,日志框架可以在运行时加载这些文件。在这一节中提供的大部分配置示例都使用了配置文件。

java.util.logging

 使用 logging.properties配置文件。用得少不做展开。

Log4j
Log4j版本1使用的语法和 java.util.logging 的语法很类似。使用了Log4j的程序会在项目目录中寻找一个名为 log4j.properties 的文件。默认情况下,Log4j配置会将所有日志消息输出到控制台上。Log4j同样也支持XML格式的配置文件,对应的配置信息会存储到 log4j.xml 文件中。
Log4j版本2支持XML、JSON和YAML格式的配置,这些配置会分别存储到 log4j2.xml、log4j2.json 和 log4j2.yaml 文件中。和版本1类似,版本2也会在工程目录中寻找这些文件。你可以在每个版本的文档中找到相应的配置文件示例。
Logback
对于Logback来说,大部分配置都是在 logback.xml 文件中完成的,这个文件使用了和Log4j类似的XML语法。Logback同时也支持通过Groovy语言的方式来进行配置,配置信息会存储到 logback.groovy 文件中。你可以通过每种类型配置文件的链接找到对应的配置文件示例。

2.2 logger

   Logger:用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性  

  • name:用来指定受此logger约束的某一个包或者具体的某一个类。  
  • level:用来设置打印级别(日志级别)
  • addtivity:是否向上级logger传递打印信息。默认是true。 一般情况下应该设置为false.  

2.3 Appenders

      Appenders将日志消息转发给期望的输出。它负责接收日志事件,使用Layout格式化事件,然后将其发送给对应的目标。对于一个日志事件,我们可以使用多个Appenders来将事件发送到不同的目标位置。例如,我们可以在控制台上显示一个简单的日志事件的同时,将其通过邮件的方式发送给指定的接收者。

    ConsoleAppender

ConsoleAppender是最常用的Appenders之一,它只是将日志消息显示到控制台上。许多日志框架都将其作为默认的Appender,并且在基本的配置中进行预配置。例如,在Log4j中ConsoleAppender的配置参数如下所示。

参数 描述
filter 用于决定是否需要使用该Appender来处理日志事件
layout 用于决定如何对日志记录进行格式化,默认情况下使用“%m%n”,它会在每一行显示一条日志记录
follow 用于决定Appender是否需要了解输出(system.out或者system.err)的变化,默认情况是不需要跟踪这种变化
name 用于设置Appender的名字
ignoreExceptions 用于决定是否需要记录在日志事件处理过程中出现的异常
target 用于指定输出目标位置,默认情况下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR

FileAppenders

    FileAppenders将日志记录写入到文件中,它负责打开、关闭文件,向文件中追加日志记录,并对文件进行加锁,以免数据被破坏或者覆盖。在Log4j中,如果想创建一个FileAppender,需要指定目标文件的名字,写入方式是追加还是覆盖,以及是否需要在写入日志时对文件进行加锁:

1

2

3

4

5

6

7

...

<Appenders>

  <File name="MyFileAppender" fileName="myLog.log" append="true" locking="true">

    <PatternLayout pattern="%m%n"/>

  </File>

</Appenders>

...

这样我们创建了一个名为MyFileAppender的FileAppender,并且在向文件中追加日志时会对文件进行加锁操作。

其他appender:

   SyslogAppenders将日志记录发送给本地或者远程系统的日志服务,需要指定syslog服务监听的主机号、端口号以及协议。Log4j中的RollingFileAppender扩展了FileAppender,它可以在满足特定条件时自动创建新的日志文件;SMTPAppender会将日志内容以邮件的形式发送出去;FailoverAppender会在处理日志的过程中,如果一个或者多个Appender失败,自动切换到其他Appender上。

2.4 Layouts

      Layouts将日志记录的内容从一种数据形式转换成另外一种。日志框架为纯文本、HTML、syslog、XML、JSON、序列化以及其它日志提供了Layouts.

在Log4j和Logback中最常用的Layouts是PatternLayout。它可以让你决定日志事件中的哪些部分需要输出,这是通过转换模式(Conversion Pattern)完成的,转换模式在每一条日志事件的数据中扮演了“占位符”的角色。例如,Log4j默认的PatternLayout使用了如下转换模式:

1

<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>

%d{HH:mm:ss.SSS} 将日期转换成时、分、秒和毫秒的形式,%level显示日志事件的严重程度,%C显示生成日志事件的类的名字,%t显示Logger的当前线程,%m显示时间的消息,最后,%n为下一个日志事件进行了换行。

2.5 JSON

   对出调用接口的出入参数来说,json的输出日志是非常合适的。

举例:我们使用了fastjson,

  logger.info("methodname param:{}", JSON.toJSONString(XXList));

三 日志追踪

   我上一篇介绍了日志追踪调用链系统。这里算是相关的补充说明。

   NDC、MDC和ThreadContext通过向单独的日志记录中添加独一无二的数据戳,来创建日志足迹(log trails)。这些数据戳也被称为鱼标记(fish tagging),我们可以通过一个或者多个独一无二的值来区分日志。这些数据戳在每个线程级别上进行管理,并且一直持续到线程结束,或者直到数据戳被删掉。例如,如果你的Web应用程序为每个用户生成一个新的线程,那么你可以使用这个用户的ID来标记日志记录。当你想在一个复杂的系统中跟踪特定的请求、事务或者用户,这是一种非常有用的方法。

  3.1 嵌套诊断上下文(NDC)

     NDC或者嵌套诊断上下文(Nested Diagnostic Context)是基于栈的思想,信息可以被放到栈上或者从栈中移除。而栈中的值可以被Logger访问,并且Logger无需显示想日志方法中传入任何值。

import java.io.FileReader;
import org.apache.Log4j.Logger;
import org.apache.Log4j.NDC;
...
String username = "admin";
String sessionID = "1234";
NDC.push(username);
NDC.push(sessionID);
try {
  // tmpFile doesn't exist, causing an exception.
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file.");
}
finally {
  NDC.pop();
  NDC.pop();
  NDC.remove();
}

   Log4j的PatternLayout类通过%x转换字符从NDC中提取值。如果一个日志事件被触发,那么完整的NDC栈就被传到Log4j:

1

<PatternLayout pattern="%x %-5p - %m%n" />

运行程序后,我们可以得出下面的输出:

1

"admin 1234 ERROR – Unable to open file."

3.2映射诊断上下文(MDC)

     MDC或者映射诊断上下文和NDC很相似,不同之处在于MDC将值存储在键值对中,而不是栈中。这样你可以很容易的在Layout中引用一个单独的键。MDC.put(key,value) 方法将一个新的键值对添加到上下文中,而 MDC.remove(key) 方法会移除指定的键值对。

如果想在日志中同样显示用户名和会话ID,我们需要使用 MDC.put() 方法将这两个变量存储成键值对:

import java.io.FileReader;
import org.apache.Log4j.Logger;
import org.apache.Log4j.MDC;
...
MDC.put("username", "admin");
MDC.put("sessionID", "1234");
try {
  // tmpFile doesn't exist, causing an exception.
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file!");
}
finally {
  MDC.clear();
}

     在日志框架中访问MDC的值时,也稍微有些区别。对于存储在上下文中的任何键,我们可以使用%X(键)的方式来访问对应的值。这样,我们可以使用 %X(username) 和 %X(sessionID) 来获取对应的用户名和会话ID:

1

2

<PatternLayout pattern="%X{username} %X{sessionID} %-5p - %m%n" />

"admin 1234 ERROR – Unable to open file!"

如果我们没有指定任何键,那么MDC上下文就会被以 {(key, value),(key, value)} 的方式传递给Appender。

3.3 ThreadContext

  Log4j2中将MDC和NDC合并到一个单独的组件中,这个组件被称为线程上下文。线程上下文是针对MDC和NDC的进化,它分别用线程上下文Map映射线程上下文栈来表示MDC和NDC。我们可以通过ThreadContext静态类来管理线程上下文,这个类在实现上类似于Log4j版本1中的MDC和NDC。

ThreadContext.put("traceId",traceId);

ThreadContext.remove("traceId");

ThreadContext类提供了一些方法,用于清除栈、清除MDC、清除存储在上下文中的所有值,对应的方法是ThreadContext.clearAll()、ThreadContext.clearMap()和ThreadContext.clearStack()。

和在MDC以及NDC中一样,我们可以使用Layouts在线程上下文中访问这些值。使用PatternLayout时,%x转换模式会从栈中获取值,%X和%X(键)会从图中获取值。

3.4 filter

BurstFilter:控制并发数

CompositeFilter:组合过滤器

ThresholdFilter:日志级别的过滤器

TimeFilter:基于时间段的过滤器

配置文件的onMatch 和 onMismatch 会决定如何处理它。关于 onMatch 和 onMismatch,我们有三个可选项:ACCEPT,它会处理过滤器的规则;DENY,它会忽略过滤器的规则;NEUTRAL,它会推迟到下一个过滤器。

这里没有一一展开使用。

log4j2的配置demo:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
        <Appenders>
                <Console name="myConsole" target="SYSTEM_OUT">
                        <PatternLayout pattern="[%d{MM-dd HH:mm:ss,SSS} %-5p] [%t] [%X{traceId}] %c{2\} - %m%n%ex" />
                </Console>
                <RollingFile name="activexAppender" fileName="../log/aaa/aaa.log"
                             filePattern="../log/aaa/aaa.log.%d{yyyy-MM-dd}.log">
                        <PatternLayout>
                                <Pattern>[%d{MM-dd HH:mm:ss SSS} %-5level] [%t] [%X{traceId}] %c{3} - %m%n%ex</Pattern>
                        </PatternLayout>
                        <Policies>
                                <TimeBasedTriggeringPolicy />
                        </Policies>
                </RollingFile>
                <RollingFile name="c_trace" fileName="../log/aaa/c_trace.log"
                             filePattern="../log/aaa/c_trace.log.%d{yyyy-MM-dd}.log">
                        <PatternLayout>
                                <Pattern>[%d{MM-dd HH:mm:ss SSS} %-5level] [%t] %c{3} - %m%n%ex
                                </Pattern>
                        </PatternLayout>
                        <Policies>
                                <TimeBasedTriggeringPolicy />
                        </Policies>
                </RollingFile>
                <RollingFile name="Data" fileName="../log/aaa/refreshdata.log"
                             filePattern="../log/aaa/refreshdata.log.%d{yyyy-MM-dd}.log">
                        <PatternLayout>
                                <Pattern>[%d{MM-dd HH:mm:ss SSS} %-5level] [%t] %c{3} - %m%n%ex
                                </Pattern>
                        </PatternLayout>
                        <Policies>
                                <TimeBasedTriggeringPolicy />
                        </Policies>
                </RollingFile>
        </Appenders>
        <Loggers>
                <Root level="info">
                        <AppenderRef ref="myConsole" />
                        <AppenderRef ref="activexAppender" />
                </Root>
                <!-- dsf-client日志配置 -->
                <logger name="dsf_client_trace" level="debug" additivity="false">
                        <AppenderRef ref="c_trace" />
                </logger>
        </Loggers>
</Configuration>

四 实践建议

日志名称

日志含义

Debug

用于开发人员开发、调试使用。除特殊调试需求,不要在线上生产环境开启DEBUG级别。

Info

重要的业务处理流程,使用info级别日志记录。在实际环境中,系统管理员或者高级用户要能理解INFO输出的信息并能很快的了解应用正在做什么。比如,一个和处理机票预订的系统,对每一张票要有且只有一条INFO信息描述 "[Who] booked ticket from [Where] to [Where]"。另外一种对INFO信息的定义是:记录显著改变应用状态的每一个action,比如:数据库更新、外部系统请求。

Warn

主业务处理流程可以继续,但必须对这个问题给予额外关注的,打warn级别日志。这个问题又可以细分成两种情况:一种是存在严重的问题但有应急措施(比如数据库不可用,使用Cache);第二种是潜在问题及建议(ATTENTION),比如生产环境的应用运行在Development模式下、管理控制台没有密码保护等。系统可以允许这种错误的存在,但必须及时做跟踪检查。

Error

系统中发生了非常严重的问题,必须马上有人进行处理,则打error级别日志。比如:NPEs(空指针异常),数据库不可用,关键业务流程中断等等。error log需接入error日志监控功能,并配置相应报警

1.推荐用日志组件:slf4j+log4j2  或 slf4j+logback,记录日志时通过调用 SLF4J 的 API

2.不要用字符串拼接,建议使用 slf4j  的占位符方式,

如: LOGGER.debug("returning content"+content);//bad

LOGGER.debug("returning content: {}", content);//good

3.异常日志打印,打印堆栈信息

   logger.error("XXmsg",e);

4. 避免打印敏感信息,如身份证号、银行卡号等.毕竟线上机器日志查看权限大,不如db那么严格。

5. 日志接入调用链系统,加入TreaceID便于跟踪,排查问题。

6. 接口的出入参(尤其是跟外部系统联调),都要按照json格式输出。便于排查

7.所有出处打印ERROR日志。

8. 日志参数格式建议打全:作发生时间、日志级别、线程ID、日志内容,建议同时打印文件名(类名)和行号。

还有一条:关注日志输出,尤其是大量日志的建议有运维定期去压缩处理,定期备份,不然会频繁接到磁盘空间不足报警。

报警了马上磁盘满了,这里怎么去快速清空日志救急啊,假设日志是a.log,不要执行 rm -f a.log.因为文件虽然删了,但是Tomcat还在运行,需要重启才能释放空间。着急忙慌的再去找目录,还不一定有权限,echo 就好。 

参考:

http://www.importnew.com/16331.html

猜你喜欢

转载自blog.csdn.net/bohu83/article/details/83072712