暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Log日志规范以及打印方法

荒凉 技术路 2020-06-16
2529

1、日志规范

1.1、背景

用户行为留痕、调用链追踪,大数据分析,线上问题定位和解决都离不开合理的日志规范,所有应用按同样的规范打印日志可为后续的工作带来极大的便利性。

1.2、日志框架选择

Slf4j 是为 Java 提供的简单日志门面。它允许用户以自己的喜好,在工程中通过 Slf4j 接入不同的日志系统。

现在常用的是logback和log4j2,其中logback是slf4j的原生实现框架,与log4j相比性能更加出众,

spring boot默认是支持logback的;log4j2出生更晚,已经不仅仅是log4j的升级,它还参考了logback的设计,并且据说在异步方面性能更加出众。如果需要用log4j2,需要在相关的spring包里剔除log相关的依赖。

  1. <!-- Web -->

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-web</artifactId>

  5. <!-- 如果在使用自带tomcat请注释下面,如果使用第三方tomcat不要注释下面 -->

  6. <exclusions>

  7. <exclusion>

  8. <groupId>org.springframework.boot</groupId>

  9. <artifactId>spring-boot-starter-logging</artifactId>

  10. </exclusion>

  11. <exclusion>

  12. <artifactId>logback-classic</artifactId>

  13. <groupId>ch.qos.logback</groupId>

  14. </exclusion>

  15. </exclusions>

  16. </dependency>

1.3、打印时机

1、 系统完成初始化,比如加载必要的配置信息完成时

2、 接口的入口和出口,入口需要打印参数信息

3、 业务流程中不应该出现的地方,暗示数据或逻辑可能有错误的地方

4、 数据库存储过程中出现异常,比如唯一索引冲突,列格式不正确,事务需要回滚等;

5、 需要保留痕迹的地方,比如用户行为留痕,为合规或审计留痕等

6、 重要的业务流程状态变化,以及对应的分支处

1.4、日志级别

我们使用常用的日志级别:error、info、warn、debug,其他级别不建议使用。

1、 error级别应该在业务逻辑出现异常、数据持久化出现异常、数据不匹配等场景下使用打印该日志意味着系统出现潜在问题,需要报警,并且需要相关人员马上介入解决。error日志需要包含问题出现时必要的信息,比如用户信息,调用栈信息等。error级别的日志文件可以独立,以便报警处理

2、 info级别用于用户行为留痕、调用链跟踪以及问题定位方面,一般在核心或者重要的方法入口需要打印,还有一些重要的代码分支也需要打印

3、 warn级别在产生不符合预期的结果,但业务并未受损的情况下可以使用,warn级别的日志可不产生短信报警,可与公司及时消息打通报警或者邮件报警,解决的时效性要求低,但需要关注。

4、 debug是在测试和本地环境使用的。为了方便定位问题,我们可以在非线上环境使用debug这个日志级别,这样可以减少线上不必要的日志。

1.5、注意事项

1、 禁止直接使用日志系统的API(比如log4j日志系统),应该使用门面模式提供的接口可以安装lombok插件,类上使用@Slf4j注解,然后代码中直接使用log.info(“message:{}”, msg);

2、 异常日志的打印不能丢失调用栈信息,可使用:log.error("Exception message:{}",msg, ex);

3、 日志打印使用debug时应该注意,生产环境是不打印出来的,考虑效率应该加入判断:

  1. if(log.isDebugEnabled()){

  2. log.debug("message: {}", object);

  3. }

4、 测试环境和线上环境禁止使用禁用 System.out.println 和 System.err.println;

5、 日志打印的上下文中如果有用户ID、业务ID或者流水号的,一定要打印出来,方便追踪。

2、sfl4j和log4j和lobback有什么区别

我在多点的时候用的是logback,并且将日志输出到额graylog中,小米的时候使用的是log4j2

slf4j
是打日志的。可以使用各种日志系统存储。Log4j
logback
就是那个日志存储系统(Log4j它自带打日志,因为自己本身就是一个日志系统。所以不能够切换日志系统)。但是slf4j 是可以随时切换到任何日志系统,所以一般我们打日志都用SLF4J进行打日志吧!!!

SLF4J
:即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统(Log4j logback)。 在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统,SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

log4j
:日志系统

logback
logback
log4j
非常相似, Logback
的内核重写了,在一些关键执行路径上性能提升10倍以上。而且 logback
不仅性能提升了,初始化内存加载也更小。但是说白了也是个日志系统

3、获取日志错误的行号,方法,报错信息

  1. StackTraceElement s= e.getStackTrace()[0];

记录报错的文件:s.getFileName()

记录报错的方法:s.getMethodName()

记录报错的行号:s.getLineNumber()

记录报错的信息(不全面)“ e.getMessage()

互利报错的类名字:e.getClassName()

打印详细的堆栈信息:logger.error("错误堆栈",e);

