Github: logger 分析版本: fcbb21
Logger — Simple, pretty and powerful logger for android.
compile 'com.orhanobut:logger:1.15'
Logger.d("hello");
Logger.e("hello");
Logger.w("hello");
Logger.v("hello");
Logger.wtf("hello");
Logger.json(JSON_CONTENT);
Logger.xml(XML_CONTENT);
Logger.log(DEBUG, "tag", "message", throwable);
Logger.d("hello %s", "world");
Logger.d(list);
Logger.d(map);
Logger.d(set);
Logger.d(new String[]);
Logger.init(YOUR_TAG);
Logger.t("mytag").d("hello");
该方法只需要调用一次,可以放在 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
}
打 release 包的时候使用 LogLevel.NONE
。
.logAdapter(new CustomLogAdapter())
从 Logger.d("hello");
入手开始看:
final class LoggerPrinter implements Printer {
private static Printer printer = new LoggerPrinter();
public static void d(Object object) {
printer.d(object);
}
}
跟到 Printer
中:
final class LoggerPrinter implements Printer {
/**
* Android's max limit for a log entry is ~4076 bytes,
* so 4000 bytes is used as chunk size since default charset
* is UTF-8
*/
private static final int CHUNK_SIZE = 4000;
/**
* The minimum stack trace index, starts at this class after two native calls.
*/
private static final int MIN_STACK_OFFSET = 3;
/**
* tag is used for the Log, the name is a little different
* in order to differentiate the logs easily with the filter
*/
private String tag;
private final Settings settings = new Settings();
private final ThreadLocal<String> localTag = new ThreadLocal<>();//该变量每个线程维护的有一份,防止多线程并发的时候打出tag出问题
@Override
public void d(Object object) {
String message;
if (object.getClass().isArray()) {//判断是否是数组
message = Arrays.deepToString((Object[]) object);//如果是的话,调用Arrays的方法
} else {
message = object.toString();
}
log(DEBUG, null, message);
}
/**
* This method is synchronized in order to avoid messy of logs' order.
*/
private synchronized void log(int priority, Throwable throwable, String msg, Object... args) {
if (settings.getLogLevel() == LogLevel.NONE) {//为NONE的就不继续了
return;
}
String tag = getTag();//得到tag
String message = createMessage(msg, args);//组装message
log(priority, tag, message, throwable);
}
/**
* @return the appropriate tag based on local or global
*/
private String getTag() {
String tag = localTag.get();
if (tag != null) {
localTag.remove();
return tag;
}
return this.tag;
}
private String createMessage(String message, Object... args) {
return args == null || args.length == 0 ? message : String.format(message, args);
}
@Override
public synchronized void log(int priority, String tag, String message, Throwable throwable) {
if (settings.getLogLevel() == LogLevel.NONE) {//当为NONE的时候不输出
return;
}
if (throwable != null && message != null) {//如果有throwable,组装到message中
message += " : " + Helper.getStackTraceString(throwable);
}
if (throwable != null && message == null) {//如果有throwable,组装到message中
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();//计算message长度
int length = bytes.length;
if (length <= CHUNK_SIZE) {//log中一行显示4000,当小于4000的时候就去一行显示
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);
}
@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());//输出
}
}
/**
* 通过chunk界别来通过LogAdapter输出
*
* @param logType
* @param tag
* @param chunk
*/
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;
}
}
/**
* 显示分割线
*
* @param logType
* @param tag
*/
private void logDivider(int logType, String tag) {
logChunk(logType, tag, MIDDLE_BORDER);
}
//----------------------- logHeaderContent begin -----------------------
/**
* Determines the starting index of the stack trace, after method calls made by this class.
* 找调用堆栈中最外层调用的index
*
* @param trace the stack trace
* @return the stack offset
*/
private int getStackOffset(StackTraceElement[] trace) {
for (int i = MIN_STACK_OFFSET; i < trace.length; i++) {//MIN_STACK_OFFSET为3,一般3返回的是最外层调用方法的地方
StackTraceElement e = trace[i];
String name = e.getClassName();
if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) {
return --i;//当name不为『LoggerPrinter』或者『Logger』的时候,接下去的就是调用该方法堆栈的信息了
}
}
return -1;
}
private String getSimpleClassName(String name) {
int lastIndex = name.lastIndexOf(".");
return name.substring(lastIndex + 1);
}
private String formatTag(String tag) {
if (!Helper.isEmpty(tag) && !Helper.equals(this.tag, tag)) {
return this.tag + "-" + tag;
}
return this.tag;
}
/**
* 如果对该条log设置了tag,那么进行tag处理
*
* @param tag
* @return
*/
private String formatTag(String tag) {
if (!Helper.isEmpty(tag) && !Helper.equals(this.tag, tag)) {
return this.tag + "-" + tag;
}
return this.tag;
}
//----------------------- logHeaderContent end -----------------------
/**
* 分行去显示内容
*
* @param logType
* @param tag
* @param chunk
*/
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_DOUBLE_LINE + " " + line);
}
}
private void logBottomBorder(int logType, String tag) {
logChunk(logType, tag, BOTTOM_BORDER);
}
}
当满足 (文件名字:行数)
的时候,便可点击并跳转到该位置。 以及其他的 v()
、 i()
等等都相似。
那么现在来看看 json 和 xml :
final class LoggerPrinter implements Printer {
/**
* Formats the json content and print it
*
* @param json the json content
*/
@Override
public void json(String json) {
if (Helper.isEmpty(json)) {
d("Empty/Null json content");
return;
}
try {
json = json.trim();
if (json.startsWith("{")) {//JsonObject
JSONObject jsonObject = new JSONObject(json);
String message = jsonObject.toString(JSON_INDENT);//转成String
d(message);
return;
}
if (json.startsWith("[")) {//JsonArray
JSONArray jsonArray = new JSONArray(json);
String message = jsonArray.toString(JSON_INDENT);//转成String
d(message);
return;
}
e("Invalid Json");
} catch (JSONException e) {
e("Invalid Json");
}
}
/**
* Formats the json content and print it
*
* @param xml the xml content
*/
@Override
public void xml(String xml) {
if (Helper.isEmpty(xml)) {
d("Empty/Null xml content");
return;
}
try {
Source xmlInput = new StreamSource(new StringReader(xml));
StreamResult xmlOutput = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(xmlInput, xmlOutput);
d(xmlOutput.getWriter().toString().replaceFirst(">", ">/n"));
} catch (TransformerException e) {
e("Invalid xml");
}
}
}
在处理 json 中,使用的是 JSONObject
和 JSONArray
两个类,利用该方法的 toString()
方法,在调用 d()
方法输出。在处理 xml 中,使用的是 StreamSource
,同样用 d()
输出。
在平时的开发的时候我们也会用到 Log,或许会将 Log 方法简单封装一下,比如 release 的时候不打出 Log 等。但是Logger 不仅仅只是封装,还处理了很多 Log 存在的缺陷,像 logger 这样处理 Log 日志真的是业界良心,后续也有很多开源库参照该库。