项目开始没有作好日志统计工做,每次有问题后端都得找前端对接,严重影响工做效率。最近特意在项目中加上日志保存策略,在此分享,供须要的人学习。前端
更详细的日志信息java
既然决定自定义一个log,那咱们就可让它显示更多的信息,如线程信息:threadId,threadName等:后端
private String getFunctionName() {
StackTraceElement[] sts = Thread.currentThread().getStackTrace();
if (sts == null) {
return null;
}
for (StackTraceElement st : sts) {
if (st.isNativeMethod()) {
continue;
}
if (st.getClassName().equals(Thread.class.getName())) {
continue;
}
if (st.getClassName().equals(this.getClass().getName())) {
continue;
}
Thread t = Thread.currentThread();
return "[T(id:" + t.getId() +
", name:" + t.getName() +
", priority:" + t.getPriority() +
", groupName:" + t.getThreadGroup().getName() +
"): " + st.getFileName() + ":"
+ st.getLineNumber() + " " + st.getMethodName() + " ]";
}
return "";
}
复制代码
StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,咱们从中获取方法执行的线程相关的信息,以及执行的方法名称等。这些信息能帮助咱们更好的查找问题之所在。多线程
private void logPrint(int logLevel, Object msg) {
if (isDebug) {
String name = getFunctionName();
customTag = TextUtils.isEmpty(customTag) ? defaultTag : customTag;
Log.println(logLevel, customTag, name + " - " + msg);
}
}
复制代码
使用Log.println方法打印相关信息便可。app
日志保存策略ide
后端的人在测试的时候会遇到BUG,有时候不知道究竟是前端出了问题仍是后端的问题,为了更好更快速的定位,后端应该知道前端的日志保存在哪里。这就须要咱们制定一个日志保存策略。(即便要上传日志,也应该先保存成文件再上传文件,否则每一条日志调用一次接口,接口的压力会很大,很不合理)oop
因为保存日志的过程是个耗时过程,咱们须要开启线程去保存。可是日志产生的频率可能很高,又不能采用通常的线程去处理,太多的线程也会损耗性能。因此咱们应该考虑队列的形式保存日志,而后一条一条的去保存。性能
public void initSaveStrategy(Context context) {
if (saveLogStrategy != null || !isDebug) {
return;
}
final int MAX_BYTES = 1024 * 1024;
String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
File cacheFile = context.getCacheDir();
if (cacheFile != null) {
diskPath = cacheFile.getAbsolutePath();
}
String folder = diskPath + File.separatorChar + "log";
HandlerThread ht = new HandlerThread("SohuLiveLogger." + folder);
ht.start();
Handler handler = new SaveLogStrategy.WriteHandler(ht.getLooper(), folder, MAX_BYTES);
saveLogStrategy = new SaveLogStrategy(handler);
}
复制代码
public static class WriteHandler extends Handler {
private final String folder;
private final int maxFileSize;
WriteHandler(@NonNull Looper looper, @NonNull String folder, int maxFileSize) {
super(checkNotNull(looper));
this.folder = checkNotNull(folder);
this.maxFileSize = maxFileSize;
}
@Override
public void handleMessage(@NonNull 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) {
if (fileWriter != null) {
try {
fileWriter.flush();
fileWriter.close();
} catch (IOException e1) {
}
}
}
}
复制代码
咱们使用HandlerThread来处理这个任务。HandlerThread是一个可使用handler的Thread。当咱们把消息保存到消息队列中去以后会在线程中去处理,又能保证不会产生不少线程。其实这里也可使用instentservice实现,这个服务适合量大而不太耗时的任务。学习
最后在一个方法中统一打印和保存便可:测试
private void logPrint(int logLevel, Object msg) {
if (isDebug) {
String name = getFunctionName();
if (saveLogStrategy != null) {
saveLogStrategy.log(Log.ERROR, customTag, name + " - " + msg);
}
Log.println(logLevel, customTag, name + " - " + msg);
}
}
复制代码
自定义的log策略仍是比较简单,主要就是这个思想:打印日志信息详细,保存要采用队列的形式。一下是所有代码:
public class Logger {
public final static String tag = "";
private static SaveLogStrategy saveLogStrategy;
private final static boolean logFlag = true;
private static Logger logger;
private int logLevel = Log.VERBOSE;
private static boolean isDebug = BuildConfig.DEBUG;
private String customTag = null;
private Logger(String customTag) {
this.customTag = customTag;
}
public void initSaveStrategy(Context context) {
if (saveLogStrategy != null || !isDebug) {
return;
}
final int MAX_BYTES = 1024 * 1024;
String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
File cacheFile = context.getCacheDir();
if (cacheFile != null) {
diskPath = cacheFile.getAbsolutePath();
}
String folder = diskPath + File.separatorChar + "log";
HandlerThread ht = new HandlerThread("Logger." + folder);
ht.start();
Handler handler = new SaveLogStrategy.WriteHandler(ht.getLooper(), folder, MAX_BYTES);
saveLogStrategy = new SaveLogStrategy(handler);
}
public static Logger getLogger(String tag) {
if (logger == null) {
logger = new Logger(tag);
}
return logger;
}
public static Logger getLogger() {
if (logger == null) {
logger = new Logger(tag);
}
return logger;
}
/** * Verbose(2) 级别日志 * * @param str String */
public void v(Object str) {
logLevel = Log.VERBOSE;
logPrint(logLevel, str);
}
/** * Debug(3) 级别日志 * * @param str String */
public void d(Object str) {
logLevel = Log.DEBUG;
logPrint(logLevel, str);
}
/** * Info(4) 级别日志 * * @param str String */
public void i(Object str) {
logLevel = Log.INFO;
logPrint(logLevel, str);
}
/** * Warn(5) 级别日志 * * @param str String */
public void w(Object str) {
logLevel = Log.WARN;
logPrint(logLevel, str);
}
/** * Error(6) 级别日志 * * @param str String */
public void e(Object str) {
logLevel = Log.ERROR;
logPrint(logLevel, str);
}
private void logPrint(int logLevel, Object msg) {
if (isDebug) {
String name = getFunctionName();
if (saveLogStrategy != null) {
saveLogStrategy.log(Log.ERROR, customTag, name + " - " + msg);
}
Log.println(logLevel, customTag, name + " - " + msg);
}
}
/** * 获取当前方法名 * * @return 方法名 */
private String getFunctionName() {
StackTraceElement[] sts = Thread.currentThread().getStackTrace();
if (sts == null) {
return null;
}
for (StackTraceElement st : sts) {
if (st.isNativeMethod()) {
continue;
}
if (st.getClassName().equals(Thread.class.getName())) {
continue;
}
if (st.getClassName().equals(this.getClass().getName())) {
continue;
}
Thread t = Thread.currentThread();
return "[Thread(id:" + t.getId() +
", name:" + t.getName() +
", priority:" + t.getPriority() +
", groupName:" + t.getThreadGroup().getName() +
"): " + st.getFileName() + ":"
+ st.getLineNumber() + " " + st.getMethodName() + " ]";
}
return "";
}
}
复制代码
public class SaveLogStrategy {
@NonNull
private final Handler handler;
public SaveLogStrategy(@NonNull Handler handler) {
this.handler = checkNotNull(handler);
}
public void log(int level, @Nullable String tag, @NonNull String message) {
checkNotNull(message);
handler.sendMessage(handler.obtainMessage(level, message));
}
static class WriteHandler extends Handler {
private final String folder;
private final int maxFileSize;
WriteHandler(@NonNull Looper looper, @NonNull String folder, int maxFileSize) {
super(checkNotNull(looper));
this.folder = checkNotNull(folder);
this.maxFileSize = maxFileSize;
}
@SuppressWarnings("checkstyle:emptyblock")
@Override
public void handleMessage(@NonNull 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) {
if (fileWriter != null) {
try {
fileWriter.flush();
fileWriter.close();
} catch (IOException e1) {
}
}
}
}
private void writeLog(@NonNull FileWriter fileWriter, @NonNull String content) throws IOException {
checkNotNull(fileWriter);
checkNotNull(content);
fileWriter.append("\n").append(content);
}
private File getLogFile(@NonNull String folderName, @NonNull String fileName) {
checkNotNull(folderName);
checkNotNull(fileName);
File folder = new File(folderName);
if (!folder.exists()) {
if (!folder.mkdirs()) {
Log.println(Log.ERROR, "saveLog", "文件未建立成功,多是读写权限没给");
}
}
int newFileCount = 0;
File newFile;
File existingFile = null;
newFile = new File(folder, String.format("%s_%s.txt", fileName, newFileCount));
while (newFile.exists()) {
existingFile = newFile;
newFileCount++;
newFile = new File(folder, String.format("%s_%s.txt", fileName, newFileCount));
}
if (existingFile != null) {
if (existingFile.length() >= maxFileSize) {
return newFile;
}
return existingFile;
}
return newFile;
}
}
}
复制代码
以上就是所有内容,但愿对你们有所帮助