爆料喽!!!开源日志库Logger的剖析分析
| 导读 | Logger类提供了多种方法来处理日志活动。上一篇介绍了开源日志库Logger的使用,今天我主要来分析Logger实现的原理。 |

我们从使用的角度来对Logger库抽茧剥丝:
String userName = "Jerry";
Logger.i(userName);
看看Logger.i()这个方法:
public static void i(String message, Object... args) {
printer.i(message, args);
}
还有个可变参数,来看看printer.i(message, args)是啥:
public Interface Printer{
void i(String message, Object... args);
}
是个接口,那我们就要找到这个接口的实现类,找到printer对象在Logger类中声明的地方:
private static Printer printer = new LoggerPrinter();
实现类是LoggerPrinter,而且这还是个静态的成员变量,这个静态是有用处的,后面会讲到,那就继续跟踪LoggerPrinter类的i(String message, Object... args)方法的实现:
@Override public void i(String message, Object... args) {
log(INFO, null, message, args);
}
/**
* This method is synchronized in order to avoid messy of logs' order.
*/
private synchronized void log(int priority, Throwable throwable, String msg, Object... args) {
// 判断当前设置的日志级别,为NONE则不打印日志
if (settings.getLogLevel() == LogLevel.NONE) {
return;
}
// 获取tag
String tag = getTag();
// 创建打印的消息
String message = createMessage(msg, args);
// 打印
log(priority, tag, message, throwable);
}
public enum LogLevel {
/**
* Prints all logs
*/
FULL,
/**
* No log will be printed
*/
NONE
}
首先,log方法是一个线程安全的同步方法,为了防止日志打印时候顺序的错乱,在多线程环境下,这是非常有必要的。
其次,判断日志配置的打印级别,FULL打印全部日志,NONE不打印日志。
再来,getTag():
private final ThreadLocal<String> localTag = new ThreadLocal<>();
/**
* @return the appropriate tag based on local or global */
private String getTag() {
// 从ThreadLocal<String> localTag里获取本地一个缓存的tag
String tag = localTag.get();
if (tag != null) {
localTag.remove();
return tag;
}
return this.tag;
}
这个方法是获取本地或者全局的tag值,当localTag中有tag的时候就返回出去,并且清空localTag的值,关于ThreadLocal还不是很清楚的可以参考主席的文章:http://blog.csdn.net/singwhat...
接着,createMessage方法:
private String createMessage(String message, Object... args) {
return args == 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,打印级别,打印的消息处理好了,接下来该打印出来了:
@Override public synchronized void log(int priority, String tag, String message, Throwable throwable) {
// 同样判断一次库配置的打印开关,为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 = "No message/exception is set";
}
// 获取方法数
int methodCount = getMethodCount();
// 判断消息是否为空
if (Helper.isEmpty(message)) {
message = "Empty/NULL log message";
}
// 打印日志体的上边界
logTopBorder(priority, tag);
// 打印日志体的头部内容
logHeaderContent(priority, tag, methodCount);
//get bytes of message with system's default charset (which is UTF-8 for Android)
byte[] bytes = message.getBytes();
int length = bytes.length;
// 消息字节长度小于等于4000
if (length <= CHUNK_SIZE) {
if (methodCount > 0) {
// 方法数大于0,打印出分割线
logDivider(priority, tag);
}
// 打印消息内容
logContent(priority, tag, message);
// 打印日志体底部边界
logBottomBorder(priority, tag);
return;
}
if (methodCount > 0) {
logDivider(priority, tag);
}
for (int i = 0; i < length; i += CHUNK_SIZE) {
int count = Math.min(length - i, CHUNK_SIZE);
//create a new String with system's default charset (which is UTF-8 for Android)
logContent(priority, tag, new String(bytes, i, count));
}
logBottomBorder(priority, tag);
}
我们重点来看看logHeaderContent方法和logContent方法:
@SuppressWarnings("StringBufferReplaceableByString")
private void logHeaderContent(int logType, String tag, int methodCount) {
// 获取当前线程堆栈跟踪元素数组
//(里面存储了虚拟机调用的方法的一些信息:方法名、类名、调用此方法在文件中的行数)
// 这也是这个库的 “核心”
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
// 判断库的配置是否显示线程信息
if (settings.isShowThreadInfo()) {
// 获取当前线程的名称,并且打印出来,然后打印分割线
logChunk(logType, tag, HORIZONTAL_DOUBLE_LINE + "Thread: " + Thread.currentThread().getName()); logDivider(logType, tag);
}
String level = "";
// 获取追踪栈的方法起始位置
int stackOffset = getStackOffset(trace) + settings.getMethodOffset();
//corresponding method count with the current stack may exceeds the stack trace. Trims the count
// 打印追踪的方法数超过了当前线程能够追踪的方法数,总的追踪方法数扣除偏移量(从调用日志的起算扣除的方法数),就是需要打印的方法数量
if (methodCount + stackOffset > trace.length) {
methodCount = trace.length - stackOffset - 1;
}
for (int i = methodCount; i > 0; i--) {
int stackIndex = i + stackOffset;
if (stackIndex >= trace.length) {
continue;
}
// 拼接方法堆栈调用路径追踪字符串
StringBuilder builder = new StringBuilder();
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方法:
private void logContent(int logType, String tag, String chunk) {
// 这个作用就是获取换行符数组,getProperty方法获取的就是"//n"的意思
String[] lines = chunk.split(System.getProperty("line.separator"));
for (String line : lines) {
// 打印出包含换行符的内容
logChunk(logType, tag, HORIZONTAL_DOUBLE_LINE + " " + line);
}
}
如上图来说内容是字符串数组,本身里面是没用换行符的,所以不需要换行,打印出来的效果就是一行,但是json、xml这样的格式是有换行符的,所以打印呈现出来的效果就是:

上面说了大半天,都还没看到具体的打印是啥,现在来看看logChunk方法:
private void logChunk(int logType, String tag, String chunk) {
// 最后格式化下tag
String finalTag = formatTag(tag);
// 根据不同的日志打印类型,然后交给LogAdapter这个接口来打印
switch (logType) {
case ERROR:
settings.getLogAdapter().e(finalTag, chunk);
break;
case INFO:
settings.getLogAdapter().i(finalTag, chunk);
break;
case VERBOSE:
settings.getLogAdapter().v(finalTag, chunk);
break;
case WARN:
settings.getLogAdapter().w(finalTag, chunk);
break;
case ASSERT:
settings.getLogAdapter().wtf(finalTag, chunk);
break;
case DEBUG:
// Fall through, log debug by default
default:
settings.getLogAdapter().d(finalTag, chunk);
break;
}
}
这个方法很简单,就是最后格式化tag,然后根据不同的日志类型把打印的工作交给LogAdapter接口来处理,我们来看看settings.getLogAdapter()这个方法(Settings.java文件):
public LogAdapter getLogAdapter() {
if (logAdapter == null) {
// 最终的实现类是AndroidLogAdapter
logAdapter = new AndroidLogAdapter();
}
return logAdapter;
}
找到AndroidLogAdapter类:

原来绕了一大圈,最终打印还是使用了:系统的Log。
好了Logger日志框架的源码解析完了,有没有更清晰呢,也许小伙伴会说这个最终的日志打印,我不想用系统的Log,是不是可以换呢。这是自然的,看开篇的那种整体架构图,这个LogAdapter是个接口,只要实现这个接口,里面做你自己想要打印的方式,然后通过Settings 的logAdapter(LogAdapter logAdapter)方法设置进去就可以。
以上就是博主分析一个开源库的思路,从使用的角度出发抽茧剥丝,基本上一个库的核心部分都能搞懂。画画整个框架的大概类图,对分析库非常有帮助,每一个轮子都有值得学习的地方,吸收了就是进步的开始,耐心的分析完一个库,还是非常有成就感的。

爆料喽!!!开源日志库Logger的剖析分析的更多相关文章
- 爆料喽!!!开源日志库Logger的使用秘籍
日志对于开发来说是非常重要的,不管是调试数据查看.bug问题追踪定位.数据信息收集统计,日常工作运行维护等等,都大量的使用到.今天介绍著名开源日志库Logger的使用,库的地址:https://git ...
- Android开源日志库Logger的使用
https://blog.csdn.net/Power_Qyh/article/details/78159598?locationNum=2&fps=1 https://github.com/ ...
- [转]开源日志库<log4cplus+VS2008使用>整理
转 开源日志库<log4cplus+VS2008使用>整理 转http://pyhcx.blog.51cto.com/713166/143549 一.简介 log4cplus是C+ ...
- arcgis api for js之echarts开源js库实现地图统计图分析
前面写过一篇关于arcgis api for js实现地图统计图的,具体见:http://www.cnblogs.com/giserhome/p/6727593.html 那是基于dojo组件来实现图 ...
- arcgis api 3.x for js 之 echarts 开源 js 库实现地图统计图分析(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...
- 开源日志库log4cplus+VS2008使用
一.简介 log4cplus是C++编写的开源的日志系统,功能非常全面.本文介绍如何在Windows+VS2008中使用该日志库. 二.下载 可从网站[url]http://log ...
- C++的开源跨平台日志库glog学习研究(一)
作为C++领域中为数不多的好用.高效的.跨平台的日志工具,Google的开源日志库glog也算是凤毛麟角了.glog 是一个C++实现的应用级日志记录框架,提供了C++风格的流操作. 恰巧趁着五一我也 ...
- 字节开源RPC框架Kitex的日志库klog源码解读
前言 这篇文章将着重于分析字节跳动开源的RPC框架Kitex的日志库klog的源码,通过对比Go原生日志库log的实现,探究其作出的改进. 为了平滑学习曲线,我写下了这篇分析Go原生log库的文章,希 ...
- C++的开源跨平台日志库glog学习研究(三)--杂项
在前面对glog分别做了两次学习,请看C++的开源跨平台日志库glog学习研究(一).C++的开源跨平台日志库glog学习研究(二)--宏的使用,这篇再做个扫尾工作,算是基本完成了. 编译期断言 动态 ...
随机推荐
- 【原】CSS3 Media在常用设备的设置值
摘要:今天的一个小小的项目中,在各种手机上样式都显示正常,唯独iphone4s的有些许问题.产品经理又说iphone4s用户还挺多的,无奈,只能查一查iphone4s的media值,顺便做个小小总结; ...
- php实现文件上传下载功能小结
文件的上传与下载是项目中必不可少的模块,也是php最基础的模块之一,大多数php框架中都封装了关于上传和下载的功能,不过对于原生的上传下载还是需要了解一下的.基本思路是通过form表单post方式实现 ...
- JavaScript学习笔记——BOM_window对象
javascript浏览器对象模型-windwo对象 BOM Browser Object Model window对象 是BOM中所有对象的核心. 一.属性 1.(位置类型-获得浏览器的位置) IE ...
- JSP 属性范围
参考文献:http://www.cnblogs.com/xdp-gacl/p/3781056.html 一.属性范围 所谓的属性范围就是一个属性设置之后,可以经过多少个其他页面后仍然可以访问的保存范围 ...
- 谁再说Matlab速度慢,我跟谁急
谁再说Matlab速度慢,我跟谁急 一.未进行内存预分配 y = 0; tic; for i=2:100000; y(i+1) = y(i)+ randn; end; toc 时间已过 0.03122 ...
- cmake 编译 c++ dll 的一个例子(更新1)
CMakeLists.txt project(xxx) add_library(xxx SHARED xxx.cpp) add_executable(yyy yyy.cpp) target_link_ ...
- UML关系图
在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composit ...
- Django基础,Day10 - template 模板引擎与路径设置
作为一个Web框架,Django需要一个方便的方式来生成动态的HTML.最常见的方法依赖于模板.模板包含所需的HTML输出的静态部分以及一些特殊的语法描述如何插入动态内容. Django框架后端默认支 ...
- Robot Framework--11 RF结合Jenkins
转自:http://blog.csdn.net/tulituqi/article/details/17846463 为什么我们要引入RF?其实最初我们引入RF是为了能够快速的开展自动化验收测试,为敏捷 ...
- Yii2创建多界面主题(Theme)
Yii2界面主题上的设计总体上和Yii1.x保持一致,区别在于两个地方: 1. 由于Yii2引入了独立的视图(View)类,因此界面主题(Theme)也交由视图来管理: 2. 视图文件和Web资源在目 ...