java异常日志,java 日志记录

  java异常日志,java 日志记录

  

java基础教程栏目介绍如何解决Java日志级别等问题

  如何解决写爬虫IP受阻的问题?立即使用。

  

1 日志常见错因

  

1.1 日志框架繁多

  不同的类库可能使用不同的日志框架,兼容性是个难题。

  

1.2 配置复杂且容易出错

  日志配置文件通常很复杂。很多同学习惯了直接从其他项目或者网志上复制一个配置文件,却没有仔细研究如何修改。常见错误发生在重复日志记录、同步日志记录的性能和异步日志记录的错误配置中。

  

1.3 日志记录本身就有些误区

  比如获取日志内容的成本,日志级别的乱用等等。不被考虑。

  

2 SLF4J

   Logback、Log4j、Log4j2、commons-logging、JDK自带的java.util.logging等。都是Java系统的日志框架,确实很多。不同的类库可能会选择使用不同的日志框架。因此,日志的统一管理变得非常困难。

  SLF4J(Java的简单日志门面)就是为了解决这个问题。

  提供了统一的日志facade API,即图中紫色部分实现了中性日志API桥接功能,蓝色部分将各种日志框架API(绿色部分)桥接到SLF4J API。这样,即使你的程序使用各种日志API来记录日志,最终也会桥接到SLF4J facade API。适配功能,红色部分,可以实现SLF4J API和实际日志框架的绑定(灰色部分)。

  SLF4J只是一个日志标准,还是它需要一个实际的日志框架?日志框架本身没有实现SLF4J API,所以需要进行预转换。Logback是按照SLF4J API标准实现的,所以不需要绑定模块进行转换。虽然Log4j-over-SLF4J可以用来桥接Log4j到SLF4J,SLF4J-log4j12也可以用来适配slf4j到Log4j,而且它们是画成一排的,但是它不能同时使用它们,否则会出现死循环。Jcl和jul是一样的。

  虽然图中有四个灰色的日志实现框架,但是Logback和Log4j是日常业务中最常用的,都是同一个人开发的。Logback可以算是Log4j的改进版,比较推荐,基本上是主流。

  Spring Boot的日志框架也是回溯的。那为什么不用手动引入Logback包就可以直接使用Logback呢?

  Spring-boot-starter模块依托spring-boot-starter-logging模块,spring-boot-starter-logging模块自动引入logback-classic(包括SLF4J和Logback log框架)以及SLF4J的部分适配器。log4j-to-SLF4J用于将Log4j2 API连接到SLF4J,而jul-to-SLF4J用于将java.util.logging API连接到slf4j。

  

3 日志重复记录

  重复日志不仅给日志查看和统计带来不必要的麻烦,还会增加磁盘和日志收集系统的负担。

  

logger配置继承关系导致日志重复记录

  定义记录四种日志的方法:调试、信息、警告和错误。

  回溯配置

  配置没什么问题。执行该方法后,存在重复的日志记录。

  分析

  这个控制台附加器同时安装到两个记录器上,即定义的记录器和根记录器。由于定义的日志程序是从root继承的,相同的日志会被日志程序记录并发送给root,所以应用程序包下的日志中存在重复记录。

  这种配置的初衷是什么?

  心里想实现自定义日志器配置,让应用中的日志可以临时打开调试级日志。实际上,不需要重复安装附加器,只需拆除安装在记录器下方的附加器即可:

  logger name= org . javaedge . time . common errors . logging level= debug /如果自定义记录器需要将日志输出到不同的Appender:

  例如

  应用程序日志输出到文件app.log其他框架日志输出到控制台可以将logger的additivity属性设置为false,不会继承root的Appender。

  

错误配置LevelFilter造成日志重复

  将日志记录到控制台时,根据不同的级别将日志记录到两个文件中。

  执行结果

  INFO.log文件包含与预期不同的信息、警告和错误日志。

  ERROR.log包含两个级别的日志,WARN和ERROR,导致日志的重复收集。

  事故责任

  有些公司使用自动化ELK方案收集日志,日志会同时输出到控制台和文件。开发人员在本地测试时并不关心文件中记录的日志。但是,在测试和生产环境中,由于开发人员没有访问服务器的权限,因此很难在原始日志文件中找到重复的问题。

  为什么日志会重复?

  

ThresholdFilter源码解析

  当日志级别配置级别返回NEUTRAL时,继续调用过滤器链中的下一个过滤器;否则返回DENY,直接拒绝登录。在这种情况下,我们将ThresholdFilter设置为WARN,这样我们就可以记录WARNERROR级别的日志。

  

LevelFilter

  用于比较日志级别,然后进行相应的处理。

  如果有匹配,调用onMatch中定义的处理方法:默认情况下会交给下一个过滤器(AbstractMatcherFilter基类中定义的默认值);否则调用onMismatch中定义的处理方法:默认情况下也会交给下一个过滤器。

  不像ThresholdFilterLevelFilter只靠配置等级真的不行。

  由于未配置onMatch和onMismatch属性,过滤器失败,导致记录高于INFO级别的日志。

  

修正

  配置LevelFilter的onMatch属性为ACCEPT,表示接收INFO级别的日志;将onMismatch属性配置为DENY,这意味着除了INFO级别之外,不记录任何记录:

  这样,_INFO.log文件将只有INFO级别的日志,不会有日志重复。

  

4 异步日志提高性能?

  在确切地了解了如何正确地将日志输出到文件之后,是时候考虑如何避免日志成为系统性能的瓶颈了。这样可以解决磁盘(如机械磁盘)IO性能差,日志量大时如何记录日志的问题。

  使用两个Appender定义以下日志配置:

  FILE是一个FileAppender,用来记录所有日志;

  CONSOLE是一个ConsoleAppender,用来记录标有时间的日志。

  如果将大量日志输出到一个文件中,日志文件将会非常大。如果混合了性能测试结果,将很难找到该日志。所以这里使用EvaluatorFilter根据标记过滤日志,过滤后的日志单独输出到控制台。在这种情况下,输出测试结果的日志标有时间。

  测试代码:记录指定数量的大型日志,每个日志包含1MB的模拟数据,最后记录一个用时间标记的方法执行的耗时日志。执行程序后,可以看到记录1000个日常日志需要5.1秒,调用10000个日志需要39秒。

  对于只记录文件日志的代码,这需要太长时间。

  

源码解析

   FileAppender从OutputStreamAppender继承。

  追加日志时,日志直接写入OutputStream,属于同步日志记录。

  这就是为什么要花很长时间写很多日志的原因。如何在不影响业务逻辑执行时间和吞吐量的情况下,写大量日志?

  

AsyncAppender

  AsyncAppender使用回退

  可以实现异步日志记录。AsyncAppender类似于decorator模式,在类中添加新的函数,而不改变其原有的基本函数。这可以将AsyncAppender附加到其他Appender,并使其异步。

  定义一个异步Appender ASYNCFILE,将之前同步文件日志记录的FileAppender包装起来,这样就可以异步的将日志记录到文件中。

  记录1000条日志和10000条日志需要时间,分别是537毫秒和1019毫秒。异步日志真的那么高性能吗?没有,因为它没有记录所有的日志。

  

AsyncAppender异步日志坑

  记录异步日志突发内存记录异步日志丢失记录异步日志阻塞。

案例

  模拟慢速记录场景:

  首先用一个从ConsoleAppender继承的自定义MySlowAppender作为登录控制台的输出设备,写入日志时休眠1秒。

  在配置文件中使用AsyncAppender,并将MySlowAppender包装为异步日志记录。

  试验码

  耗时短,但日志丢失:记录1000条日志,控制台只能搜索215条日志,日志的行号变成问号。

  原因AsyncAppender提供了一些配置参数,但是目前没有用。

  

源码解析

   includeCallerData

  默认情况下为False:对于方法行号和方法名等信息,不显示queueSize。

  控制阻塞队列的大小。使用的阻塞队列是ArrayBlockingQueue。默认容量为256:内存中最多存储256个日志,丢弃阈值。

  丢弃日志的阈值,以防止队列满时阻塞。如果队列的默认剩余容量小于队列长度的20%,跟踪、调试和信息级别的日志neverBlock将被丢弃。

  控制队列满时,加入的数据是否直接丢弃,不会阻塞等待,默认是错误的队列满时:报价不阻塞,而放会阻塞永不封锁为真实的时,使用公开招股类AsyncAppender扩展async appender baseiloggingevent {

  //是否收集调用方数据

  boolean includeCallerData=false;

  受保护的布尔值isDiscardable(ILoggingEvent事件){

  级别级别=事件。getlevel();

  //丢弃信息级日志

  返回level.toInt()=Level .INFO _ INT

  }

  受保护的空的预处理(ILoggingEvent事件对象){

  事件对象。preparefordeferredprocessing();

  if (includeCallerData)

  事件对象。getcallerdata();

  } }公共类AsyncAppenderBaseE扩展UnsynchronizedAppenderBaseE实现AppenderAttachableE {

  //阻塞队列:实现异步日志的核心

  阻塞队列

  //默认队列大小

  public static final int DEFAULT _ QUEUE _ SIZE=256;

  int QUEUE SIZE=DEFAULT _ QUEUE _ SIZE;

  static final int UNDEFINED=-1;

  int discardingThreshold=UNDEFINED;

  //当队列满时:加入数据时是否直接丢弃,不会阻塞等待

  布尔型neverBlock=false

  @覆盖

  public void start() {

  .

  blocking queue=new arrayblockingqueue(queueSize);

  if(discardingThreshold==UNDEFINED)

  //默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法

  discardingThreshold=队列大小/5;

  .

  }

  @覆盖

  受保护的void append(E eventObject) {

  if(isQueueBelowDiscardingThreshold()可丢弃(事件对象)){//判断是否可以丢数据

  返回;

  }

  预处理(事件对象);

  put(事件对象);

  }

  private boolean isQueueBelowDiscardingThreshold(){

  返回(阻塞队列。remainingcapacity()discardingThreshold);

  }

  私有void put(E eventObject) {

  if (neverBlock) { //根据永不封锁决定使用不阻塞的提供还是阻塞的放方法

  阻塞队列。offer(事件对象);

  }否则{

  不间断地放入(事件对象);

  }

  }

  //以阻塞方式添加数据到队列

  私有空的不间断地放入(E事件对象){

  布尔中断=假;

  尝试{

  while (true) {

  尝试{

  阻塞队列。put(事件对象);

  打破;

  } catch (InterruptedException e) {

  中断=真;

  }

  }

  }最后{

  如果(中断){

  Thread.currentThread().中断();

  }

  }

  }}默认队列大小256,达到80%后开始丢弃=信息级日志后,即可理解日志中为什么只有两百多条信息日志了。

  

