安卓开发- Log日志工具类
定制日志工具类
Android开发中,经常会用到android.util.Log日志类来输出打印信息:
private static String TAG = "AppUtil";
public init(){android.util.Log.d(TAG, "init: start");
}
在调试某个单独的Java类时,可以直接使用Log类来进行调试,但是在一个大型应用的开发中,对于每个Java文件都定义一个TAG然后各自控制内部打印,这种做法是不可取的,主要原因如下:
- 在应用上线时,需要管控日志的输出级别,这样就需要在每个Java类中进行控制,工作量巨大,且不好管控;
- 某些情况下需要保存Log日志,如果log都由Java类内部打印输出,那就要在每个log的地方写入文件,这明显是不现实的;
- 在应用出问题需要排查log日志时,每个Java文件都是使用内部Tag,不好分辨哪些是本应用的日志;
针对上面的问题,可以通过定制日志工具类来解决:
使用统一的日志工具类
import android.util.Log;/*** 应用内的Java类统一使用该打印工具打印日志*/
public final class LogUtil {// true 表示打印调试信息,默认为falseprivate static boolean LOCAL_DBG_ON = false; private static String TAG = "XXLog";private LogUtil() {}/*** 设置是否打印DeBug日志,一般在Application中设置* @param localDbgOn 是否打印Bug日志*/public static void setLocalDbgOn(boolean localDbgOn) {LOCAL_DBG_ON = localDbgOn;}/*** 设置TAG* @param tag 日志TAG*/public static void setLogTag(String tag) {TAG = tag;}/*** 调试日志,后期停止打印* @param message 日志信息*/public static void debugInform(String message) {if (!LOCAL_DBG_ON) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + " (" + currentStack.getLineNumber() + " )::" +currentStack.getMethodName() + " - " + message;Log.d(TAG, strMsg);}/*** 普通日志打印,比如:不频繁的操作日志,开始、结束、重要过程等* @param message*/public static void inform(String message) {if (!LOCAL_DBG_ON) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + "(" + currentStack.getLineNumber() + ")::" +currentStack.getMethodName() + " - " + message;Log.i(TAG, strMsg);}/*** 异常日志打印* @param message 日志信息*/public static void error(String message) {if (!LOCAL_DBG_ON) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + "(" + currentStack.getLineNumber() + ")::" +currentStack.getMethodName() + " - " + message;Log.e(TAG, strMsg);}// ...其他类型日志
}
上面的例子定义了一个打印辅助类,通过设置LOCAL_DBG_ON属性值来控制打印的输出,这个开关可以定义系统属性来控制,或者在Api中设置开关,从而达到控制日志开关的效果:
// 由系统属性控制log日志开关
private void refreshLogSwitch() {boolean enabled = SystemProperties.getBoolean("persist.sys.myapp.debug.switch", false);LogUtil.setLocalDbgOn(enabled);
}// 由api控制log日志开关
private void setLogSwitch(boolean enabled) {LogUtil.setLocalDbgOn(enabled);
}
也可以动态调整Tag的内容:
public static void setLogTag(String tag) {TAG = tag;
}
关键实现如下:
public static void debugInform(String message) {if (!LOCAL_DBG_ON) {// LOCAL_DBG_ON属性为false,则不输出打印return;}// 获取调用者的信息StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];// 添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + " (" + currentStack.getLineNumber() + " )::" +currentStack.getMethodName() + " - " + message;Log.d(TAG, strMsg);
}
// 其他Java类调用:private void resetApp() {LogUtil.debugInform("testMessage")
}
由上面的代码可以看出,在外部调用LogUtil的日志方法,也可以将调用者的类名、方法名甚至是行数输出来,这样就可以做到在同一个应用内,统一管控日志的输出了。
上面的工具类还不能控制日志的等级,可以继续优化下。
控制日志等级
虽然在Log中可以使用Log.d()、Log.e()来区分不同等级的日志,但是在日志工具类中只是加了一个统一的开关,并没有明显地区分日志等级,我们可以按照下面的方式,给日志工具类加上日志等级控制功能:
// 定义几个int型的常量,作为日志等级的控制
private static int VERBOSE = 1;
private static int DEBUG = 2;
private static int INFO = 3;
private static int WARN =4;
private static int ERROR = 5;
private static int NOTHING = 6;
// 设置默认日志等级, 等级越高输出内容越少
private static int level = VERBOSE; // 提供方法给外部控制打印等级
// 或者可以使用一个系统属性来存储日志等级,方便通过adb控制:settings put system log_level 3
public static void setLogLevel(int logLevel) {level = logLevel;
}// 在输出打印的时候同时判断打印开关和日志等级
public static void debugInform(String message) {if (!LOCAL_DBG_ON || level > DEBUG) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + " (" + currentStack.getLineNumber() + " )::" +currentStack.getMethodName() + " - " + message;Log.d(TAG, strMsg);
}public static void inform(String message) {if (!LOCAL_DBG_ON || level > INFO) {return;}// ...
}
上面的注释也写得比较清楚:首先在工具类中定义一些日志等级常量(VERBOSE、DEBUG等),然后增加一个日志等级变量level,各个日志方法内部通过判断当前的日志等级变量来控制是否要输出打印,需要注意的是,各个日志等级需要和日志等级常量保存一致,如debug的需要判断DEBUG,inform的判断INFO:
public static void debugInform(String message) {if (!LOCAL_DBG_ON || level > DEBUG) {// 打印关闭或等级不满足要求,则直接returnreturn;}// ...Log.d(TAG, strMsg);
}public static void inform(String message) {if (!LOCAL_DBG_ON || level > INFO) {return;}// ...
}
日志等级变量level跟LOCAL_DBG_ON开关属性一样,可以保存在系统属性中,或者由api接口通过setLogLevel()方法来更改。
这样优化之后就可以同时控制打印开关和打印等级了。
输出日志文件功能
控制打印开关和打印等级功能有了,现在还差一个保存日志的功能,要保存日志也很简单,就是在输出日志的时候,将日志信息写入到指定文件中即可:
public static void debugInform(String message) {if (!LOCAL_DBG_ON || level > DEBUG) {return;}// ...Log.d(TAG, strMsg);// 保存日志writerLog(strMsg);
}/*** 路径 "/storage/emulated/0/appLog"* @param msg Log日志的内容*/
public static void writerLog(String msg) {// 保存到的文件路径final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath();FileWriter fileWriter;BufferedWriter bufferedWriter = null;try {// 创建文件夹File dir = new File(filePath, "appLog");if (!dir.exists()) {dir.mkdir();}// 创建文件File file = new File(dir, "myAppLog.txt");if (!file.exists()) {file.createNewFile();}// 写入日志文件fileWriter = new FileWriter(file, true);bufferedWriter = new BufferedWriter(fileWriter);bufferedWriter.write(getCurrentTime() + "---" + msg + "\n");bufferedWriter.close();} catch (Exception e) {e.printStackTrace();} finally {if (bufferedWriter != null) {try {bufferedWriter.close();} catch (IOException e) {e.printStackTrace();}}}
}/*** 获取当前时间* @author wm* @createTime 2023/10/9 20:27* @return : java.lang.String*/
private static String getCurrentTime() {Calendar calendar = Calendar.getInstance();@SuppressLint("SimpleDateFormat")SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf.format(calendar.getTime());
}
这里利用FileWriter跟BufferedWriter将Log日志信息写入文件中,为了方便排查,还加上了日志当前时间。到这里整个日志工具类就实现了。
完整日志工具类的示例
/*** 应用内的Java类统一使用该打印工具打印日志*/
public final class LogUtil {// true 表示打印调试信息,默认为falseprivate static boolean LOCAL_DBG_ON = false; private static String TAG = "XXLog";// 定义几个int型的常量,作为日志等级的控制private static int VERBOSE = 1; private static int DEBUG = 2; private static int INFO = 3; private static int WARN =4; private static int ERROR = 5; private static int NOTHING = 6; // 设置默认日志等级, 等级越高输出内容越少private static int level = VERBOSE; // 提供方法给外部控制打印等级// 或者可以使用一个系统属性来存储日志等级,方便通过adb控制:settings put system log_level 3public static void setLogLevel(int logLevel) {level = logLevel;}private LogUtil() {}/*** 设置是否打印DeBug日志,一般在Application中设置* @param localDbgOn 是否打印Bug日志*/public static void setLocalDbgOn(boolean localDbgOn) {LOCAL_DBG_ON = localDbgOn;}/*** 设置TAG* @param tag 日志TAG*/public static void setLogTag(String tag) {TAG = tag;}/*** 调试日志,后期停止打印* @param message 日志信息*/public static void debugInform(String message) {if (!LOCAL_DBG_ON || level > DEBUG) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + " (" + currentStack.getLineNumber() + " )::" +currentStack.getMethodName() + " - " + message;Log.d(TAG, strMsg);writerLog(strMsg);}/*** 普通日志打印,比如:不频繁的操作日志,开始、结束、重要过程等* @param message*/public static void inform(String message) {if (!LOCAL_DBG_ON || level > INFO) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + "(" + currentStack.getLineNumber() + ")::" +currentStack.getMethodName() + " - " + message;Log.i(TAG, strMsg);writerLog(strMsg);}/*** 异常日志打印* @param message 日志信息*/public static void error(String message) {if (!LOCAL_DBG_ON || level > ERROR) {return;}StackTraceElement[] stacks = new Throwable().getStackTrace();StackTraceElement currentStack = stacks[1];//添加类名、方法名和对应的行数String strMsg = currentStack.getFileName() + "(" + currentStack.getLineNumber() + ")::" +currentStack.getMethodName() + " - " + message;Log.e(TAG, strMsg);writerLog(strMsg);}// ...其他类型日志/*** 路径 "/storage/emulated/0/appLog"* @param msg Log日志的内容*/public static void writerLog(String msg) {// 保存到的文件路径final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath();FileWriter fileWriter;BufferedWriter bufferedWriter = null;try {// 创建文件夹File dir = new File(filePath, "appLog");if (!dir.exists()) {dir.mkdir();}// 创建文件File file = new File(dir, "myAppLog.txt");if (!file.exists()) {file.createNewFile();}// 写入日志文件fileWriter = new FileWriter(file, true);bufferedWriter = new BufferedWriter(fileWriter);bufferedWriter.write(getCurrentTime() + "---" + msg + "\n");bufferedWriter.close();} catch (Exception e) {e.printStackTrace();} finally {if (bufferedWriter != null) {try {bufferedWriter.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 获取当前时间* @author wm* @createTime 2023/10/9 20:27* @return : java.lang.String*/private static String getCurrentTime() {Calendar calendar = Calendar.getInstance();@SuppressLint("SimpleDateFormat")SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf.format(calendar.getTime());}
}