3.1、 StackTraceElements=e.getStackTrace()[0];

  1. public static void main(String[] args) {


  2. try {

  3. int i =1/0 ;

  4. }catch (Exception e){

  5. log(e,ExceptionLogUtils.class );

  6. }



  7. }



  8. public static void log(Throwable e,Class c){

  9. Logger logger = LoggerFactory.getLogger(c);


  10. StackTraceElement s= e.getStackTrace()[0];//数组长度为 1

  11. logger.error("\n\n-----------------"+

  12. "\n报错文件名:"+s.getFileName()+

  13. "\n报错的类:"+s.getClassName()+

  14. "\n报错方法::"+s.getMethodName()+

  15. "\n报错的行:"+ s.getLineNumber()+

  16. "\n报错的message:"+ e.getMessage()+

  17. "\n错误堆栈:\n"+getStackTrace(e)+

  18. "\n------------------\n\n");

  19. }




  20. //获取堆栈信息

  21. public static String getStackTrace(Throwable throwable){

  22. StringWriter sw = new StringWriter();

  23. PrintWriter pw = new PrintWriter(sw);

  24. try

  25. {

  26. throwable.printStackTrace(pw);

  27. return sw.toString();

  28. } finally

  29. {

  30. pw.close();

  31. }

  32. }






  33. 11:40:53.732 [main] ERROR com.duodian.youhui.admin.utils.ExceptionLogUtils -


  34. -----------------

  35. 报错文件名:ExceptionLogUtils.java

  36. 报错的类:com.duodian.youhui.admin.utils.ExceptionLogUtils

  37. 报错方法::main

  38. 报错的行:68

  39. 报错的message:/ by zero

  40. 错误堆栈:

  41. java.lang.ArithmeticException: / by zero

  42. at com.duodian.youhui.admin.utils.ExceptionLogUtils.main(ExceptionLogUtils.java:68)


  43. ------------------

3.2、 Thread.currentThread().getStackTrace()

  1. public static void main(String[] args) {

  2. logInfo("HealerJean",ExceptionLogUtils.class);

  3. }



  4. public static void logInfo(String msg,Class c){

  5. Logger logger = LoggerFactory.getLogger(c);

  6. String location="";

  7. StackTraceElement[] stacks = Thread.currentThread().getStackTrace();

  8. System.out.println(stacks.length); //长度为3

  9. for(StackTraceElement stackTraceElement:stacks){

  10. logger.info("\n\n**************"+

  11. "\n打印文件名:"+stackTraceElement.getFileName() +

  12. "\n打印类名:"+ stackTraceElement.getClassName() +

  13. "\n方法名:" + stackTraceElement.getMethodName() +

  14. "\n行号:" + stackTraceElement.getLineNumber() +

  15. "\n打印内容:"+msg+

  16. "\n**************\n\n");

  17. System.out.println(location);

  18. }


  19. }






  20. 11:44:47.685 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -


  21. **************

  22. 打印文件名:Thread.java

  23. 打印类名:java.lang.Thread

  24. 方法名:getStackTrace

  25. 行号:1559

  26. 打印内容:HealerJean

  27. **************




  28. 11:44:47.689 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -


  29. **************

  30. 打印文件名:ExceptionLogUtils.java

  31. 打印类名:com.duodian.youhui.admin.utils.ExceptionLogUtils

  32. 方法名:logInfo

  33. 行号:31

  34. 打印内容:HealerJean

  35. **************




  36. 11:44:47.689 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -


  37. **************

  38. 打印文件名:ExceptionLogUtils.java

  39. 打印类名:com.duodian.youhui.admin.utils.ExceptionLogUtils

  40. 方法名:main

  41. 行号:49

  42. 打印内容:HealerJean

  43. **************

4、Throwable、Exception、Error

Throwable是java.lang包中一个专门用来处理异常的类。它有两个子类,即Error 和Exception,它们分别用来处理两组异常。

4.1、Error

*用来处理程序运行环境方面的异常,Error无法预期的错误因此,这是不可捕捉的,无法采取任何恢复的操作,一般只能显示错误的信息 *

比如,虚拟机错误、装载错误和连接错误,这类异常主要是和硬件有关的,而不是由程序本身抛出的。

比如 OutOfMemoryError,试多少次很大概率出错的。

4.2、Exception

java提供了两类主要的异常:运行时异常runtime exception和一般异常checked exception。

是在逻辑上又科分成检查异常和非检查异常

4.2.1、正常分类

4.2.1.1、运行时异常

Java
程序运行时常常遇到的各种异常的处理,其中包括隐式异常。比如,程序中除数为0引起的错误、数组下标越界错误等,这类异常也称为运行时异常,,因为它们虽然是由程序本身引起的异常,但不是程序主动抛出的,而是在程序运行中产生的。

运行时异常我们可以不处理。这样的异常由虚拟机接管。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终

4.2.1.2、一般异常

这些异常也称为显式异常。它们都是在程序中用语句抛出、并且也是用语句进行捕获的,比如,文件没找到引起的异常、类没找到引起的异常等。

