1 前言
我们在实际开发中日志这个功能时必不可少的,有些是用原生的android.util.Log,或者使用网上开源的第三方框架例如Logger等,不可否认,第三方框架相对于原生的功能增强了不少。但是这里,我想在原生的基础上封装一个非常简单并且实用的日志框架。请看下面的介绍
2 Log日志框架的主要功能
要封装这个日志框架,首先要知道我们需要什么,要把这个框架做成什么样子。经过思考,我打算把日志框架做成如下样子
1 日志记录功能,能记录各种日志,包括各种级别VERBOSE,DEBUG, INFO, WARN, ERROR,如果调整级别,将相应的隐藏日志的打印
2 日志提供打印到文件中的功能,每个进程打印一个文件,可以为特定的日志指定单独打印到一个文件中
3 根据APP是Debug还是Release 状态自动的开启关闭日志打印功能
4 提供日志打开关闭的开关
5 日志文件可上传至指定的服务器
6 日志打印到文件中格式必须记录 那个文件(那个类), 那个方法,多少行等信息
为什么要记录那个文件,那个方法,多少行呢,因为当外场问题发生时,我们往往无法拿到客户的手机,进行问题的复现,这个时候定位问题只能靠日志了,因此我们的日志要尽量记录有用的信息
基于此,我们思考一下,我们的日志功能该怎么设计呢?请看下面
3 Log日志框架的设计与实现
首先,我们设计的框架API使用起来要方便,最好有一个类来统一进行管理,这里我就交给LogManager了。我们的API也尽量设计成静态方法,因为Log的使用是在是太多,如果不设置成静态,势必要产生大量的对象,也不利于管理,也消耗内存。另外,我们将API的实现和API分开。基于此,我们可以设计如下的类图:
说明如下:
1 LogManager之所以设计成Map< String,LogImpl >类型,是打算在日志打印到文件中是,同一个进程也可以打印多个日志文件,例如,对于一些重要的,复杂的逻辑流程,我们可以将它单独打印到某个文件中,没有必要和主进程的日志夹杂在一起,例如笔者的项目中用到了SIP协议,这其中的一些协议交互逻辑,如果混在主进程则不好定位,放在单独的一个文件中就很好定位。
2 LogConstant的主要作用就是来存放使用的常量的,例如,目前的截图如下:
3 LogImpl主要为日志打印的实现,目前耦合性稍微重了一点,后续视情况拆分
4 LogImpl中isOpen,isWriteFile分别用来区分是否打开日志开关,是否打印到文件中,相关核心代码如下:
/**
* 日志开关是否打开
*/
private boolean isOpen;
/**
* 是否写入文件
*/
private boolean isWriteFile;
/**
* 打印日志
* @param level
* @param tag
* @param msg
* @return
*/
public int print(int level,String tag, String msg){
//如果是非调试状态,直接不打印,返回
if (!AndroidUtil.isDebug(RuntimeEnv.appContext) && !isOpen){
return 0;
}
Log.d(LogConstant.TAG,"log print --> level:" + level);
//大于等于该级别才会打印
if (level >= mLevel){
if (isWriteFile){
//打印到文件 只有设置了为true才会打印到文件
printToFile(level,tag,msg);
}
//打印到控制台
printToConsole(level,tag, msg);
}
return 0;
}
5 printToFile(),printToConsole()分别为打印到文件中,打印到Android Studio 终端中。printToConsole()直接调用android.util.Log实现打印。
6 日志打印到文件头中,打印头的处理。我这里的格式如下:
2017-08-28 19:45:14.894 D 15655|15655[SkinManager.java->loadSkin() 94][SkinManager]path --> /storage/emulated/0/red.skin
日志头为:
2017-08-28 19:45:14.894 D 15655|15655[SkinManager.java->loadSkin() 94]
其中格式为:时间 + 级别 + 进程ID | 线程ID + [文件名(类名) ->方法名 行数] + [TAG] + message
响应的代码如下:
/**
* 打印到日志文件中
* @param level
* @param tag
* @param msg
*/
private void printToFile(int level, String tag, String msg){
// 时间格式 2017-8-26 23.22.23.445
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String time = sf.format(new Date());
String preTAG = V;
switch (level) {
case LogConstant.VERBOSE:
preTAG = V;
break;
case LogConstant.DEBUG:
preTAG = D;
break;
case LogConstant.INFO:
preTAG = I;
break;
case LogConstant.WARN:
preTAG = W;
break;
case LogConstant.ERROR:
preTAG = E;
break;
case LogConstant.ASSERT:
preTAG = A;
break;
default:
break;
}
//打印进程ID 线程ID 当前类 当前方法
String message = time + " " + preTAG + " "
+ ""+ android.os.Process.myPid() + "|" +""+ android.os.Process.myTid()
+ "[" + RuntimeEnv.getCurrentFileName() + "->" + RuntimeEnv.getCurrentMethodName()+"]"
+ "[" + tag +"]" + msg;
printMessage(message);
}
RuntimeEnv.java中
/***
* 获取当前运行的类的方法 和行数
* @return
*/
public static String getCurrentMethodName() {
StackTraceElement element = getCallLogManagerStackTrace();
if (element != null){
String methodName = element.getMethodName();
int lineNumber = element.getLineNumber();
return methodName + "() " + lineNumber;
}
return null;
}
/**
* 获取当前运行的Class
* @return
*/
public static String getCurrentClassName() {
StackTraceElement element = getCallLogManagerStackTrace();
if (element != null){
String clazz = element.getClassName();
//去最后一个即 类的简名
if (clazz.contains(".")){
String strArray[] = clazz.split("\\.");
clazz = strArray[strArray.length -1];
}
return clazz;
}
return null;
}
/**
* 获取当前运行的Class
* @return
*/
public static String getCurrentFileName() {
StackTraceElement element = getCallLogManagerStackTrace();
if (element != null){
String fileName = element.getFileName();
return fileName;
}
return null;
}
基于此,一个简单的日志框架就搭建完成了,后续可以根据项目实际情况添加功能,或者进行优化。详细的代码可以参考我的github
https://github.com/qiyei2015/EssayJoke 中SDK 下的log目录
下面贴一个打印到文件中的截图: