Optimize Android Log class and save log content to file

Keywords: Android Mobile Eclipse

What is the purpose?

Why optimize log? Take two examples.

When we encounter problems in development, we like to use log to help us analyze problems. Usually, we don't have the habit of deleting the log output code after solving the problems, so the problem comes. Others can also analyze your program's vulnerabilities based on the log information you develop, so security companies generally recommend deleting the log input code in the release package. It's impossible to delete them line by line (too much work).

Android system updates quickly, with many and miscellaneous models. Considering compatibility issues, test it on at least several mainstream OS versions and brand phones. Even if a professional IT company has enough equipment for you to test, I believe you want to leave the test to a professional tester. When problems arise, you can't ask others to bring your mobile phone to you and connect it to your computer for debugging. Therefore, it is necessary to save the log output memory to the file, and then analyze the log file directly.

So, let's look at how to package.

Enumeration Log Level

Analyzing the native log class, there are six common log levels, from VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, six values correspond to six integer constants in turn.
Seven enumeration variables are generated accordingly (adding CLOSE to generate release packages without exporting logs).

    /**
     * log level
     */
    public enum Level {

        VERBOSE(Log.VERBOSE),

        DEBUG(Log.DEBUG),

        INFO(Log.INFO),

        WARN(Log.WARN),

        ERROR(Log.ERROR),

        ASSERT(Log.ASSERT),

        CLOSE(Log.ASSERT + 1);

        int value;

        Level(int value) {
            this.value = value;
        }
    }

Packaging native method

Packaging has three purposes:

  1. For TAG, you need to declare a static constant or write a parameter at a time. The overloaded function changes the parameter String to Object so that the class object can be passed directly with this keyword and the class name can be obtained as TAG through target.getClass().getSimpleName().
  2. Seven enumeration variables were previously declared to be used for log filtering and log output closure, so the filter logic code was added before calling the native six log output functions.
  3. There are two ways to save logs to ask price: using the logcat command (AS and eclipse's log window), and saving them manually before calling the log function. This paper focuses on this method.
    public static final void i(String tag, String msg) {
        if (currentLevel.value > Level.INFO.value)
            return;
        if (isWriter) {
            write(tag, msg, "I");
        }
        Log.i(tag, msg);
    }

    public static final void i(String tag, String msg, Throwable throwable) {
        if (currentLevel.value > Level.INFO.value)
            return;
        if (isWriter) {
            write(tag, msg, "I", throwable);
        }
        Log.i(tag, msg, throwable);
    }

    public static final void v(String tag, String msg) {
        if (currentLevel.value > Level.VERBOSE.value)
            return;
        if (isWriter) {
            write(tag, msg, "V");
        }
        Log.v(tag, msg);
    }

    public static final void v(String tag, String msg, Throwable throwable) {
        if (currentLevel.value > Level.VERBOSE.value)
            return;
        if (isWriter) {
            write(tag, msg, "V", throwable);
        }
        Log.v(tag, msg, throwable);
    }

    public static final void d(String tag, String msg) {
        if (currentLevel.value > Level.DEBUG.value)
            return;
        if (isWriter) {
            write(tag, msg, "D");
        }
        Log.d(tag, msg);
    }

    public static final void d(String tag, String msg, Throwable throwable) {
        if (currentLevel.value > Level.DEBUG.value)
            return;
        if (isWriter) {
            write(tag, msg, "D", throwable);
        }
        Log.d(tag, msg, throwable);
    }

    public static final void e(String tag, String msg) {
        if (currentLevel.value > Level.ERROR.value)
            return;
        if (isWriter) {
            write(tag, msg, "E");
        }
        Log.e(tag, msg);
    }

    public static final void e(String tag, String msg, Throwable throwable) {
        if (currentLevel.value > Level.ERROR.value)
            return;
        if (isWriter) {
            write(tag, msg, "E", throwable);
        }
        Log.e(tag, msg, throwable);
    }

    public static final void w(String tag, String msg) {
        if (currentLevel.value > Level.WARN.value)
            return;
        if (isWriter) {
            write(tag, msg, "W");
        }
        Log.w(tag, msg);
    }

    public static final void w(String tag, String msg, Throwable throwable) {
        if (currentLevel.value > Level.WARN.value)
            return;
        if (isWriter) {
            write(tag, msg, "W", throwable);
        }
        Log.w(tag, msg, throwable);
    }

    public static final void i(Object target, String msg) {
        i(target.getClass().getSimpleName(), msg);
    }

    public static final void i(Object target, String msg, Throwable throwable) {
        i(target.getClass().getSimpleName(), msg, throwable);
    }

    public static final void v(Object target, String msg) {
        v(target.getClass().getSimpleName(), msg);
    }

    public static final void v(Object target, String msg, Throwable throwable) {
        v(target.getClass().getSimpleName(), msg, throwable);
    }

    public static final void d(Object target, String msg) {
        d(target.getClass().getSimpleName(), msg);
    }

    public static final void d(Object target, String msg, Throwable throwable) {
        d(target.getClass().getSimpleName(), msg, throwable);
    }

    public static final void e(Object target, String msg) {
        e(target.getClass().getSimpleName(), msg);
    }

    public static final void e(Object target, String msg, Throwable throwable) {
        e(target.getClass().getSimpleName(), msg, throwable);
    }

    public static final void w(Object target, String msg) {
        w(target.getClass().getSimpleName(), msg);
    }

    public static final void w(Object target, String msg, Throwable throwable) {

        w(target.getClass().getSimpleName(), msg, throwable);
    }

Save log contents to files

The purpose of encapsulating the native method is that we can insert the write method and save the log content to the file. LOG_FORMAT is a log output format imitating AS. The results will be attached later.

    /**
     * Write file operation
     *
     * @param tag       Log label
     * @param msg       Log content
     * @param level     log level
     * @param throwable Anomaly capture
     */
    private static final void write(String tag, String msg, String level, Throwable throwable) {
        String timeStamp = LOG_TIME_FORMAT.format(Calendar.getInstance().getTime());

        try {
            writer.write(String.format(LOG_FORMAT, timeStamp, Process.myPid(), Process.myTid(), pkgName, level, tag));
            writer.write(msg);
            writer.newLine();
            writer.flush();
            osWriter.flush();
            fos.flush();
            if (throwable != null)
                saveCrash(throwable);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Save exceptions
     *
     * @param throwable
     * @throws IOException
     */
    private static void saveCrash(Throwable throwable) throws IOException {
        StringWriter sWriter = new StringWriter();
        PrintWriter pWriter = new PrintWriter(sWriter);
        throwable.printStackTrace(pWriter);
        Throwable cause = throwable.getCause();
        while (cause != null) {
            cause.printStackTrace(pWriter);
            cause = cause.getCause();
        }
        pWriter.flush();
        pWriter.close();
        sWriter.flush();
        String crashInfo = writer.toString();
        sWriter.close();
        writer.write(crashInfo);
        writer.newLine();
        writer.flush();
        osWriter.flush();
        fos.flush();
    }

The contents of the logs are as follows:

08-14 17:15:03.665 24152-24152/com.flueky.app D/TAG:838E512687D20F6B40409A2E3A7B24156774F47C

Component initialization

  1. The context is passed in to get the package name and the external cache directory of the program: / sdcard/Android/data / package name /. The directory where the log files are saved is: / sdcard/Android/data / package name / log / log file.
  2. Does the isWriter tag need to save log content to a file?
  3. Level sets the log output level. When level equals CLOSE, no logs are output and no files are saved.
    /**
     * Log component initialization
     *
     * @param appCtx   application context
     * @param isWriter Does it save files?
     * @param level    log level
     */
    public static final void initialize(Context appCtx, boolean isWriter, Level level) {
        currentLevel = level;
        if (level == Level.CLOSE) {
            isWriter = false;
            return;
        }
        Logger.isWriter = isWriter;
        if (!Logger.isWriter) {//Do not save logs to files
            return;
        }
        String logFoldPath = appCtx.getExternalCacheDir().getAbsolutePath() + "/../log/";
        pkgName = appCtx.getPackageName();
        File logFold = new File(logFoldPath);
        boolean flag = false;
        if (!(flag = logFold.exists()))
            flag = logFold.mkdirs();
        if (!flag) {
            Logger.isWriter = false;
            return;
        }
        logFilePath = logFoldPath + FILE_NAME_FORMAT.format(Calendar.getInstance().getTime()) + ".log";
        try {
            File logFile = new File(logFilePath);
            if (!(flag = logFile.exists()))
                flag = logFile.createNewFile();
            Logger.isWriter = isWriter & flag;
            if (Logger.isWriter) {
                fos = new FileOutputStream(logFile);
                osWriter = new OutputStreamWriter(fos);
                writer = new BufferedWriter(osWriter);
            }
        } catch (IOException e) {
            e.printStackTrace();
            Logger.isWriter = false;
        }
    }

Posted by gilbertwang on Tue, 04 Jun 2019 11:13:27 -0700