JAVA要求程序员对其进行catch。所以,面对这种异常不管我们是否愿意,只能 catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

4.2.1、逻辑分类:

逻辑分类:checked检查异常和unchecked非检查异常。

checkedException
就是在写代码的时候,IDE(比如Eclipse)会要求你写try catch的那种Exception,比如IOException。这种Exception是Java的设计者要求你的程序去处理的。这种异常一般不会影响程序的主体,容易手动诊断修复,所以Java要求你在catch下面写出处理的代码,以保证程序遇到此类exception之后还可以正常运行

unchecked
这一类就是你在代码处理了checked exception之后,你在运行时候依然会遇到的exception,所以又叫做RunTimeException,比如NullPointerException, IndexOutOfBoundsException。此类exception相较于前面那种更容易影响程序运行,从设计者角度不提倡从程序中catch出来并处理,当然你也可以这么做。

  1. /**

  2. * 将CheckedException转换为UncheckedException

  3. */

  4. public static RuntimeException toUncheckedException(Exception e) {

  5. if (e instanceof RuntimeException) {

  6. return (RuntimeException) e;

  7. } else {

  8. return new RuntimeException(e);

  9. }

  10. }

5、Logback

5.1、logback.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <configuration>

  3. <!--学习 https://blog.csdn.net/ZYC88888/article/details/85060315-->


  4. <!--

  5. 格式化输出:%d表示日期,

  6. %thread表示线程名,

  7. %-5level:级别从左显示5个字符宽度,

  8. %logger{50} 表示 Logger 名字最长36个字符,

  9. %msg:日志消息,

  10. %M : 日志输出所在方法名

  11. %L : 日志输出所在行数

  12. %n是换行符 -->

  13. -->

  14. <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg -%logger{50}.%M[%L]%n "/>




  15. <property name="LOG_PATH" value="/Users/healerjean/Desktop/logs"/>

  16. <property name="FILE_PATH_INFO" value="${LOG_PATH}/hlj-logback.log"/>

  17. <property name="FILE_PATH_ERROR" value="${LOG_PATH}/hlj-logback-error.log"/>


  18. <!--控制台-->

  19. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

  20. <filter class="com.hlj.proj.controller.config.LogbackJsonFilter"/>

  21. <encoder charset="UTF-8" >

  22. <pattern>${LOG_PATTERN}</pattern>

  23. </encoder>

  24. </appender>



  25. <appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">

  26. <!--日志文件输出的文件名 -->

  27. <File>${FILE_PATH_INFO}</File>

  28. <!--滚动日志 基于时间和文件大小-->

  29. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

  30. <!-- 滚动日志文件保存格式 i是超出文件大小MaxFileSize 讲历史日志后缀名从0开始起步,

  31. 如果超过了最大的totalSizeCap,就会全部删除,重新开始-->

  32. <FileNamePattern>${FILE_PATH_INFO}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>

  33. <MaxFileSize>1MB</MaxFileSize>

  34. <totalSizeCap>5GB</totalSizeCap>

  35. <!--日志最大的历史 10天 -->

  36. <MaxHistory>10</MaxHistory>

  37. </rollingPolicy>

  38. <!-- 按临界值过滤日志:低于INFO以下级别被抛弃 -->

  39. <filter class="ch.qos.logback.classic.filter.ThresholdFilter">

  40. <level>INFO</level>

  41. </filter>

  42. <encoder>

  43. <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->

  44. <pattern>${LOG_PATTERN}</pattern>

  45. </encoder>

  46. </appender>



  47. <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">

  48. <File>${FILE_PATH_ERROR}</File>

  49. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

  50. <FileNamePattern>${FILE_PATH_ERROR}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>

  51. <MaxFileSize>60MB</MaxFileSize>

  52. <totalSizeCap>5GB</totalSizeCap>

  53. <MaxHistory>10</MaxHistory>

  54. </rollingPolicy>

  55. <filter class="ch.qos.logback.classic.filter.ThresholdFilter">

  56. <level>ERROR</level>

  57. </filter>

  58. <encoder>

  59. <pattern>${LOG_PATTERN}</pattern>

  60. </encoder>

  61. </appender>




  62. <!--以配置文件application.properties 中为主,如果配置文件中不存在以它为主-->

  63. <root level="info">

  64. <appender-ref ref="STDOUT" />

  65. <appender-ref ref="FILE-ERROR"/>

  66. <appender-ref ref="FILE-INFO"/>

  67. </root>

  68. </configuration>

5.2、Logback日志到数据库

