Android技术知识Android开发程序员

Logger源码理解分析

2018-02-09  本文已影响923人  请叫我张懂

安卓开发过程中,log日志是我们接触最多的一部分。如何优雅的获取log日志呢?我个人推荐使用Logger

GitHub/Logger传送门

Logger效果展示

备注:

log级别 颜色
Verbose BBBBBB
Debug 0070BB
Info 48BB31
Warm BBBB23
Error FF0006
Assert 8F0005

控制台日志

代码部分:
Logcat代码部分.png
截图部分:
Verbose.png Debug.png Info.png Warm.png Error.png Assert.png json.png

CsvFile文件日志

代码部分
CsvFile代码部分.png
截图部分(文件保存在手机存储logger目录下)
CsvFile.png

源码分析

这些是 Logger 最基础的用法,同时还支持 xml 的打印。而且在GitHub上的README中有自定义参数的用法:

FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
    .showThreadInfo(false)  // (Optional) Whether to show thread info or not. Default true
    .methodCount(0)         // (Optional) How many method line to show. Default 2
    .methodOffset(7)        // (Optional) Hides internal method calls up to offset. Default 5
    .logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat
    .tag("My custom tag")   // (Optional) Global tag for every log. Default PRETTY_LOGGER
    .build();

Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));

不过,我们先从就从 v 方法开始分析:

Logger.java 部分代码
     private static Printer printer = new LoggerPrinter();

  public static void v(String message, Object... args) {
        printer.v(message, args);
  }
LoggerPrinter.java 部分代码
    private final ThreadLocal<String> localTag = new ThreadLocal<>();
    
    @Override 
    public Printer t(String tag) {
        if (tag != null) {
          localTag.set(tag);
        }
        return this;
    }
    
    @Override 
    public void v(String message, Object... args) {
        log(VERBOSE, null, message, args);
    }

    private synchronized void log(int priority, Throwable throwable, String msg, Object... args) {
        String tag = getTag();
        String message = createMessage(msg, args);
        log(priority, tag, message, throwable);
    }
    
    private String getTag() {
        String tag = localTag.get();
        if (tag != null) {
          localTag.remove();
          return tag;
        }
        return null;
    }
    
    @Override 
    public synchronized void log(int priority, String tag, String message, Throwable throwable) {
        if (throwable != null && message != null) {
          message += " : " + Utils.getStackTraceString(throwable);
        }
        if (throwable != null && message == null) {
          message = Utils.getStackTraceString(throwable);
        }
        if (Utils.isEmpty(message)) {
          message = "Empty/NULL log message";
        }
    
        for (LogAdapter adapter : logAdapters) {
          if (adapter.isLoggable(priority, tag)) {
            adapter.log(priority, tag, message);
          }
        }
    }
t方法使用.png

我们在README中可以看到两个实现类:

AndroidLogAdapterDiskLogAdapter

下面我们分别对它们进行分析

AndroidLogAdapter.java代码

public class AndroidLogAdapter implements LogAdapter {

  private final FormatStrategy formatStrategy;

  public AndroidLogAdapter() {
    this.formatStrategy = PrettyFormatStrategy.newBuilder().build();
  }

  public AndroidLogAdapter(FormatStrategy formatStrategy) {
    this.formatStrategy = formatStrategy;
  }

  @Override public boolean isLoggable(int priority, String tag) {
    return true;
  }

  @Override public void log(int priority, String tag, String message) {
    formatStrategy.log(priority, tag, message);
  }

}
PrettyFormatStrategy.java部分代码
  private final int methodCount;
  private final int methodOffset;
  private final boolean showThreadInfo;
  private final LogStrategy logStrategy;
  private final String tag;
    
@Override public void log(int priority, String onceOnlyTag, String message) {
    String tag = formatTag(onceOnlyTag);

    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);
}   

private String formatTag(String tag) {
    if (!Utils.isEmpty(tag) && !Utils.equals(this.tag, tag)) {
      return this.tag + "-" + tag;
    }
    return this.tag;
}

private void logContent(int logType, String tag, String chunk) {
    String[] lines = chunk.split(System.getProperty("line.separator"));
    for (String line : lines) {
      logChunk(logType, tag, HORIZONTAL_LINE + " " + line);
    }
}

private void logChunk(int priority, String tag, String chunk) {
    logStrategy.log(priority, tag, chunk);
}
LogcatLogStrategy.java
public class LogcatLogStrategy implements LogStrategy {

  @Override public void log(int priority, String tag, String message) {
    Log.println(priority, tag, message);
  }

}

DiskLogAdapter.java部分代码

