开源日志库Logger架构是什么(logger,开发技术)

时间:2024-04-29 23:25:17 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

库的整体架构图

开源日志库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());}}
开源日志库Logger架构是什么

接下来看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这样的格式是有换行符的,所以打印呈现出来的效果就是:

开源日志库Logger架构是什么

上面说了大半天,都还没看到具体的打印是啥,现在来看看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类:

开源日志库Logger架构是什么

原来绕了一大圈,最终打印还是使用了:系统的Log。

好了Logger日志框架的源码解析完了,有没有更清晰呢,也许小伙伴会说这个最终的日志打印,我不想用系统的Log,是不是可以换呢。这是自然的,看开篇的那种整体架构图,这个LogAdapter是个接口,只要实现这个接口,里面做你自己想要打印的方式,然后通过Settings 的logAdapter(LogAdapter logAdapter)方法设置进去就可以。

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:开源日志库Logger架构是什么的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:ES6对象有什么用下一篇:

3 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18