前言:
項目開始沒有作好日誌統計工做,每次有問題後端都得找前端對接,嚴重影響工做效率。最近特意在項目中加上日誌保存策略,在此分享,供須要的人學習。前端
一.更詳細的日誌信息
既然決定自定義一個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
二.日誌保存策略
後端的人在測試的時候會遇到BUG,有時候不知道究竟是前端出了問題仍是後端的問題,爲了更好更快速的定位,後端應該知道前端的日誌保存在哪裏。這就須要咱們制定一個日誌保存策略。(即便要上傳日誌,也應該先保存成文件再上傳文件,否則每一條日誌調用一次接口,接口的壓力會很大,很不合理)ide
因爲保存日誌的過程是個耗時過程,咱們須要開啓線程去保存。可是日誌產生的頻率可能很高,又不能採用通常的線程去處理,太多的線程也會損耗性能。因此咱們應該考慮隊列的形式保存日誌,而後一條一條的去保存。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; } } }
以上就是所有內容,但願對你們有所幫助, 若是喜歡個人文章,想與一羣資深開發者一塊兒交流學習的話,歡迎加入個人合做羣Android Senior Engineer技術交流羣:925019412ui