平时为了方便测试和定位错误(特别是demo工具时),又不想依赖日志框架,习惯使用System.out.println()
,但这种知识简单输出文本,而且打印异常时不好定位,对打印不同级别的日志也不能满足需求。本文对System.out.println()
进行简单封装,轻量级调用。
以前这样打印日志:
public static void main(String[] args) {
testLogPrintBefore();
}
private static void testLogPrintBefore() {
System.out.println("hello world!");
try {
LogTest logTest = null;
logTest.clone();
} catch (Exception e) {
System.out.println("发生了一个错:" + e.getMessage());
}
}
输出:
hello world!
发生了一个错:null
可以看出非常不直观,只是简单输出文本,定位错误不好。
使用ConsoleUtils
之后:
public static void main(String[] args) {
testLogPrintAfter();
}
private static void testLogPrintAfter() {
ConsoleUtils.d("hello world!");
ConsoleUtils.i("hello world!");
ConsoleUtils.w("hello world!");
try {
LogTest logTest = null;
logTest.clone();
} catch (Exception e) {
ConsoleUtils.w("发生了一个错误", e);
}
ConsoleUtils.e("hello world!");
ConsoleUtils.e("hello world!", new RuntimeException("发生了一个错误"));
try {
Class.forName("java.util.Test");
} catch (ClassNotFoundException ignored) {
ConsoleUtils.e("hello world!", ignored);
}
}
输出:
2021-08-12 17:20:587/D:testLogPrintAfter(LogTest.java:24)hello world! ---->Thread:main
2021-08-12 17:20:605/I:testLogPrintAfter(LogTest.java:25)hello world! ---->Thread:main
2021-08-12 17:20:605/W:testLogPrintAfter(LogTest.java:26)hello world! ---->Thread:main
2021-08-12 17:20:606/W:testLogPrintAfter(LogTest.java:32)发生了一个错误 ---->Thread:main
java.lang.NullPointerException
at com.sjl.socket.demo.LogTest.testLogPrintAfter(LogTest.java:30)
at com.sjl.socket.demo.LogTest.main(LogTest.java:18)
2021-08-12 17:20:606/E:testLogPrintAfter(LogTest.java:35)hello world! ---->Thread:main
2021-08-12 17:20:606/E:testLogPrintAfter(LogTest.java:36)hello world! ---->Thread:main
java.lang.RuntimeException: 发生了一个错误
at com.sjl.socket.demo.LogTest.testLogPrintAfter(LogTest.java:36)
at com.sjl.socket.demo.LogTest.main(LogTest.java:18)
2021-08-12 17:20:607/E:testLogPrintAfter(LogTest.java:40)hello world! ---->Thread:main
java.lang.ClassNotFoundException: java.util.Test
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.sjl.socket.demo.LogTest.testLogPrintAfter(LogTest.java:38)
at com.sjl.socket.demo.LogTest.main(LogTest.java:18)
上面虽然信息多了,日志定位信息时非常明了,输出了日志时间、日志级别、调用方法,定位行、描述信息,非常适合平时做一些测试之类工作的日志打印
ConsoleUtils
是基于System.out.println
封装,不同级别日志没有特别明显的颜色区分。这里介绍一个辅助插件,Grep Console是一款和IDEA Console相关的插件(当然也适用Android)。可以通过expression表达式过滤日志、给不同级别的日志或者给不同pattern的日志加上背景色与前景色。
Grep Console可以IDEA Setting->Plugin界面,搜索Grep安装即可
上面使用Grep Console插件后,输出效果如下:
可见,有颜色之后,区分非常明了
Grep Console配置截图:
最后国际规则,贴上ConsoleUtils
的完整代码:
/**
* 控制台日志工具类
*
* @author Kelly
* @version 1.0.0
* @filename ConsoleUtils
* @time 2021/8/11 11:24
* @copyright(C) 2021 song
*/
public class ConsoleUtils {
private static final int LOG_DEBUG = 1;
private static final int LOG_INFO = 2;
private static final int LOG_WARN = 3;
private static final int LOG_ERROR = 4;
private static final int PLATFORM_ANDROID = 0;
private static final int PLATFORM_PC = 1;
/**
* 是否显示日志
*/
private static boolean debug = true;
public static void d(Object msg) {
printLog(LOG_DEBUG, msg, null);
}
public static void i(Object msg) {
printLog(LOG_INFO, msg, null);
}
public static void w(Object msg) {
printLog(LOG_WARN, msg, null);
}
public static void w(Object msg, Throwable tr) {
printLog(LOG_WARN, msg, tr);
}
public static void e(Object msg) {
printLog(LOG_ERROR, msg, null);
}
public static void e(Object msg, Throwable tr) {
printLog(LOG_ERROR, msg, tr);
}
/**
* 打印日志
*
* @param type
* @param msgObj
* @param tr
* @return
*/
private static int printLog(int type, Object msgObj, Throwable tr) {
if (!debug) {
return -1;
}
String level = "--";
switch (type) {
case LOG_DEBUG:
level = "D";
break;
case LOG_INFO:
level = "I";
break;
case LOG_WARN:
level = "W";
break;
case LOG_ERROR:
level = "E";
break;
}
int platformFlag = checkPlatform();
String content = createLog(msgObj.toString(), platformFlag) + (tr != null ? ('\n' + getStackTraceString(tr)) : "");
StringBuilder sb = new StringBuilder();
String formatDate = getFormatTime();
if (platformFlag == PLATFORM_ANDROID) {
sb.append(content);
} else {
sb.append(formatDate).append("/").append(level).append(":").append(content);
}
System.out.println(sb.toString());
return 0;
}
/**
* 创建日志定位信息
*
* @param msg
* @param platformFlag
* @return
*/
private static String createLog(String msg, int platformFlag) {
StringBuilder builder = new StringBuilder();
try {
Thread thread = Thread.currentThread();
int stackTraceIndex;
if (platformFlag == PLATFORM_ANDROID) { //这里根据把ConsoleUtils所在的位置确认,可以打个断点确认
stackTraceIndex = 5;
} else {
stackTraceIndex = 4;
}
StackTraceElement[] stackTrace = thread.getStackTrace();
String className = stackTrace[stackTraceIndex].getFileName();
String methodName = stackTrace[stackTraceIndex].getMethodName();
int lineNumber = stackTrace[stackTraceIndex].getLineNumber();
builder.append(methodName);
builder.append("(").append(className).append(":").append(lineNumber).append(")");
builder.append(msg);
builder.append(" ---->").append("Thread:").append(thread.getName());
} catch (Exception e) {
e.printStackTrace();
}
return builder.toString();
}
private static int checkPlatform() {
try {
Class.forName("android.os.Build");
return PLATFORM_ANDROID;
} catch (ClassNotFoundException ignored) {
return PLATFORM_PC;
}
}
/**
* 获取日志异常栈信息
*
* @param tr
* @return
*/
public static String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, false);
tr.printStackTrace(pw);
pw.flush();
pw.close();
return sw.toString();
}
private static String getFormatTime() {
Date date = new Date();
String strDateFormat = "yyyy-MM-dd mm:ss:SSS";
SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
return sdf.format(date);
}
}