SEO外包平台,我们为您提供专业的企业网站SEO整站优化外包服务 SEO设置

SEO外包平台

专注于企业网站SEO整站优化外包服务

Logback日志框架-日志打印过程和Logger继承特性的源代码分析

作者:jcmp      发布时间:2021-05-19      浏览量:0
总结最后一个“Logback日志框架初始

总结

最后一个“Logback日志框架初始化全过程源代码解析”分析Logback的整个初始化过程和Logger的创建。本文将继续分析Logger的打印过程。上一篇文章没有介绍Logback的体系结构,因此我们将首先简要介绍Logback的基本组件。然后介绍了logback.xml配置文件中的主要元素以及它们之间的协作关系,重点介绍了Logger的继承特性,然后结合日志打印的整个过程,将这些概念逐一编入源代码进行验证,最后做了一个简单的总结。

Logback基本模块

主要分为三个模块:

当我们一般使用Logback时,我们指的是slf4j-api、Logback-core和Logback-古典。

Logback配置

Logback提供了多种配置:

配置文件实际上提供了两种类型:logback.grovy和logback.xml。

两者大致相同,但语法不同,

<配置><布局类=“ch.qos.logback.classic.Pattern.PatternLayout”>>${filename:-demo。<编码器><模式>${Pattern}/logger>

logback.xml我们主要查看6个XML元素:

附录>

布局

布局用于根据模式规则格式化日志内容,将日志事件转换为字符串类型,然后通过java.io.Writer输出。

编码器

编码器出现在0.9.19版本之后。她和布局本质上是一样的,通过模式规则格式化日志内容,只不过编码器将日志事件转换为字节数组,然后写入OutputStream。在

0.9.19之后,正式推荐使用编码器而不是布局。官方解释如下:

为什么中断更改?布局,如下一章中详细讨论的,只能将事件转换为字符串。此外,考虑到布局无法控制事件写入事件时,布局不能将事件聚合成批处理。相比之下,编码器不仅可以完全控制所写字节的格式,还可以控制何时(以及是否)写入这些字节。

意味着布局只能将日志事件转换为字符串,不能控制日志事件何时被写入,并且不能将多个事件收集到一个组中。编码器不仅可以控制日志字节数据的格式,还可以控制是否写入和何时写入数据。简单地说,编码器比布局好,如果可以使用编码器,就不需要布局。当然,对于使用布局的旧代码,官员还提供了一些适配类,例如LayoutWrappingEncoder。

模式

模式主要用于定义日志等的输出格式,详见PatternLayout。

记录器

记录器可以与LoggerFactory进行比较。在我的编解码器中,getLogger生成的Logger对象对应三个属性:

Appder-ref

addder-ref将一个记录器对象绑定到指定记录器将输出日志到哪个附件的特定附录。一个记录器可以有多个Appder-Ref,也就是说,可以同时绑定多个附加器.

根实际上是一个特殊的记录器。通过查看名称,您知道她是一个根节点,是所有记录器的祖先,并且配置文件中必须有一个根。

日志打印源代码分析

过滤和创建LogEvent

这里我们实际上在我们常用的logger.info()条目中调用filterAndLog_1()方法。

此方法做两件事,过滤并创建LoggingEvent,并将其推送到实际绑定的Appder对象中。

公共最终类Logger实现org.slf4j.Logger、LocationAwareLogger、AppenderAttacable、Serialable{public voidinfo(String Format,Object Arg){filterAndLog_1(FQCN,NULL,Level.INFO,Format,Arg,null);}私有voidfilterAndLog_1(最终字符串localFQCN、最终标记标记、最终级别、最终字符串MSg、最终对象Param、最终Throwable t){/*在这里由一系列过滤器处理,并根据传入标记*FilterReply.NEUTRAL表示中立的意思*/FinalFilterReply=loggerContext所满足的条件返回不同的结果。GetTurboFilterChainDecision_1(标记,this,level,msg,param,t);过滤后返回到中性,进一步确定当前要打印的日志级别“info”是否高于或等于有效的日志级别。*如果它小于有效的日志级别,则意味着日志无效,直接返回,并且不执行特定的输出操作。*/if(Decision==FilterReply.NEUTric){if(EfftiveLevelInt>Level.Level Int){Reback;}Else if(Decision==FilterReply)。拒绝){返回;}buildLoggingEventAndAppend(localFQCN,标记,级别,msg,新对象[]{param},t);}/*创建日志事件,推送到与记录器绑定的所有Appder中*/绑定的所有BuildLoggingEventAndAppend(最终字符串localFQCN、最终标记标记、最终级别、最终字符串MSg、最终对象[]Params、Final Throwable t){/创建日志事件LoggingEvent le=新的LoggingEvent(localFQCN,此、级别、g、t、params);le。SETMARKER(标记);//推送到与记录器绑定的所有Appder中CallAppders(Le);}公共无效CallAppders(ILoggingEvent事件){int写=0;这里有一个熟悉的单词加法,我们在介绍记录器时说过,*她是记录器的一个属性,默认值是true*结合这段代码,我们看到for循环,第一个Push事件,进入当前的记录器附加程序,累积*写,然后确定加法,*如果是,取记录器的父程序。继续将事件推送到父记录器的附录中,*等等,直到父或祖先的加法为false,跳出循环,或将其发送到根日志根节点。*这解释了添加剂的作用。*/for(Logger l=this;l!=NULL;l=1.parent){写+=1.附录LoopOnAppders(事件);if(!L.相加){Break;}/无层次结构中的追加器,如果(写=0){loggerContext。NoAppenderDefinedWarning(This);}私有int附录LoopOnAppders(ILoggingEvent事件){if(AAI!=NULL){返回aai.附录LoopOnAppders(事件);}Other{返回0;}}/*AppderList是绑定到Logger*的一个Appder*,例如,上面logback.xml*der中由name=“file”绑定的记录器是ch.qos.logback.core.FileAppender类。*因为每个记录器可以同时绑定多个附加器,所以使用数组AppderList在这里存储这些附录。*/公共附录LoopOnAppders(E_E){int size=0;最后一个附录[]AppderArray=AppderList.asTypedArray();Final int len=AppderArray.Length;for(int i=0;i

通过FileAppender打印到文件

logback.xml中名称=“file”的记录器绑定的Appder是ch.qos.logback.core。类,所以让我们以FileAppender为例来分析日志写入文件的整个过程。

FIleAppender类的继承图:

整个过程如下:

FileAppender指定日志打印的目标文件,并指定文件到OutputStreamAppender的输出流OutputStream。

目标文件的指定操作发生在记录器初始化期间。通过加载和解析logback.xml,可以找到name=“file”的元素,并读取子元素的值以获得该文件。

OutputStreamAppender提供了一个基本服务,它继承并实现了UnSynizedAppenderBase抽象类。

添加特定日志格式和打印操作的抽象方法(E Event Object),实际上,要将格式化的日志输入到FileAppender

提供的输出流中,可以看到UnSynizedAppenderBase没有执行线程安全度量,而是将线程安全问题留给子类自己处理,OutputStreamAppender自己处理了这些问题,并且通过向写字节(字节[]字节数组)方法添加ReentrantLock锁来确保日志写入中的线程安全。

接下来,我们从下到上分析以下类中的重要方法:

公共类FileAppender扩展OutputStreamAppender{。/*/public voidstart(){int错误=0;if(getFile()!=null){/p>

/*检查日志文件是否被其他附录绑定,并返回false*/if(checkForFileCollisionInPreviousFileAppers()){addError(“与前面定义的FileAppender/RollingAppender实例检测到的冲突”)。“如果不绑定中止。”;addError(MORE_INFO_PREX+CORICATION_WITOR_PREER_APPENDER_URL);错误++;}OROR{try{/*打开未被其他附录绑定到OutputStreamAppender的文件输出流,*用于后续日志输入。*/OpenFile(getFile());}Catch(java.io.IOException e){错误++;addError(“OpenFile(”+filename+“,”+append+“)调用失败。AddError(“\”File\“属性未为名为[”+name+“].”的Appder设置);}if(Error=0){超级。开始();}}/*检查日志文件是否被其他附录绑定,如果绑定,则返回true*,否则将记录Appder的名称和日志文件名,指示当前文件已被Appder绑定,*无法再绑定到另一个Appder,然后返回false*/受保护的布尔校验ForFileCollisionInPreviousFileAppders(){boolesionsDetected=false if(filename==null){返回false;}@Suppress Warnings(“uncheck”)Mapmap=(map)context.getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);如果(map==NULL){返回冲突被检测;}对于(entryentry:map.entrySet()){if(fileName.equals(entry.getValue(){addErrorForCollision(“File”,entry.getValue(),entry.getKey());ColsionsDetected=true;}}if(name!=null){map。PUT(getName(),filename);}返回冲突sDetected;}/**创建一个ResilientFileOutputStream输出流,*到OutputStreamAppender中的outputStream成员变量::Documents必须是可写的*尽管该方法是公共的,但不要直接调用它。*相反,在调用*/public voidOpenFile(String File_Name)引发IOException{lock.lock()之前,逐步通过start方法设置返回成员变量;尝试{File file=新文件(File_Name);布尔结果=FileUtil.createMissingParentDirectors(File)if(!Response){addError(“未能为[”+file.getAbtePath()+“]创建父目录”);}ResilientFileOutputStream弹性Fos=新ResilientFileOutputStream(文件、追加、缓冲区Size.getSize());弹性Fos.setContext(上下文);setOutputStream(弹性Fos);}{lock.unlock();}}.}

OutputStreamAppender

公共类OutputStreamAppender扩展非同步化AppenderBase受保护编码器;受保护的最终ReentrantLock锁=新ReentrantLock(False);}/*真正的的写入操作*

*大多数WriterAppender的子类将需要覆盖此*方法。*/受保护的voidsubAppend(E事件){if(!isStarted()){back;}try{//此步骤避免LBCLASSIC-139 if(DeferredProcessingAware的事件实例){((DeferredProcessingAware)事件).preareForDeferredProcessing();}//格式化日志文件字节[]byteArray=this.编码器。使用ReentrantLock确保线程安全,写入日志(ByteArray);}例如(IOException IoE){this.start=false;addStatus(新的ErrorStatus(“Adperder中的IO失败”,this,IoE)}}/**例如OutputStream,**例如FileAppender中打开的ResilientFileOutputStream*/公共voidsetOutputStream(OutputStream,OutputStream){lock();尝试{//关闭以前打开的任何输出流CloseOutputStream();this.unlock();}}}

UnsynchronizedAppenderBase

abstract公共类UnSynizedAppenderBase扩展ContextAwareBase实现Appder{受保护的布尔启动=false;/*保护用来防止Appder重复调用自己的doAppend方法*/Private ThreadLocal卫士=新的线程本地();/*附录命名。*/受保护的字符串名称;私有FilterAttachableImplfai=新的FilterAttachableImpl();私有int statusRepeatCount=0;私有int异常计数=0;静态INT LEXED_REBERATS=3;公共无效doAppend(E Event Object){//确定当前线程是否正在打印日志。如果(Boolean.TRUE.等于(卫士.获取(){返回;}尝试{卫士设置(Boolean.TRUE);if(!this.

汇总

ConsoleAppender:打印日志到控制台RollingFileAppender:可以根据某些规则(如日期、日志大小等)剪切和备份日志。

这篇文章没有一个一个地介绍,它们的过程是相同的,让一个感兴趣的学生自己来分析。