背景:在工业互联网项目开发过程中,当程序出现异常错误,由于条件限制,开发者往往无法及时获取到程序异常信息,于是需要将程序运行的状态以及内部参数完整的记录到本地,以便于开发调试。
为什么要写这篇博客:“com.orhanobut:logger”这个日志框架还是比较好用的,虽然网上有很多篇博客介绍了此框架如何打印日志并保存到本地磁盘,但打印出来的日志文件名称都是log_0.cvs、log_1.cvs ...... 等一些流水名称,在查找日志的时候非常非常的不方便,而且对于一个有强迫症的程序猿来说,不能自定义路径简直比SL我还要难受,于是在看了logger日志框架的源码后有了以下内容....
本文介绍:
1.)如何将日志保存到本地,且自定义日志输出路径;
2.)日志文件夹按天分类,便于查找、清理;
3.)降低日志框架与项目的耦合;
4.)复制就能直接用;
在module中的build.gradle的dependencies代码块中引入以下依赖
//日志工具
implementation 'com.orhanobut:logger:2.2.0'
1.)日志接口:ChjLog;简单概括这个接口,使用接口方式,降低项目对日志框架的依赖耦合
package com.chj.chjloghandle.logutil.inter;
/***
* 作者:chj233
* 时间:2022/7/7
* 描述:日志接口,需要更多方法可自行拓展...
*/
public interface ChjLog {
/**
* 日志工具初始化
* @param logPath 日志输出路径
*/
void init(String logPath);
/**
* debug日志
* @param msg
*/
void d(String msg);
/**
* 错误日志
* @param msg
*/
void e(String msg);
}
2.)日志处理类:ChjLogHandle ;简单概括这个类,设置日志打印策略,以及降低项目与日志框架的耦合
package com.chj.chjloghandle.logutil.handle;
import android.os.Handler;
import android.os.HandlerThread;
import com.chj.chjloghandle.logutil.inter.ChjLog;
import com.orhanobut.logger.CsvFormatStrategy;
import com.orhanobut.logger.DiskLogAdapter;
import com.orhanobut.logger.FormatStrategy;
import com.orhanobut.logger.LogStrategy;
import com.orhanobut.logger.Logger;
/**
*
* 功能描述:打印日志到本地
* 使用场景:
**/
public class ChjLogHandle implements ChjLog {
@Override
public void init(String logPath) {
HandlerThread ht = new HandlerThread("AndroidFileLogger." + logPath);
ht.start();
try {
//单个文件最大限制5M 超过则创建新文件记录日志
int maxFileSize = 5 * 1024 * 1024;
//日志打印线程
Handler cxHandle = new ChjCXWriteHandler(ht.getLooper(), logPath, maxFileSize);
//创建缓存策略
LogStrategy diskLogStrategy = new ChjCXDiskLogStrategy(cxHandle);
//构建格式策略
FormatStrategy strategy = CsvFormatStrategy.newBuilder().logStrategy(diskLogStrategy).build();
//创建适配器
DiskLogAdapter adapter = new DiskLogAdapter(strategy);
//设置日志适配器
Logger.addLogAdapter(adapter);
} catch (Exception e) {
e.printStackTrace();
ht.quit();//退出
}
}
/**
* 此处仅实现两个
* @param log
*/
@Override
public void d(String log) {
Logger.d(log);
}
@Override
public void e(String log) {
Logger.e(log);
}
}
2.)日志打印策略类:ChjCXDiskLogStrategy ;简单概括一下这个类,当日志框架触发log时,此类将日志内容传给 Handler 线程,由Handler 线程进行异步打印。
package com.chj.chjloghandle.logutil.handle;
import android.os.Handler;
import com.orhanobut.logger.DiskLogStrategy;
/**
*
* 功能描述:日志打印策略
* 使用场景:需要打印日志并输出到本地文件夹中
**/
public class ChjCXDiskLogStrategy extends DiskLogStrategy {
private Handler handler;
public ChjCXDiskLogStrategy(Handler handler) {
super(handler);
this.handler = handler;
}
@Override
public void log(int level, String tag, String message) {
// do nothing on the calling thread, simply pass the tag/msg to the background thread
handler.sendMessage(handler.obtainMessage(level, message));
}
}
3.) 日志打印线程:ChjCXWriteHandler ;当日志打印策略发送日志内容时,此类会接收到日志,并打印日志,也是打印日志的关键代码。
package com.chj.chjloghandle.logutil.handle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 功能描述:日志写入类
**/
public class ChjCXWriteHandler extends Handler {
private String folder;//日志存储路径
private int maxFileSize;//单个日志最大占用内存
private final SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd");
public ChjCXWriteHandler(Looper looper, String folder, int maxFileSize) {
super(looper);
this.folder = folder;
this.maxFileSize = maxFileSize;
}
@SuppressWarnings("checkstyle:emptyblock")
@Override
public void handleMessage(Message msg) {
String content = (String) msg.obj;
FileWriter fileWriter = null;
File logFile = getLogFile(folder, "logs");
try {
fileWriter = new FileWriter(logFile, true);
writeLog(fileWriter, content);
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
handleException(fileWriter);
}
}
private void handleException(FileWriter fileWriter) {
if (fileWriter == null) return;
try {
fileWriter.flush();
fileWriter.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
/**
* This is always called on a single background thread.
* Implementing classes must ONLY write to the fileWriter and nothing more.
* The abstract class takes care of everything else including close the stream and catching IOException
*
* @param fileWriter an instance of FileWriter already initialised to the correct file
*/
private void writeLog(FileWriter fileWriter, String content) throws IOException {
fileWriter.append(content);
}
private File getLogFile(String folderName, String fileName) {
folderName = folderName + "/" + getTime();
fileName = fileName + "-" + getTime();
File folder = new File(folderName);
if (!folder.exists()) folder.mkdirs();
int newFileCount = 0;
File existingFile = null;
File newFile = new File(folder, String.format("%s-%s.csv", fileName, newFileCount));
while (newFile.exists()) {
existingFile = newFile;
newFileCount++;
newFile = new File(folder, String.format("%s-%s.csv", fileName, newFileCount));
}
if (existingFile == null) return newFile;
if (existingFile.length() >= maxFileSize) return newFile;
return existingFile;
}
private String getTime() {
return sdfTime.format(new Date());
}
}
4.)日志管理类: MyLog ;为了方便上次调用此处做控制反转。
package com.chj.chjloghandle.logutil;
import android.os.Environment;
import android.util.Log;
import com.chj.chjloghandle.logutil.handle.ChjLogHandle;
import com.chj.chjloghandle.logutil.inter.ChjLog;
/***
* 作者:chj233
* 时间:2022/7/7
* 描述:
*/
public class MyLog {
//使用接口 避免耦合 后续若要更换日志打印框架 直接new 其它实现类即可
private static ChjLog log = new ChjLogHandle();
/**
* 日志初始化
*/
public static void init(){
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ChjLogs";
log.init(path);
d("日志初始化成功!");
}
/**
* debug日志
* @param str
*/
public static void d(String str){
log.d(str);
Log.d(MyLog.class.getName(),str);
}
/**
* 错误日志
* @param str
*/
public static void e(String str){
log.e(str);
Log.e(MyLog.class.getName(),str);
}
}
1.) 使用注意事项:注意使用之前一定要先授权,让APP可操作内存
package com.chj.chjloghandle;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.chj.chjloghandle.logutil.MyLog;
/***
* 作者:chj233
* 时间:2022/7/7
* 描述:
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注意 使用之前 需要用户允许操作SD卡权限
MyLog.init();//日志初始化 推荐在Application的onCreate中初始化
setContentView(R.layout.activity_main);
findViewById(R.id.test).setOnClickListener((view) -> {//萌新友好提示 java8的写法
MyLog.d("用户点击类按钮.");
});
findViewById(R.id.test1).setOnClickListener((view) -> {
new Thread(() -> {// 萌新友好提示 java8的写法
for (int i = 0; i < 10; i++){
MyLog.d("线程循环...." + i);
}
}).start();
});
}
}
此日志框架使用 Handler 机制,保证多线程下日志输出顺序不会乱。demo使用策略设计模式,降低代码的耦合程度。