5.2.1、创建数据库表

  1. DROP TABLE IF EXISTS logging_event_property;

  2. DROP TABLE IF EXISTS logging_event_exception;

  3. DROP TABLE IF EXISTS logging_event;



  4. CREATE TABLE logging_event

  5. (

  6. timestmp BIGINT NOT NULL,

  7. formatted_message TEXT NOT NULL,

  8. logger_name VARCHAR(254) NOT NULL,

  9. level_string VARCHAR(254) NOT NULL,

  10. thread_name VARCHAR(254),

  11. reference_flag SMALLINT,

  12. arg0 VARCHAR(254),

  13. arg1 VARCHAR(254),

  14. arg2 VARCHAR(254),

  15. arg3 VARCHAR(254),

  16. caller_filename VARCHAR(254) NOT NULL,

  17. caller_class VARCHAR(254) NOT NULL,

  18. caller_method VARCHAR(254) NOT NULL,

  19. caller_line CHAR(4) NOT NULL,

  20. event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY

  21. )



  22. CREATE TABLE logging_event_property

  23. (

  24. event_id BIGINT NOT NULL,

  25. mapped_key VARCHAR(150) NOT NULL,

  26. mapped_value TEXT,

  27. PRIMARY KEY(event_id, mapped_key),

  28. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)

  29. );


  30. CREATE TABLE logging_event_exception

  31. (

  32. event_id BIGINT NOT NULL,

  33. i SMALLINT NOT NULL,

  34. trace_line VARCHAR(254) NOT NULL,

  35. PRIMARY KEY(event_id, i),

  36. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)

  37. );

5.2.3、日志配置

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <configuration>

  3. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

  4. <encoder>

  5. <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>

  6. </encoder>

  7. </appender>




  8. <!--日志异步到数据库 -->

  9. <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">

  10. <!--日志异步到数据库-->

  11. <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">

  12. <driverClass>com.mysql.jdbc.Driver</driverClass>

  13. <url>jdbc:mysql://localhost:3306/healerjean?useUnicode=true&amp;allowMultiQueries=true&amp;characterEncoding=utf8&amp;zeroDateTimeBehavior=convertToNull&amp;useSSL=false</url>

  14. <user>healerjean</user>

  15. <password>healerjean</password>

  16. </connectionSource>

  17. </appender>




  18. <root level="info">

  19. <appender-ref ref="STDOUT" />

  20. <appender-ref ref="DB"/>

  21. </root>

  22. </configuration>

5.2.3、查看日志

  1. SELECT * from logging_event;

5.3、LogBack打印Json数据

  1. <!--控制台-->

  2. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

  3. <filter class="com.hlj.proj.controller.config.LogbackJsonFilter"/>

  4. <encoder charset="UTF-8" >

  5. <pattern>${LOG_PATTERN}</pattern>

  6. </encoder>

  7. </appender>

  1. package com.hlj.proj.controller.config;


  2. import ch.qos.logback.classic.spi.ILoggingEvent;

  3. import ch.qos.logback.core.filter.Filter;

  4. import ch.qos.logback.core.spi.FilterReply;

  5. import com.hlj.proj.controller.utils.JsonUtils;




  6. public class LogbackJsonFilter extends Filter<ILoggingEvent> {

  7. @Override

  8. public FilterReply decide(ILoggingEvent event) {

  9. if (event.getLoggerName().startsWith("com.hlj")) {

  10. Object[] params = event.getArgumentArray();

  11. for (int index = 0; index < params.length; index++) {

  12. Object param = params[index];

  13. // class.isPrimitive() 8种基本类型的时候为 true,其他为false

  14. if (!param.getClass().isPrimitive()) {

  15. params[index] = JsonUtils.toJsonString(param);

  16. }

  17. }

  18. }

  19. return FilterReply.ACCEPT;

  20. }

  21. }

6、Log4j

