寫在前面
這篇文章也是我以前寫在CSDN上的文章...
可以這樣說,Log日志是除了debug外我們調(diào)試程序的全部了,但是在實(shí)際的開發(fā)中,系統(tǒng)原生的Log功能并不強(qiáng)大,它只能打印簡單的字符串,如果碰到JSON,MAP一類的特殊字符串它的打印效果將極其糟糕。
機(jī)緣巧合下,我有幸見在github上見到了一個功能很強(qiáng)大的LOG日志庫Logger,它功能強(qiáng)大,當(dāng)你使用它打印LOG日志時,它不僅能把普通的字符串打印出來,甚至能定位你打印的位置。并且它能直接將JSON字符串格式化并打印出來,省下了我們手動格式化JSON字符串的時間。
下面是它的打印效果圖
簡單Logger日志實(shí)現(xiàn)
好了,看完Logger的功能介紹和效果圖,想必你也很心動,也想要搞這樣一個Logger庫。
現(xiàn)在如果你想擁有這樣一個日志系統(tǒng),那么有兩個簡單的方法來供你選擇:
一、Clone 點(diǎn)擊我,然后關(guān)閉此窗口...
二、繼續(xù)往下看。
非常感謝你留下來繼續(xù)看我的廢話,現(xiàn)在讓我們一起研究Logger的主要功能吧!
JSON格式化部實(shí)現(xiàn)
當(dāng)我第一次使用Logger JSON格式化的功能時,我心情是悲傷的,因?yàn)樗屛覓仐壛伺惆槲叶嗄甑腏SON格式化網(wǎng)頁。本著無比悲痛的心情,有生以來我第一次極其認(rèn)真的查看了開源項(xiàng)目的代碼。
后來,看完代碼后的我,心情是崩潰的,原本以為JOSN格式化這么高達(dá)上的功能一定是很復(fù)雜的代碼實(shí)現(xiàn),后來發(fā)現(xiàn)尼瑪就一行代碼就能搞定,這是什么鬼啊,說好的復(fù)雜代碼呢??
/**
* 打印JSON
*
* @param jsonStr
*/
public static void j(String jsonStr) {
if (isDebug) {
String message;
try {
if (jsonStr.startsWith("{")) {
JSONObject jsonObject = new JSONObject(jsonStr);
message = jsonObject.toString(JSON_INDENT); //這個是核心方法
} else if (jsonStr.startsWith("[")) {
JSONArray jsonArray = new JSONArray(jsonStr);
message = jsonArray.toString(JSON_INDENT);
} else {
message = jsonStr;
}
} catch (JSONException e) {
message = jsonStr;
}
message = LINE_SEPARATOR + message;
String[] lines = message.split(LINE_SEPARATOR);
StringBuilder sb = new StringBuilder();
printLog(D, lines);//請不要關(guān)注這行,這是控制臺輸出的Log方法
}
}
以上就是JSON格式化的方法,想必你和我當(dāng)初一樣充滿了錯愕,我們每天都在用toString()、toString()...然后竟然不知道還有個toString(int xxxx)的重載方法!?。?!
是的,高大上的JSON格式化功能不需要你998行代碼,也不需要你99行代碼,沒錯,你沒看錯,它就只需要你一行toString(int xxxx),并且附帶贈送你xxxx個縮進(jìn)空格?。?/p>
代碼定位的實(shí)現(xiàn)
當(dāng)年,我還是個孩子的時候我就知道我很無知,后來當(dāng)我看了Logger的Json格式化代碼的實(shí)現(xiàn)后,我已經(jīng)對它不屑一顧。后來當(dāng)我看了代碼定位的功能后,我覺得我還是太年輕了...
/**
* 代碼定位
*
* @param type
*/
private static void printLocation(char type, String... msg) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int i = 0;
//獲取代碼所運(yùn)行的位置
for (StackTraceElement e : stack) {
String name = e.getClassName();
if (!name.equals(L.class.getName())) {
i++;
} else {
break;
}
}
//進(jìn)行方法位置偏移
i += 3;
//當(dāng)我能準(zhǔn)確獲取到I時,本部分已經(jīng)完結(jié),以下代碼都是廢話,請不要關(guān)注
String className = stack[i].getFileName();
String methodName = stack[i].getMethodName();
int lineNumber = stack[i].getLineNumber();
StringBuilder sb = new StringBuilder();
printHunk(type, HORIZONTAL_DOUBLE_LINE + " Location:");
sb.append(HORIZONTAL_DOUBLE_LINE)
.append(" (").append(className).append(":").append(lineNumber).append(")# ").append(methodName);
printHunk(type, sb.toString());
printHunk(type, msg == null || msg.length == 0 ? BOTTOM_BORDER : MIDDLE_BORDER);
}
注:上面并不是純粹的Logger源碼,那是我根據(jù)Logger的思路自己寫的代碼定位功能,事實(shí)上Logger源代碼那部分寫得很優(yōu)秀,但是你也懂的,優(yōu)秀也就意味著代碼不好看懂...
好吧,我承認(rèn),我當(dāng)初就是因?yàn)榭催@部分看得心煩,感覺很不爽,然后一怒之下,根據(jù)它的思路重新來寫的。。。
好吧,現(xiàn)在來所說代碼定位的原理。
如下圖所示:
我想,上面的圖你一定很熟悉,沒錯,那就是我們經(jīng)常用的debug 然后watch某個元素時候的界面。
在上面的圖片中,我們看到了一堆數(shù)組,其中圈紅色的部分是不是感覺有點(diǎn)特殊,因?yàn)榘⒉皇窍到y(tǒng)的包名,那些都是我們自己的類,自己的方法的元素。
現(xiàn)在,我想聰明的你已經(jīng)懂了,代碼定位的原理是通過Thread.currentThread().getStackTrace()方法獲取到所有的運(yùn)行元素,并根據(jù)獲取到的數(shù)組,進(jìn)行一定的偏移來準(zhǔn)確捕獲Looger( )被調(diào)用的真正位置。
根據(jù)JVM的運(yùn)行原理,每當(dāng)JVM執(zhí)行一個方法時,它都會把該方法壓到一個方法棧里面,根據(jù)棧的后入先出原則,我們可以知道,Logger( )被調(diào)用的位置一定是方法棧里面最先被壓入的函數(shù)的位置。也就是紅圈里面的最后一個元素。也就是我為什么要 i += 3的原因;
附上我自己寫的類L(Logger)的調(diào)用流程:
我們終于獲取到了代碼所在行的確切位置了,要在控制臺上實(shí)現(xiàn)代碼定位也就簡單多了。
根據(jù)獲取到的StackTraceElement元素,我們能通過api接口獲取到運(yùn)行類的類名及其該方法所被調(diào)用的位置,然后按照下面的格式進(jìn)行封裝,便能實(shí)現(xiàn)控制臺代碼的定位。
代碼定位格式:
(類名:代碼行)
只要按照上面的規(guī)則進(jìn)行組合類名和行,再通過系統(tǒng)提供的Log進(jìn)行輸出,便能通過控制臺直接跳轉(zhuǎn)到所在的代碼行。
打印Map
這個沒啥好說的...
/**
* 打印MAp
*/
public static void m(Map map) {
Set set = map.entrySet();
if (set.size() < 1) {
printLog(D, "[]");
return;
}
int i = 0;
String[] s = new String[set.size()];
for (Object aSet : set) {
Map.Entry entry = (Map.Entry) aSet;
s[i] = entry.getKey() + " = " + entry.getValue() + ",\n";
i++;
}
printLog(V, s);
}