queueSize 过大

   可能导致OOM

  

queueSize 较小

   默认值256就已经算很小了,且discardingThreshold设置为大于0(或为默认值),队列剩余容量少于discardingThreshold的配置就会丢弃=信息日志。这里的坑点有两个:

  因为discardingThreshold,所以设置queueSize时容易踩坑。

  比如本案例最大日志并发1000,即便置queueSize为1000,同样会导致日志丢失discardingThreshold参数容易有歧义,它不是百分比,而是日志条数。对于总容量10000队列,若希望队列剩余容量少于1000时丢弃,需配置为1000

neverBlock 默认false

   意味总可能会出现阻塞。

  若discardingThreshold = 0,那么队列满时再有日志写入就会阻塞若discardingThreshold != 0,也只丢弃信息级日志,出现大量错误日志时,还是会阻塞队列大小、丢弃阈值和永不封锁三参密不可分,务必按业务需求设置:

  如果绝对性能优先,neverBlock=true,永不阻塞;如果数据优先,永不丢失,设置discardingThreshold=0,即使INFO级别日志也不会丢失。但是最好把queueSize设置的大一点。毕竟默认的queueSize显然太小,太容易阻塞。如果两者兼顾,可以舍弃不重要的日志,将queueSize设置成大一点的,然后设置一个大于discardingThreshold的合理日志配置。两个最常见的误解是

  再看日志本身的误区。

  

使用日志占位符就无需判断日志级别?

  验证码:返回结果需要1秒。如果记录了调试日志,并且只设置了=INFO log,程序会不会也需要1秒?

  三种测试方法:

  通过拼接字符串记录slowString,通过占位符记录slowString。首先,判断在日志级别是否启用了DEBUG。

  前两个方法调用slowString,所以需要1s。第二种方法是使用占位符来记录slowString。这样虽然允许传递Object而不显式拼接字符串,只是延迟了(如果不记录日志,会保存)拼接日志参数对象.toString()个字符串的耗时。

  除非事先判断好日志级别,否则这种情况下会调用slowString。

  因此,使用{}占位符并不能通过延迟参数值获取来解决日志数据获取的性能问题。

  除了提前判断日志级别,还可以通过lambda表达式延迟参数内容获取。但是SLF4J的API还不支持lambda,所以你需要使用Log4j2 log API,用**@Log4j2**注释替换Lombok的@Slf4j注解,提供lambda表达式参数的方法:

  这样,当你调用debug并签署Supplier?时,参数会延迟到你真正需要日志的时候:

  因此,debug4不调用slowString方法。

  改成Log4j2 API就行了,真正的日志还是Logback,这就是SLF4J适配的优势。

  

总结

   SLF4J统一了Java日志框架。当使用SLF4J时,你应该弄清楚它的桥接API和绑定。如果程序启动时出现SLF4J错误提示,可能是配置问题。可以使用Maven的dependency:tree命令来整理依赖关系。异步日志通过用空间换取时间来解决性能问题。但毕竟空间有限。当空间已满时,考虑阻塞并等待或丢弃日志。如果您不希望丢弃重要的日志,请选择阻塞和等待;如果您希望程序不被日志阻止,您需要丢弃日志。日志框架提供的参数化日志记录方式并不能完全代替日志级别判断。如果你的日志量很大,而且获取日志参数花费很大,那么就要判断日志的级别,避免花时间获取日志参数而不进行日志记录。以上是了解Java日志级别,重复记录,丢失日志的详细情况。请多关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: