Android Logger 日志框架源码分析

Logger框架使用

Logger框架是一个优雅的日志系统,通过Logger可以很漂亮的打印出Android系统日志。下面从用法开始逐层介绍。

在Gradle 依赖中添加,引入Logger库

compile 'com.orhanobut:logger:1.15'

这里写图片描述

 使用方法

初始化,一般在Application中完成。

Logger
  .init(YOUR_TAG)                 // default PRETTYLOGGER or use just init()
  .methodCount(3)                 // default 2
  .hideThreadInfo()               // default shown
  .logLevel(LogLevel.NONE)        // default LogLevel.FULL
  .methodOffset(2)                // default 0
  .logAdapter(new AndroidLogAdapter()); //default AndroidLogAdapter

2 调用Logger

Logger.d("hello");
Logger.e(exception, "message");
Logger.json(JSON_CONTENT);

这里写图片描述


源码分析

Logger类图
这里写图片描述

从Logger.init()开始
可以看到Logger.init()最终会调到

 /**
   * It is used to change the tag
   *
   * @param tag is the given string which will be used in Logger as TAG
   */
  public static Settings init(String tag) {
    printer = new LoggerPrinter();
    return printer.init(tag);
  }

这个方法初始化Printer类,所有的配置信息通过Setting类进行维护,可以看到Setting类中有许多默认的成员变量,这些都是设置的默认值


public final class Settings {

  private int methodCount = 2;
  private boolean showThreadInfo = true;
  private int methodOffset = 0;
  private LogAdapter logAdapter;

  /**
   * Determines to how logs will be printed
   */
  private LogLevel logLevel = LogLevel.FULL;

  // 省略

}

初始化完成了,下面看下如何打印出log,下面以debug模式进行输出.

LoggerPrinter是真正的日志输出类,下面看先实现.

    @Override
    public void d(String message, Object... args) {
        log(DEBUG, null, message, args);
    }

    @Override
    public void d(Object object) {
        String message;
        if (object.getClass().isArray()) {
            // 如果输出对象是数组的化,将数组打印
            message = Arrays.deepToString((Object[]) object);
        } else {
            message = object.toString();
        }
        log(DEBUG, null, message);
    }

继续看log方法

    /**
     * This method is synchronized in order to avoid messy of logs' order.
     * log方法是一个同步方法,防止多线程调用时顺序混乱
     */
    private synchronized void log(int priority, Throwable throwable, String msg, Object... args) {
        if (settings.getLogLevel() == LogLevel.NONE) {
            return;
        }
        String tag = getTag(); // 此处获取用户设置的TAG
        String message = createMessage(msg, args);
        log(priority, tag, message, throwable);
    }

Logger将Tag设置到ThreadLocal中进行保存
这样可以给不同的线程设置不同的Tag,下面简单介绍下ThreadLocal,这也是本框架的关键点之一

  /**
     * @return the appropriate tag based on local or global
     * 
     * 这个方法的意思是,从当前线程的ThreadLocal中取得Tag,取到的话清
     * 除ThreadLocal中的Tag;没有设置线程的tag的话,使用系统或用户设置
     * 的Tag
     */
    private String getTag() {
        String tag = localTag.get();
        if (tag != null) {
            localTag.remove();
            return tag;
        }
        return this.tag;
    }

ThreadLocal详解
ThreadLocal 是一个线程内部的存储类,它可以在指定线程中存储数据,对于其他线程来说则无法获取到数据.一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

在Java的Thread类中,有一个localValues字段,是用来存储数据的,这个也是ThreadLocal的实际存储容器.

// Thread 类中

/**
 * Normal thread local values.
 */
ThreadLocal.Values localValues;

/**
 * Inheritable thread local values.
 */
ThreadLocal.Values inheritableValues;

看先ThreadLocal中的get方法(从Thread中取出容器,从容器中取出对象)

    /**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

接着往回看Logger,实际的Log方法


    @Override
    public synchronized void log(int priority, String tag, String message, Throwable throwable) {
        if (settings.getLogLevel() == LogLevel.NONE) {
            return;
        }
        // 拼接Log信息
        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;
        if (length <= CHUNK_SIZE) {
            if (methodCount > 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);
    }

// 调用LogChunk进行输出,输出使用的Adapter
private void logChunk(int logType, String tag, String chunk) {
        String finalTag = formatTag(tag);
        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;
        }
    }
// 底层仍然使用Android提供的Log方法进行输出
class AndroidLogAdapter implements LogAdapter {
    @Override
    public void d(String tag, String message) {
        Log.d(tag, message);
    }

    @Override
    public void e(String tag, String message) {
        Log.e(tag, message);
    }

    @Override
    public void w(String tag, String message) {
        Log.w(tag, message);
    }

    @Override
    public void i(String tag, String message) {
        Log.i(tag, message);
    }

    @Override
    public void v(String tag, String message) {
        Log.v(tag, message);
    }

    @Override
    public void wtf(String tag, String message) {
        Log.wtf(tag, message);
    }
}

Logger在Header中可以打印出Log的位置,这个是怎么做到的呢?接着往下分析


    @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("║ ")
                    .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());
        }
    }

这里我们直接调用的StackTraceElement的toString方法

StackTraceElement表示一个虚拟机栈帧,通过这个栈帧可以获取如下信息

  • getClassName
  • getMethodName
  • getFileName
  • getLineNumber

看名字就知道什么意思了,我们可以根据这些信息拼接要打印的信息。

下面简单写一个Demo,实现一个类似的功能.

public class MyLog {

    public static String TAG = "yang";

    public static void d(Class clazz, String msg) {
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        String trace = null;
        int i = 0;
        // 和MyLog相同的下一个栈帧为MyLog调用的栈帧信息
        for (i = 0; i < elements.length; i++) {
            if (elements[i].getClassName().equals(MyLog.class.getName())) {
                break;
            }
        }
        StackTraceElement targetElement = elements[++i];
        trace = "(" + targetElement.getFileName() + ":" + targetElement.getLineNumber() + ")";
        Log.d(TAG, trace);

    }
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值