开源日志库Logger架构是什么
这篇“开源日志库Logger架构是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“开源日志库Logger架构是什么”文章吧。
我们从使用的角度来对Logger库抽茧剥丝:
StringuserName="Jerry";Logger.i(userName);
看看Logger.i()这个方法:
publicstaticvoidi(Stringmessage,Object...args){printer.i(message,args);}
还有个可变参数,来看看printer.i(message, args)是啥:
publicInterfacePrinter{voidi(Stringmessage,Object...args);}
是个接口,那我们就要找到这个接口的实现类,找到printer对象在Logger类中声明的地方:
privatestaticPrinterprinter=newLoggerPrinter();
实现类是LoggerPrinter,而且这还是个静态的成员变量,这个静态是有用处的,后面会讲到,那就继续跟踪LoggerPrinter类的i(String message, Object… args)方法的实现:
@Overridepublicvoidi(Stringmessage,Object...args){log(INFO,null,message,args);}/***Thismethodissynchronizedinordertoavoidmessyoflogs'order.*/privatesynchronizedvoidlog(intpriority,Throwablethrowable,Stringmsg,Object...args){//判断当前设置的日志级别,为NONE则不打印日志if(settings.getLogLevel()==LogLevel.NONE){return;}//获取tagStringtag=getTag();//创建打印的消息Stringmessage=createMessage(msg,args);//打印log(priority,tag,message,throwable);}publicenumLogLevel{/***Printsalllogs*/FULL,/***Nologwillbeprinted*/NONE}
首先,log方法是一个线程安全的同步方法,为了防止日志打印时候顺序的错乱,在多线程环境下,这是非常有必要的。 其次,判断日志配置的打印级别,FULL打印全部日志,NONE不打印日志。 再来,getTag():
privatefinalThreadLocallocalTag=newThreadLocal();/***@returntheappropriatetagbasedonlocalorglobal*/privateStringgetTag(){//从ThreadLocallocalTag里获取本地一个缓存的tagStringtag=localTag.get();if(tag!=null){localTag.remove();returntag;}returnthis.tag;}
这个方法是获取本地或者全局的tag值,当localTag中有tag的时候就返回出去,并且清空localTag的值
接着,createMessage方法:
privateStringcreateMessage(Stringmessage,Object...args){returnargs==null||args.length==0?message:String.format(message,args);}
这里就很清楚了,为什么我们用Logger.i(message, args)的时候没有写args,也就是null,也可以打印,而且是直接打印的message消息的原因。同样博主上一篇文章也提到了:
Logger.i("博主今年才%d,英文名是%s",16,"Jerry");
像这样的可以拼接不同格式的数据的打印日志,原来实现的方式是用String.format方法,这个想必小伙伴们在开发Android应用的时候String.xml里的动态字符占位符用的也不少,应该很容易理解这个format方法的用法。
重头戏,我们把tag,打印级别,打印的消息处理好了,接下来该打印出来了:
@Overridepublicsynchronizedvoidlog(intpriority,Stringtag,Stringmessage,Throwablethrowable){//同样判断一次库配置的打印开关,为NONE则不打印日志if(settings.getLogLevel()==LogLevel.NONE){return;}//异常和消息不为空的时候,获取异常的原因转换成字符串后拼接到打印的消息中if(throwable!=null&&message!=null){message+=":"+Helper.getStackTraceString(throwable);}if(throwable!=null&&message==null){message=Helper.getStackTraceString(throwable);}if(message==null){message="Nomessage/exceptionisset";}//获取方法数intmethodCount=getMethodCount();//判断消息是否为空if(Helper.isEmpty(message)){message="Empty/NULLlogmessage";}//打印日志体的上边界logTopBorder(priority,tag);//打印日志体的头部内容logHeaderContent(priority,tag,methodCount);//getbytesofmessagewithsystem'sdefaultcharset(whichisUTF-8forAndroid)byte[]bytes=message.getBytes();intlength=bytes.length;//消息字节长度小于等于4000if(length0){//方法数大于0,打印出分割线logDivider(priority,tag);}//打印消息内容logContent(priority,tag,message);//打印日志体底部边界logBottomBorder(priority,tag);return;}if(methodCount>0){logDivider(priority,tag);}for(inti=0;isdefaultcharset(whichisUTF-8forAndroid)logContent(priority,tag,newString(bytes,i,count));}logBottomBorder(priority,tag);}
我们重点来看看logHeaderContent方法和logContent方法:
@SuppressWarnings("StringBufferReplaceableByString")privatevoidlogHeaderContent(intlogType,Stringtag,intmethodCount){//获取当前线程堆栈跟踪元素数组//(里面存储了虚拟机调用的方法的一些信息:方法名、类名、调用此方法在文件中的行数)//这也是这个库的“核心”StackTraceElement[]trace=Thread.currentThread().getStackTrace();//判断库的配置是否显示线程信息if(settings.isShowThreadInfo()){//获取当前线程的名称,并且打印出来,然后打印分割线logChunk(logType,tag,HORIZONTAL_DOUBLE_LINE+"Thread:"+Thread.currentThread().getName());logDivider(logType,tag);}Stringlevel="";//获取追踪栈的方法起始位置intstackOffset=getStackOffset(trace)+settings.getMethodOffset();//correspondingmethodcountwiththecurrentstackmayexceedsthestacktrace.Trimsthecount//打印追踪的方法数超过了当前线程能够追踪的方法数,总的追踪方法数扣除偏移量(从调用日志的起算扣除的方法数),就是需要打印的方法数量if(methodCount+stackOffset>trace.length){methodCount=trace.length-stackOffset-1;}for(inti=methodCount;i>0;i--){intstackIndex=i+stackOffset;if(stackIndex>=trace.length){continue;}//拼接方法堆栈调用路径追踪字符串StringBuilderbuilder=newStringBuilder();builder.append("U").append(level).append(getSimpleClassName(trace[stackIndex].getClassName()))//追踪到的类名.append(".").append(trace[stackIndex].getMethodName())//追踪到的方法名.append("").append("(").append(trace[stackIndex].getFileName())//方法所在的文件名.append(":").append(trace[stackIndex].getLineNumber())//在文件中的行号.append(")");level+="";//打印出头部信息logChunk(logType,tag,builder.toString());}}
接下来看logContent方法:
privatevoidlogContent(intlogType,Stringtag,Stringchunk){//这个作用就是获取换行符数组,getProperty方法获取的就是"//n"的意思String[]lines=chunk.split(System.getProperty("line.separator"));for(Stringline:lines){//打印出包含换行符的内容logChunk(logType,tag,HORIZONTAL_DOUBLE_LINE+""+line);}}
如上图来说内容是字符串数组,本身里面是没用换行符的,所以不需要换行,打印出来的效果就是一行,但是json、xml这样的格式是有换行符的,所以打印呈现出来的效果就是:
上面说了大半天,都还没看到具体的打印是啥,现在来看看logChunk方法:
privatevoidlogChunk(intlogType,Stringtag,Stringchunk){//最后格式化下tagStringfinalTag=formatTag(tag);//根据不同的日志打印类型,然后交给LogAdapter这个接口来打印switch(logType){caseERROR:settings.getLogAdapter().e(finalTag,chunk);break;caseINFO:settings.getLogAdapter().i(finalTag,chunk);break;caseVERBOSE:settings.getLogAdapter().v(finalTag,chunk);break;caseWARN:settings.getLogAdapter().w(finalTag,chunk);break;caseASSERT:settings.getLogAdapter().wtf(finalTag,chunk);break;caseDEBUG://Fallthrough,logdebugbydefaultdefault:settings.getLogAdapter().d(finalTag,chunk);break;}}
这个方法很简单,就是最后格式化tag,然后根据不同的日志类型把打印的工作交给LogAdapter接口来处理,我们来看看settings.getLogAdapter()这个方法(Settings.java文件):
publicLogAdaptergetLogAdapter(){if(logAdapter==null){//最终的实现类是AndroidLogAdapterlogAdapter=newAndroidLogAdapter();}returnlogAdapter;}
找到AndroidLogAdapter类:
原来绕了一大圈,最终打印还是使用了:系统的Log。
好了Logger日志框架的源码解析完了,有没有更清晰呢,也许小伙伴会说这个最终的日志打印,我不想用系统的Log,是不是可以换呢。这是自然的,看开篇的那种整体架构图,这个LogAdapter是个接口,只要实现这个接口,里面做你自己想要打印的方式,然后通过Settings 的logAdapter(LogAdapter logAdapter)方法设置进去就可以。
以上就是关于“开源日志库Logger架构是什么”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。