Github: logger 分析版本:fcbb21

Logger — Simple, pretty and powerful logger for android.

介绍

logger

特点

  • 线程信息(Thread information)
  • 类信息(Class information)
  • 方法信息(Method information)
  • Json 格式化(Pretty-print for json content)
  • 遇 ‘\n’ 分行(Pretty-print for new line “\n”)
  • 简洁易看的输出(Clean output)
  • 跳转代码(Jump to source)

使用

1
compile 'com.orhanobut:logger:1.15'

提供方法

  • 基础:
1
2
3
4
5
6
7
8
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);
  • 支持 String format :
1
Logger.d("hello %s", "world");
  • 支持 Array、Map、Set 和 List 格式:
1
2
3
4
Logger.d(list);
Logger.d(map);
Logger.d(set);
Logger.d(new String[]);
  • 设置所有的 log 输出的 tag :
1
Logger.init(YOUR_TAG);
  • 当前 log 的 tag:
1
Logger.t("mytag").d("hello");
  • Logger 的设置:

该方法只需要调用一次,可以放在 Application 中,所有的参数都是可选的。

1
2
3
4
5
6
7
8
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

  • 使用自定义的 log :
1
.logAdapter(new CustomLogAdapter())

源码

Logger.d("hello"); 入手开始看:

1
2
3
4
5
6
7
final class LoggerPrinter implements Printer {
private static Printer printer = new LoggerPrinter();

public static void d(Object object) {
printer.d(object);
}
}

跟到 Printer 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 中,使用的是 JSONObjectJSONArray 两个类,利用该方法的 toString() 方法,在调用 d() 方法输出。在处理 xml 中,使用的是 StreamSource ,同样用 d() 输出。

总结

在平时的开发的时候我们也会用到 Log,或许会将 Log 方法简单封装一下,比如 release 的时候不打出 Log 等。但是Logger 不仅仅只是封装,还处理了很多 Log 存在的缺陷,像 logger 这样处理 Log 日志真的是业界良心,后续也有很多开源库参照该库。