6.1、 log4j.properties

  1. ## 必填内容,info/all/.., stdout 为必填,后面的根据log4j.appender.内容进行填写,如果下面有内容则,这里必须加上,

  2. # level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。

  3. log4j.rootLogger=info, stdout, log, errorlog,proj




  4. #%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921

  5. #%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,

  6. #%t: 输出产生该日志事件的线程名

  7. #%C: 输出日志信息所属的类目,通常就是所在类的全名

  8. #%M: 输出代码中指定的消息,产生的日志具体信息

  9. #%F: 输出日志消息产生时所在的文件名称

  10. #%L: 输出代码中的行号

  11. #%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)

  12. #%r: 输出自应用启动到输出该log信息耗费的毫秒数

  13. #%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。

  14. #%%: 输出一个”%”字符

  15. #%n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行

  16. #%hostName : 本地机器名

  17. #%hostAddress : 本地ip地址-->

  18. #可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:

  19. #1) c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。

  20. #2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-”号指定左对齐。

  21. #3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。

  22. #4) .30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉



  23. # RollingFileAppender按log文件最大长度限度生成新文件

  24. # DailyRollingFileAppender按日期生成新文件,不能根据大小清除历史日志,但是我们可以自定义来实现


  25. # %d{yyyy-MM-dd HH:mm:ss,SSS}日期 %p級別 %t当前线程名称 %m日志信息 [%C.%M]类名加方法 %L行数 %n换行

  26. # 举例 # 2019-07-13 09:05:14,674 [INFO]-[http-nio-8888-exec-1] info日志================== com.hlj.proj.controler.Log4jController.log4j][25]




  27. # 控制台输出

  28. log4j.appender.stdout=org.apache.log4j.ConsoleAppender

  29. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

  30. log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n





  31. ## 根据日期生成配置文件当前log.log 如果时间超过了设置的格式的时间DatePattern 则会在后面加上 log.log.2019-07-12.log

  32. # 解释:也就是说log文件会暂存每天的日志,到第二天时会再加上yyyy-MM,产生当天的完整日志文件

  33. ### Log info

  34. log4j.appender.log = org.apache.log4j.DailyRollingFileAppender

  35. log4j.appender.log.File = /Users/healerjean/Desktop/logs/hlj-log4j.log

  36. log4j.appender.log.Append = true

  37. log4j.appender.log.Threshold = INFO

  38. #超过日期则讲历史日志加上后缀日期用于区分

  39. log4j.appender.log.DatePattern='.'yyyy-MM-dd'.log'

  40. log4j.appender.log.layout = org.apache.log4j.PatternLayout

  41. log4j.appender.log.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n






  42. ## 5 按照文件大小进行日志切分 文件历史日志依次 error.log.1 error.log.2

  43. log4j.appender.errorlog=org.apache.log4j.RollingFileAppender

  44. log4j.appender.errorlog.File=/Users/healerjean/Desktop/logs/error.log

  45. log4j.appender.errorlog.Append=true

  46. log4j.appender.errorlog.Threshold=error

  47. #设置日志文件的大小

  48. log4j.appender.errorlog.MaxFileSize=2000KB

  49. #保存200个备份文件

  50. log4j.appender.errorlog.MaxBackupIndex=200

  51. log4j.appender.errorlog.layout=org.apache.log4j.PatternLayout

  52. log4j.appender.errorlog.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L %n




  53. log4j.appender.proj=com.hlj.proj.utils.RoolingAndDateFileAppender

  54. log4j.appender.proj.file=/Users/healerjean/Desktop/logs/logRecoed.log

  55. log4j.appender.proj.Append=true

  56. log4j.appender.proj.DatePattern='.'yyyy-MM-dd'.log'

  57. log4j.appender.proj.Threshold=error

  58. #设置日志文件的大小

  59. log4j.appender.proj.MaxFileSize=5KB

  60. #最大保留多少个文件,超过之后会进行重新命名,所以尽量不要超过

  61. log4j.appender.proj.maxIndex=10

  62. #只保留多长时间的

  63. log4j.appender.proj.expirDays=1

  64. log4j.appender.proj.layout=org.apache.log4j.PatternLayout

  65. log4j.appender.proj.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n

7、Log4j2

log4j一直存在两个问题,一是打日志影响到系统性能效率,二是有多线程的时候,日志会比较乱

log4j2是log4j 1.x 的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:

  1. 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。


  2. 性能提升, log4j2相较于log4j 1logback都具有很明显的性能提升,后面会有官方测试的数据。


  3. 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。


  4. 无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc

