千家信息网

开源日志库Logger架构是什么

发表于:2025-11-13 作者:千家信息网编辑
千家信息网最后更新 2025年11月13日,这篇"开源日志库Logger架构是什么"文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇"
千家信息网最后更新 2025年11月13日开源日志库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;}// 获取tagString 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 localTag = new ThreadLocal();/*** @return the appropriate tag based on local or global */private String getTag() {// 从ThreadLocal localTag里获取本地一个缓存的tagString tag = localTag.get();if (tag != null) {localTag.remove();return tag;}return this.tag;}

这个方法是获取本地或者全局的tag值,当localTag中有tag的时候就返回出去,并且清空localTag的值

接着,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;// 消息字节长度小于等于4000if (length  0) {// 方法数大于0,打印出分割线logDivider(priority, tag);}// 打印消息内容logContent(priority, tag, message);// 打印日志体底部边界logBottomBorder(priority, tag);return;}if (methodCount > 0) {logDivider(priority, tag);}for (int i = 0; i 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) {// 最后格式化下tagString 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 defaultdefault:settings.getLogAdapter().d(finalTag, chunk);break;}}

这个方法很简单,就是最后格式化tag,然后根据不同的日志类型把打印的工作交给LogAdapter接口来处理,我们来看看settings.getLogAdapter()这个方法(Settings.java文件):

public LogAdapter getLogAdapter() {if (logAdapter == null) {// 最终的实现类是AndroidLogAdapterlogAdapter = new AndroidLogAdapter();}return logAdapter;}

找到AndroidLogAdapter类:

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

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

以上就是关于"开源日志库Logger架构是什么"这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注行业资讯频道。

方法 日志 内容 消息 就是 接口 线程 架构 时候 字符 换行符 文件 格式 不同 信息 字符串 数组 篇文章 级别 配置 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 数据库安全的第一道保障是 国泰安数据库不能查美股吗 邯郸市坤玥网络技术有限公司 云服务器写进目录里的东西删不掉 网络技术有限公司注册条件 网络安全病毒纪录片 融媒体网络安全隐患排查 怎么看数据库字符多少 算法在数据库中的应用几个表格 北京昊天四方网络技术有限公司 甘肃手机软件开发平台 关于公司无线网络安全的报告 讲网络安全的凯撒 普陀区正规数据库收费标准 广东服务器机柜哪家有卖 网络安全法律体系专门立法 数据库系统的安全等级包括 架设服务器提示网络通讯错误 思科网络技术学院教程第二章 南京手机软件开发客户至上 妇联国家网络安全宣传标语 国泰安数据库不能查美股吗 gitlab连接pg数据库 江苏mac软件开发 我的世界 国际版 服务器 免费 产品小批量制作软件开发 构建国家网络安全 移动数据库系统 网络安全计算题期末考试 发呗社区软件开发
0