Preface:

At the beginning of the project, the log statistics were not done well. Every time there was a problem, the back-end had to find a front-end connection, which seriously affected the work efficiency. I recently added a log saving strategy to the project and shared it here for those who need to learn.

More detailed log information

Now that we’ve decided to customize a log, we can make it display more information, such as thread information: threadId, threadName, etc. :

private String getFunctionName(a) {
        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 "";
    }
Copy the code

A StackTrace holds information about the method call stack, which lets us get information about the thread on which the method is executing, the name of the method being executed, and so on. This information can help us better locate the problem.

private void logPrint(int logLevel, Object msg) {
        if (isDebug) {
            String name = getFunctionName();
            customTag = TextUtils.isEmpty(customTag) ? defaultTag : customTag;
            Log.println(logLevel, customTag, name + "-"+ msg); }}Copy the code

Use log.println to print the information.

Log Saving Policy

Sometimes, people on the back end will encounter bugs when testing. Sometimes, they don’t know whether the front end is faulty or the back end is faulty. In order to better and faster locate, the back end should know where the front-end logs are saved. This requires us to develop a log retention strategy. (Even if you want to upload logs, you should save the logs to a file before uploading the file. Otherwise, the interface is called once for each log, which will cause great and unreasonable pressure on the interface.)

Since saving logs is a time-consuming process, we need to enable threads to save logs. However, logs can be generated frequently and cannot be processed by normal threads, and too many threads can degrade performance. So we should think about storing logs in queues, and then storing them one by one.

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);
    }
Copy the code
 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) {

                    }
                }
            }
        }
Copy the code

We use HandlerThread to handle this task. A HandlerThread is a Thread that can use a handler. When a message is stored in a message queue, it is processed in a thread, ensuring that it does not generate many threads. Instentservice can also be used, which is suitable for tasks that are not too time-consuming and require a lot of work.

Finally, print and save in one method:

    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); }}Copy the code

The custom log policy is relatively simple. The main idea is to print detailed log information and save it in a queue. Here is the complete code:

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(a) {
        if (logger == null) {
            logger = new Logger(tag);
        }
        return logger;
    }

    /** * Verbose(2) level log **@param str String
     */
    public void v(Object str) {
        logLevel = Log.VERBOSE;
        logPrint(logLevel, str);
    }

    /** * Debug(3) Level logs **@param str String
     */
    public void d(Object str) {
        logLevel = Log.DEBUG;
        logPrint(logLevel, str);
    }

    /** * Info(4) Level log **@param str String
     */
    public void i(Object str) {
        logLevel = Log.INFO;
        logPrint(logLevel, str);
    }

    /** * Warn(5) Level logs **@param str String
     */
    public void w(Object str) {
        logLevel = Log.WARN;
        logPrint(logLevel, str);
    }

    /** * Error(6) Level log **@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); }}/** * gets the current method name **@returnThe method name * /
    private String getFunctionName(a) {
        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 ""; }}Copy the code
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"."Failed to create file, maybe read/write permission not granted"); }}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;
            }

            returnnewFile; }}}Copy the code

The above is all content, I hope to help you