7.1、 log4j2.xml

  1. <?xml version="1.0" encoding="UTF-8"?>



  2. <!--status Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,

  3. 你会看到log4j2内部各种详细输出。可以设置成OFF(关闭)或Error(只输出错误信息)-->

  4. <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->

  5. <configuration status="error" monitorInterval="30">

  6. <!-- %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间-->

  7. <!-- %p : 日志输出格式-->

  8. <!-- %thread表示线程名,-->

  9. <!-- %c : logger的名称-->

  10. <!-- %m : 日志内容,即 logger.info("message")-->

  11. <!-- %n : 换行符-->

  12. <!-- %C : Java类名-->

  13. <!-- %L : 日志输出所在行数-->

  14. <!-- %M : 日志输出所在方法名-->

  15. <!-- hostName : 本地机器名-->

  16. <!-- hostAddress : 本地ip地-->


  17. <!-- 日志文件目录和压缩文件目录配置 -->

  18. <Properties>

  19. <Property name="level">info</Property>

  20. <Property name="LOG_PATTERN">

  21. %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n

  22. </Property>


  23. <Property name="logDri">/Users/healerjean/Desktop/logs</Property>

  24. <Property name="logFileName">hlj-client</Property>


  25. <Property name="infoLogDri">${logDri}/info</Property>

  26. <Property name="infoLogGz">${infoLogDri}/gz</Property>

  27. <Property name="infoLogFileName">${logFileName}.log</Property>


  28. <Property name="errorLogDri">${logDri}/error</Property>

  29. <Property name="errorLogGz">${errorLogDri}/gz</Property>

  30. <Property name="errorLogFileName">${logFileName}.error</Property>


  31. </Properties>


  32. <appenders>

  33. <console name="Console" target="SYSTEM_OUT">

  34. <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->

  35. <ThresholdFilter level="${level}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>

  36. <!--输出日志的格式-->

  37. <PatternLayout pattern="${LOG_PATTERN}"/>

  38. </console>



  39. <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->

  40. <RollingRandomAccessFile name="infoFile" fileName="${infoLogDri}/${infoLogFileName}"

  41. filePattern="${infoLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${infoLogFileName}.gz">

  42. <PatternLayout pattern="${LOG_PATTERN}"/>

  43. <Policies>

  44. <!-- 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小 -->

  45. <SizeBasedTriggeringPolicy size="500 MB"/>

  46. <!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour -->

  47. <TimeBasedTriggeringPolicy interval="6" modulate="true"/>

  48. </Policies>

  49. <Filters>

  50. <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)

  51. onMatch属性设置为DENY,过滤掉高等级的日志;onMismatch设置为NEUTRAL,把低等级的日志放行,

  52. -->

  53. <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/>

  54. <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>

  55. </Filters>

  56. <!-- 指定每天(文件夹是以天的,看上面的)的最大压缩包个数,默认7个,超过了会覆盖之前的(用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)) -->

  57. <DefaultRolloverStrategy max="2000"/>

  58. </RollingRandomAccessFile>



  59. <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->

  60. <RollingRandomAccessFile name="errorFile" fileName="${errorLogDri}/${errorLogFileName}"

  61. filePattern="${errorLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${errorLogFileName}.gz">

  62. <PatternLayout pattern="${LOG_PATTERN}"/>

  63. <Policies>

  64. <!-- 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小 -->

  65. <SizeBasedTriggeringPolicy size="500 MB"/>

  66. <!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour -->

  67. <TimeBasedTriggeringPolicy interval="6" modulate="true"/>

  68. </Policies>

  69. <Filters>

  70. <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)

  71. onMatch属性设置为DENY,过滤掉高等级的日志;onMismatch设置为NEUTRAL,把低等级的日志放行,

  72. -->

  73. <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>

  74. </Filters>

  75. <!-- 指定每天(文件夹是以天的,看上面的)的最大压缩包个数,默认7个,超过了会覆盖之前的(用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)) -->

  76. <DefaultRolloverStrategy max="2000"/>

  77. </RollingRandomAccessFile>



  78. </appenders>



  79. <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->

  80. <loggers>

  81. <!-- AsyncRoot - 异步记录日志 - 需要LMAX Disruptor的支持 -->

  82. <!-- additivity如果设置为true将会输出两次日志,意思和log4j里面意思是否追加 -->

  83. <AsyncRoot level="${level}" additivity="false" includeLocation="true">

  84. <AppenderRef ref="Console"/>

  85. <AppenderRef ref="infoFile"/>

  86. <AppenderRef ref="errorFile"/>

  87. </AsyncRoot>

  88. </loggers>



  89. </configuration>

7.1.1、日志打印位置

main方法和服务器日志都在一起

8、日志唯一标识追踪

1、Controll入参和出参打印

2、唯一标识

8.1、日志格式

8.1.1、log4j

  1. %d{yyyy-MM-dd HH:mm:ss} %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n

8.1.1、logback

  1. %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg -%logger{50}.%M[%L]%n

8.2、过滤器 Log4j2ReqUidFilter

  1. package com.healerjean.proj.config.filter;



  2. import org.slf4j.MDC;


  3. import javax.servlet.*;

  4. import java.io.IOException;

  5. import java.util.UUID;


  6. /**

  7. * @author HealerJean

  8. * @ClassName Log4j2Filter

  9. * @date 2020/6/15 20:12.

  10. * @Description

  11. */

  12. public class Log4j2ReqUidFilter implements Filter {


  13. private static final String REQ_UID = "REQ_UID";

  14. private FilterConfig filterConfig;



  15. @Override

  16. public void init(FilterConfig filterConfig) throws ServletException {

  17. this.filterConfig = filterConfig;

  18. }


  19. @Override

  20. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  21. MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));

  22. filterChain.doFilter(servletRequest, servletResponse);

  23. MDC.remove(REQ_UID);

  24. }


  25. @Override

  26. public void destroy() {

  27. this.filterConfig = null;

  28. }




  29. }

  1. @Bean

  2. public FilterRegistrationBean log4j2Fiter() {

  3. FilterRegistrationBean fitler = new FilterRegistrationBean();

  4. fitler.setFilter(new Log4j2Filter());

  5. fitler.addUrlPatterns("/*");

  6. fitler.setName("log4j2Fiter");

  7. fitler.setDispatcherTypes(DispatcherType.REQUEST);

  8. return fitler;

  9. }

9、日志Controller出参入参打印

9.1、pom依赖

  1. <!-- aop 切面 -->

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-aop</artifactId>

  5. </dependency>