public class DiskLogAdapter implements LogAdapter {

  private final FormatStrategy formatStrategy;

  public DiskLogAdapter() {
    formatStrategy = CsvFormatStrategy.newBuilder().build();
  }

  public DiskLogAdapter(FormatStrategy formatStrategy) {
    this.formatStrategy = formatStrategy;
  }

  @Override public boolean isLoggable(int priority, String tag) {
    return true;
  }

  @Override public void log(int priority, String tag, String message) {
    formatStrategy.log(priority, tag, message);
  }
}
CsvFormatStrategy.java部分代码
    private final Date date;
  private final SimpleDateFormat dateFormat;
  private final LogStrategy logStrategy;
  private final String tag;

  private CsvFormatStrategy(Builder builder) {
    date = builder.date;
    dateFormat = builder.dateFormat;
    logStrategy = builder.logStrategy;
    tag = builder.tag;
  }

  public static Builder newBuilder() {
    return new Builder();
  }

  @Override public void log(int priority, String onceOnlyTag, String message) {
    String tag = formatTag(onceOnlyTag);

    date.setTime(System.currentTimeMillis());

    StringBuilder builder = new StringBuilder();

    // machine-readable date/time
    builder.append(Long.toString(date.getTime()));

    // human-readable date/time
    builder.append(SEPARATOR);
    builder.append(dateFormat.format(date));

    // level
    builder.append(SEPARATOR);
    builder.append(Utils.logLevel(priority));

    // tag
    builder.append(SEPARATOR);
    builder.append(tag);

    // message
    if (message.contains(NEW_LINE)) {
      // a new line would break the CSV format, so we replace it here
      message = message.replaceAll(NEW_LINE, NEW_LINE_REPLACEMENT);
    }
    builder.append(SEPARATOR);
    builder.append(message);

    // new line
    builder.append(NEW_LINE);

    logStrategy.log(priority, tag, builder.toString());
  }
  
  public static final class Builder {
    .
    .
    .
    public CsvFormatStrategy build() {
      if (date == null) {
        date = new Date();
      }
      if (dateFormat == null) {
        dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS", Locale.UK);
      }
      if (logStrategy == null) {
        String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String folder = diskPath + File.separatorChar + "logger";

        HandlerThread ht = new HandlerThread("AndroidFileLogger." + folder);
        ht.start();
        Handler handler = new DiskLogStrategy.WriteHandler(ht.getLooper(), folder, MAX_BYTES);
        logStrategy = new DiskLogStrategy(handler);
      }
      return new CsvFormatStrategy(this);
    }
    
  }
DiskLogStrategy.java部分代码
private final Handler handler;

 public DiskLogStrategy(Handler handler) {
    this.handler = handler;
 }

 @Override public void log(int level, String tag, String message) {
 // do nothing on the calling thread, simply pass the tag/msg to the background thread
 handler.sendMessage(handler.obtainMessage(level, message));
}


static class WriteHandler extends Handler {

private final String folder;
private final int maxFileSize;

WriteHandler(Looper looper, String folder, int maxFileSize) {
  super(looper);
  this.folder = folder;
  this.maxFileSize = maxFileSize;
}

@SuppressWarnings("checkstyle:emptyblock")
@Override public void handleMessage(Message msg) {
  String content = (String) msg.obj;

  FileWriter fileWriter = null;
  File logFile = getLogFile(folder, "logs");

  try {
    fileWriter = new FileWriter(logFile, true);

    writeLog(fileWriter, content);

    fileWriter.flush();
    fileWriter.close();
  } catch (IOException e) {
    if (fileWriter != null) {
      try {
        fileWriter.flush();
        fileWriter.close();
      } catch (IOException e1) { /* fail silently */ }
    }
  }
}

/**
 * This is always called on a single background thread.
 * Implementing classes must ONLY write to the fileWriter and nothing more.
 * The abstract class takes care of everything else including close the stream and catching IOException
 *
 * @param fileWriter an instance of FileWriter already initialised to the correct file
 */
private void writeLog(FileWriter fileWriter, String content) throws IOException {
  fileWriter.append(content);
}

private File getLogFile(String folderName, String fileName) {
  .
  .
  .
  return newFile;
}

How it works

how_it_works.png

在GitHub上,官方给出了原理图,我们分析的方向也大致如此。注意:Printer与LogAdapter的关系为1对多

UML类图

Logger类图.png

上图为代码分析过程中使用 StarUML 画出的类图,截图过于模糊,原件 GitHub/KLogger/KLogger.mdj

上一篇下一篇

猜你喜欢

热点阅读