对于 Verbose 日志,一般没什么有用信息,可以只进行控制台输出。(Debug 日志也可以一样处理)
代码如下:
public class LogUtil {
private static String TAG = "LogUtil";
/**
* 对外 v 日志接口
* 只进行控制台输出
*/
public static void v(String tag, String msg) {
printLog(Log.VERBOSE, tag, msg);
}
private static void printLog(int logLevel, String TAG, String msg) {
printLog(logLevel, TAG, msg);
}
/**
* 将日志输入到控制台
* 可以在这里对控制台的日志输出进行通用性的控制
*/
private static void printLog(int logLevel, String tag, String msg, Throwable e) {
if (TextUtils.isEmpty(msg)) {
return;
}
if (tag == null) {
tag = "";
}
// 只有在测试环境下才会将日志输出到控制台
if (isDebug()) {
StringBuffer stringBuffer = new StringBuffer();
// 添加主线程标志
if (Looper.myLooper() == Looper.getMainLooper() && TextUtils.equals(Thread.currentThread().getName(), "main")) {
stringBuffer.append("[main]");
}
if (!TextUtils.isEmpty(tag)) {
stringBuffer.append(tag).append(" ");
}
stringBuffer.append(msg);
if (logLevel == Log.VERBOSE) {
stringBuffer.insert(0, "[V]");
String msgInfo = stringBuffer.toString();
Log.i(TAG, msgInfo);
} else if (logLevel == Log.DEBUG) {
stringBuffer.insert(0, "[D]");
String msgInfo = stringBuffer.toString();
Log.d(TAG, msgInfo);
} else if (logLevel == Log.INFO) {
stringBuffer.insert(0, "[I]");
String msgInfo = stringBuffer.toString();
Log.w(TAG, msgInfo);
} else if (logLevel == Log.WARN) {
stringBuffer.insert(0, "[W]");
String msgInfo = stringBuffer.toString();
Log.w(TAG, msgInfo);
} else if (logLevel == Log.ERROR) {
stringBuffer.insert(0, "[E]");
String msgInfo = stringBuffer.toString();
Log.e(TAG, msgInfo);
} else {
stringBuffer.insert(0, "[I]");
String msgInfo = stringBuffer.toString();
Log.i(TAG, msgInfo);
}
}
}
private static boolean isDebug() {
return false;
}
}
对于 INFO 及以上等级的日志,我们需要考虑保存到日志文件中
public class LogUtil {
private static String TAG = "LogUtil";
public static void i(String tag, String msg) {
printAndSaveLog(Log.INFO, tag, msg);
}
private static void printAndSaveLog(int logLevel, String tag, String msg) {
String messageInfo = "";
printLog(logLevel, tag, msg);
saveLog(tag, logLevel, msg, false);
}
/**
* 保存日志到文件
* @param isNowSave 是否马上保存
*/
private static void saveLog(String tag, int logLevel, String msgInfo, boolean isNowSave) {
if (TextUtils.isEmpty(msgInfo)) {
return;
}
msgInfo = "[" + tag + "]" + msgInfo;
if (isNowSave) {
LogWriterProxy.writeLogNow(logLevel, msgInfo);
} else {
LogWriterProxy.writeLog(logLevel, msgInfo);
}
}
}
LogWriterProxy 是一个代理类,实际真正的操作交给实现了 ILogWriter 接口的对象去做。
public class LogWriterProxy {
private static ILogWriter mLogWriter = null;
public static void setLogWriter(ILogWriter logWriter) {
mLogWriter = logWriter;
}
/**
* 立即保存所有日志
* 一般用于 Crash 日志保存
*/
public static void writeLogNow(int logLevel, String msgInfo) {
if (mLogWriter != null) {
mLogWriter.writeLogNow(logLevel, msgInfo);
}
}
/**
* 一般普通日志保存都不是即时的
*/
public static void writeLog(int logLevel, String msgInfo) {
if (mLogWriter != null) {
mLogWriter.writeLog(logLevel, msgInfo);
}
}
public static void release() {
if (mLogWriter != null) {
mLogWriter.release();
}
}
}
public interface ILogWriter {
void writeLogNow(int logLevel, String msgInfo);
void writeLog(int logLevel, String msgInfo);
void release();
}
定义一个 LogWriter 类实现 ILogWriter
public class LogWriter implements ILogWriter{
private static volatile LogWriter mInstance = null;
private IWriteStrategy mWriteStrategy;
public static LogWriter getInstance() {
if (mInstance == null) {
synchronized (LogWriter.class) {
if (mInstance == null) {
mInstance = new LogWriter();
}
}
}
return mInstance;
}
private LogWriter() {
mWriteStrategy = new JavaWriteStrategy();
mWriteStrategy.initFilePath(LogPath.CACHE_LOG_FILE_PATH);
}
@Override
public void writeLogNow(int logLevel, String msgInfo) {
writeLog(logLevel, msgInfo, true);
}
@Override
public void writeLog(int logLevel, String msgInfo) {
writeLog(logLevel, msgInfo, false);
}
private void writeLog(int logLevel, String msgInfo, boolean isNow) {
if (TextUtils.isEmpty(msgInfo) || msgInfo.length() > 5000) {
return;
}
LogLineInfo logLineInfo = new LogLineInfo.Builder()
.setLevel(logLevel)
.setTime(System.currentTimeMillis())
.setInfo(msgInfo)
.build();
write(isNow, logLineInfo);
}
private void write(boolean isNow, LogLineInfo logLineInfo) {
mWriteStrategy.write(isNow, logLineInfo);
}
@Override
public void release() {
mWriteStrategy.release();
}
}
定义一个 JavaWriteStrategy 实现 IWritesStrategy。
public class JavaWriteStrategy implements IWriteStrategy {
private HandlerThread mHandlerThread = null;
private Handler mHandler = null;
private String mLogFileDirPath = ""; // 日志文件目录
private ConcurrentLinkedQueue<LogLineInfo> mLogList = null;
private Object mWriteLockObj = new Object();
/**
* 内存中日志最大数目
*/
private static final int MAX_RAM_LOG_COUNT = 20;
/**
* 内存中日志检查时间间隔
*/
private static final int MAX_CHECK_GAP_TIME = 5 * 1000;
private static final int MSG_CHECK_WRITE_LOG_ACTION = 0x1001;
private static final int MSG_NO_CHECK_WRITE_LOG_ACTION = 0x1002;
public JavaWriteStrategy() {
// 初始化工作线程和 handler
mHandlerThread = new HandlerThread("LogWriterThread");
mHandlerThread.start();
mHandler = new WriteLogHandler(mHandlerThread.getLooper());
}
@Override
public void initFilePath(String logFileDirPath) {
this.mLogFileDirPath = logFileDirPath;
}
@Override
public void write(boolean isNow, LogLineInfo logLineInfo) {
if (isNow) {
// 直接在主线程触发写入磁盘操作
synchronized (mWriteLockObj) {
ConcurrentLinkedQueue<LogLineInfo> logList = null;
if (mLogList != null && mLogList.size() > 0) {
logList = new ConcurrentLinkedQueue<>(mLogList);
mLogList.clear();
} else {
logList = new ConcurrentLinkedQueue<>();
}
logList.add(logLineInfo);
writeLog(logList);
}
} else {
// 检查内存日志数量后,满足条件再触发写入磁盘操作
if (mHandler == null || !mHandlerThread.isAlive()) {
return;
}
Message msg = mHandler.obtainMessage();
msg.what = MSG_CHECK_WRITE_LOG_ACTION;
msg.obj = logLineInfo;
mHandler.sendMessage(msg);
}
}
@Override
public void release() {
}
private class WriteLogHandler extends Handler {
public WriteLogHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
int what = msg.what;
Object object = msg.obj;
mHandler.removeMessages(MSG_NO_CHECK_WRITE_LOG_ACTION);
// 检查内存日志条数,满足条件就写入磁盘
if (what == MSG_CHECK_WRITE_LOG_ACTION) {
synchronized (mWriteLockObj) {
if (mLogList == null) {
mLogList = new ConcurrentLinkedQueue<>();
}
if (object instanceof LogLineInfo) {
mLogList.add((LogLineInfo) object);
}
int curLogSize = mLogList.size();
if (curLogSize >= MAX_RAM_LOG_COUNT) {
ConcurrentLinkedQueue<LogLineInfo> logList = new ConcurrentLinkedQueue<>(mLogList);
mLogList.clear();
writeLog(logList);
} else if (curLogSize > 0) {
// 延时5秒后,不检查内存日志条数,直接写入磁盘
mHandler.sendEmptyMessageDelayed(MSG_NO_CHECK_WRITE_LOG_ACTION, MAX_CHECK_GAP_TIME);
}
}
} else if (what == MSG_NO_CHECK_WRITE_LOG_ACTION) {
// 不检查内存日志条数,直接写入磁盘
synchronized (mWriteLockObj) {
if (mLogList != null && mLogList.size() > 0) {
ConcurrentLinkedQueue<LogLineInfo> logList = new ConcurrentLinkedQueue<>(mLogList);
mLogList.clear();
writeLog(logList);
}
}
}
}
}
private void writeLog(ConcurrentLinkedQueue<LogLineInfo> logList) {
FileWriter fileWriter = null;
try {
File writePathFile = getLogDirFile();
if (writePathFile != null && !writePathFile.exists()) {
writePathFile.mkdirs();
}
File writeLogFile = new File(writePathFile, LogPath.LOG_NORMAL_FILE_NAME);
if (writeLogFile != null && !writeLogFile.exists()) {
writeLogFile.createNewFile();
}
boolean reNameSuccess = true;
long fileSize = writeLogFile.length();
// 如果文件大于 5兆,则需要重新起一个新的文件来写
if (fileSize >= 5 * 1024 * 1024) {
// 根据系统_系统版本号_应用版本号_设备型号_设备id_uid_上报时间 来生产新的文件名
String logFileName = generateNewLogFileName();
if (!TextUtils.isEmpty(logFileName)) {
reNameSuccess = writeLogFile.renameTo(new File(writePathFile, logFileName));
}
}
if (reNameSuccess) {
// 只保留最近修改的两个日志文件,其他删除
deleteRedundantFile();
writeLogFile = new File(writePathFile, LogPath.LOG_NORMAL_FILE_NAME);
if (writeLogFile != null && !writeLogFile.exists()) {
writeLogFile.createNewFile();
}
} else {
return;
}
if (fileWriter == null) {
fileWriter = new FileWriter(writeLogFile, true);
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
e.printStackTrace();
}
if (fileWriter != null) {
try {
for (LogLineInfo logLineInfo: logList) {
fileWriter.append(logLineInfo.getMessage()).append("\n");
}
fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private File getLogDirFile() {
File logDirFile = new File(mLogFileDirPath);
return logDirFile;
}
}
可以通过开关设置,把保存的最多两个日志文件压缩后上传
我们可以修改 LogUtil 的 saveLog 方法,通过观察者模式,将日志信息上传到 APM。
触发时机:启动上传、每次新增日志时检查数据库日志数量
日志上传后成功后,根据 id 删除本地数据库冗余数据。