9.2、自定义注解标识方法名字

  1. @Documented

  2. @Target(ElementType.METHOD)

  3. @Retention(RetentionPolicy.RUNTIME)

  4. public @interface InterfaceName {


  5. String value() default "";

  6. }

  1. @InterfaceName("demo控制器--------demo实体")

  2. @ApiOperation(value = "demo实体",

  3. notes = "demo实体",

  4. consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,

  5. produces = MediaType.APPLICATION_JSON_UTF8_VALUE,

  6. response = DemoDTO.class)

  7. @GetMapping(value = "demo/get", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

  8. @ResponseBody

  9. public ResponseBean get(DemoDTO demoDTO) {

  10. String validate = ValidateUtils.validate(demoDTO, ValidateGroup.HealerJean.class);

  11. if (!validate.equals(CommonConstants.COMMON_SUCCESS)) {

  12. throw new BusinessException(ResponseEnum.参数错误, validate);

  13. }

  14. return ResponseBean.buildSuccess(demoEntityService.getMmethod(demoDTO));

  15. }

9.3、AOP拦截

  1. package com.healerjean.proj.config.aspect;


  2. import com.healerjean.proj.annotation.InterfaceName;

  3. import lombok.extern.slf4j.Slf4j;

  4. import org.aspectj.lang.ProceedingJoinPoint;

  5. import org.aspectj.lang.Signature;

  6. import org.aspectj.lang.annotation.Around;

  7. import org.aspectj.lang.annotation.Aspect;

  8. import org.aspectj.lang.reflect.MethodSignature;

  9. import org.springframework.core.annotation.Order;

  10. import org.springframework.stereotype.Component;


  11. import java.lang.reflect.Method;


  12. @Aspect

  13. @Component

  14. @Slf4j

  15. @Order(1)

  16. public class ControllerLogAspect {




  17. @Around("execution(* com.healerjean.proj.controller.*Controller.*(..))")

  18. public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

  19. Signature signature = proceedingJoinPoint.getSignature();

  20. String className = signature.getDeclaringTypeName();

  21. String methodName = signature.getName();

  22. Object[] args = proceedingJoinPoint.getArgs();


  23. String value = "";

  24. Method method = ((MethodSignature) signature).getMethod();

  25. if (method.isAnnotationPresent(InterfaceName.class)) {

  26. value = "请求接口:【" + method.getAnnotation(InterfaceName.class).value() + "】,";

  27. }


  28. long start = System.currentTimeMillis();

  29. try {

  30. log.info("请求开始:{}类名:【{}】,方法名:【{}】, 参数:【{}】", value, className, methodName, args);

  31. Object result = proceedingJoinPoint.proceed();

  32. long timeCost = System.currentTimeMillis() - start;

  33. log.info("请求结束:{}类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", value, className, methodName, args, result, timeCost);

  34. return result;

  35. } catch (Exception e) {

  36. long timeCost = System.currentTimeMillis() - start;

  37. log.info("请求出错:{}类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", value, className, methodName, args, timeCost);

  38. throw e;

  39. }

  40. }

  41. }

10、dubbo日志追踪

10.1、服务提供者

10.1.1、aop切面pom依赖

  1. <!-- aop 切面 -->

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-aop</artifactId>

  5. </dependency>

10.1.2、 ServiceLogAspect
接口出入参日志打印

  1. package com.healerjean.proj.config.aspect;


  2. import lombok.extern.slf4j.Slf4j;

  3. import org.aspectj.lang.ProceedingJoinPoint;

  4. import org.aspectj.lang.Signature;

  5. import org.aspectj.lang.annotation.Around;

  6. import org.aspectj.lang.annotation.Aspect;

  7. import org.springframework.core.annotation.Order;

  8. import org.springframework.stereotype.Component;



  9. @Aspect

  10. @Component

  11. @Slf4j

  12. @Order(1)

  13. public class ServiceLogAspect {


  14. @Around("execution(* com.healerjean.proj.service.*Service.*(..))")

  15. public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

  16. Signature signature = proceedingJoinPoint.getSignature();

  17. String className = signature.getDeclaringTypeName();

  18. String methodName = signature.getName();

  19. Object[] args = proceedingJoinPoint.getArgs();

  20. long start = System.currentTimeMillis();

  21. try {

  22. log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);

  23. Object result = proceedingJoinPoint.proceed();

  24. long timeCost = System.currentTimeMillis() - start;

  25. log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);

  26. return result;

  27. } catch (Exception e) {

  28. long timeCost = System.currentTimeMillis() - start;

  29. log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);

  30. throw e;

  31. }

  32. }

  33. }

10.1.3、 ProviderRpcTraceFilter
dubbo日志追踪过滤器

  1. package com.healerjean.proj.config.dubbo;


  2. import com.alibaba.dubbo.common.Constants;

  3. import com.alibaba.dubbo.common.extension.Activate;

  4. import com.alibaba.dubbo.rpc.*;

  5. import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;

  6. import org.apache.commons.lang3.StringUtils;

  7. import org.slf4j.MDC;


  8. import java.util.UUID;


  9. @Activate(group = Constants.PROVIDER, order = 1)

  10. public class ProviderRpcTraceFilter extends FutureFilter {



  11. private static final String DUBBO_REQ_UID = "REQ_UID";


  12. @Override

  13. public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

  14. String reqUid = RpcContext.getContext().getAttachment(DUBBO_REQ_UID);

  15. if (StringUtils.isBlank(reqUid)) {

  16. //传递丢失

  17. reqUid = "CUSTOM:" + UUID.randomUUID().toString().replace("-", "");

  18. }

  19. MDC.put(DUBBO_REQ_UID, reqUid);

  20. RpcContext.getContext().setAttachment(DUBBO_REQ_UID, reqUid);

  21. try {

  22. return invoker.invoke(invocation);

  23. } finally {

  24. MDC.remove(DUBBO_REQ_UID);

  25. }

  26. }

  27. }

10.1.4、配置dubbo过滤器

com.alibaba.dubbo.rpc.Filter

  1. ProviderRpcTraceFilter=com.healerjean.proj.config.dubbo.ProviderRpcTraceFilter

10.2、服务消费者

10.2.1、aop切面pom依赖

  1. <!-- aop 切面 -->

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-aop</artifactId>

  5. </dependency>

10.2.2、 ControllerLogAspect
controller出入参打印

  1. package com.healerjean.proj.config.aspect;


  2. import lombok.extern.slf4j.Slf4j;

  3. import org.aspectj.lang.ProceedingJoinPoint;

  4. import org.aspectj.lang.Signature;

  5. import org.aspectj.lang.annotation.Around;

  6. import org.aspectj.lang.annotation.Aspect;

  7. import org.springframework.core.annotation.Order;

  8. import org.springframework.stereotype.Component;



  9. @Aspect

  10. @Component

  11. @Slf4j

  12. @Order(1)

  13. public class ControllerLogAspect {


  14. @Around("execution(* com.healerjean.proj.controller.*Controller.*(..))")

  15. public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

  16. Signature signature = proceedingJoinPoint.getSignature();

  17. String className = signature.getDeclaringTypeName();

  18. String methodName = signature.getName();

  19. Object[] args = proceedingJoinPoint.getArgs();

  20. long start = System.currentTimeMillis();

  21. try {

  22. log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);

  23. Object result = proceedingJoinPoint.proceed();

  24. long timeCost = System.currentTimeMillis() - start;

  25. log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);

  26. return result;

  27. } catch (Exception e) {

  28. long timeCost = System.currentTimeMillis() - start;

  29. log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);

  30. throw e;

  31. }

  32. }

  33. }

10.2.3、日志追踪过滤器

  1. package com.healerjean.proj.config.filter;



  2. import org.slf4j.MDC;


  3. import javax.servlet.*;

  4. import java.io.IOException;

  5. import java.util.UUID;


  6. /**

  7. * @author HealerJean

  8. * @ClassName Log4j2Filter

  9. * @date 2020/6/15 20:12.

  10. * @Description

  11. */

  12. public class Log4j2ReqUidFilter implements Filter {


  13. private static final String REQ_UID = "REQ_UID";

  14. private FilterConfig filterConfig;



  15. @Override

  16. public void init(FilterConfig filterConfig) throws ServletException {

  17. this.filterConfig = filterConfig;

  18. }


  19. @Override

  20. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  21. MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));

  22. filterChain.doFilter(servletRequest, servletResponse);

  23. MDC.remove(REQ_UID);

  24. }


  25. @Override

  26. public void destroy() {

  27. this.filterConfig = null;

  28. }




  29. }

  1. @Configuration

  2. public class InterceptorConfig extends WebMvcConfigurerAdapter {


  3. @Bean

  4. public FilterRegistrationBean log4j2ReqUidFilter() {

  5. FilterRegistrationBean fitler = new FilterRegistrationBean();

  6. fitler.setFilter(new Log4j2ReqUidFilter());

  7. fitler.addUrlPatterns("/*");

  8. fitler.setName("Log4j2ReqUidFilter");

  9. fitler.setDispatcherTypes(DispatcherType.REQUEST);

  10. return fitler;

  11. }

  12. }

10.2.4、 ConsumerRpcTraceFilter
dubbo日志追踪过滤器

  1. package com.healerjean.proj.config.dubbo;


  2. import com.alibaba.dubbo.common.Constants;

  3. import com.alibaba.dubbo.common.extension.Activate;

  4. import com.alibaba.dubbo.rpc.*;

  5. import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;

  6. import org.apache.commons.lang3.StringUtils;

  7. import org.slf4j.MDC;


  8. import java.util.UUID;


  9. @Activate(group = Constants.CONSUMER, order = 1)

  10. public class ConsumerRpcTraceFilter extends FutureFilter {



  11. private static final String DUBBO_REQ_UID = "REQ_UID";


  12. @Override

  13. public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

  14. RpcContext.getContext().setAttachment(DUBBO_REQ_UID, MDC.get(DUBBO_REQ_UID));

  15. return invoker.invoke(invocation);

  16. }

  17. }

10.2.5、配置dubbo过滤器

com.alibaba.dubbo.rpc.Filter

  1. ConsumerRpcTraceFilter=com.healerjean.proj.config.dubbo.ConsumerRpcTraceFilter


文章转载自荒凉 技术路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论