前言:

项目开始没有做好日志统计工作,每次有问题后端都得找前端对接,严重影响工作效率。最近特地在项目中加上日志保存策略,在此分享,供需要的人学习。

一.更详细的日志信息

既然决定自定义一个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方法打印相关信息即可。

二.日志保存策略

后端的人在测试的时候会遇到BUG,有时候不知道到底是前端出了问题还是后端的问题,为了更好更快速的定位,后端应该知道前端的日志保存在哪里。这就需要我们制定一个日志保存策略。(即使要上传日志,也应该先保存成文件再上传文件,不然每一条日志调用一次接口,接口的压力会很大,很不合理)

由于保存日志的过程是个耗时过程,我们需要开启线程去保存。但是日志产生的频率可能很高,又不能采用一般的线程去处理,太多的线程也会损耗性能。所以我们应该考虑队列的形式保存日志,然后一条一条的去保存。

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进阶:一、日志打印和保存策略的更多相关文章

  1. android的Log日志打印管理工具类(一)

    android的Log日志的打印管理工具类: package com.gzcivil.utils; import android.util.Log; /** * 日志打印管理 * * @author ...

  2. python(36):python日志打印,保存,logging模块学习

    1.简单的将日志打印到屏幕 import logging logging.debug('This is debug message') logging.info('This is info messa ...

  3. Android日志打印类LogUtils,能够定位到类名,方法名以及出现错误的行数并保存日志文件

    Android日志打印类LogUtils,能够定位到类名,方法名以及出现错误的行数并保存日志文件 在开发中,我们常常用打印log的方式来调试我们的应用.在Java中我们常常使用方法System.out ...

  4. spring boot 集成mybatis使用logback打印并保存日志信息

    spring boot 打印执行的sql语句 最近在学习spring boot 整合了Mybatis和druid之后总感觉少点什么东西,看了下在别的项目上用的框架,发现自己整合的东西不打印sql语句, ...

  5. Android Studio中JNI程序的单步调试和日志打印

    近日有个算法(检测碰撞)需要用C++实现,目的是IOS和ANDROID中共享同一段程序. 下面说说android调用这段程序过程中遇到的一些事情.(过程中网上搜索了一些相关文章,大部分说的是eclip ...

  6. Android中logcat和日志打印

     一.logcat对日志过滤 1.# logcat --help # logcat --help Usage: logcat [options] [filterspecs] options inclu ...

  7. 为什么说 Gradle 是 Android 进阶绕不去的坎 —— Gradle 系列(1)

    请点赞,你的点赞对我意义重大,满足下我的虚荣心. Hi,我是小彭.本文已收录到 GitHub · Android-NoteBook 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,欢迎 ...

  8. 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!

    本文来自:http://blog.csdn.net/ouyang_peng/article/details/11358405 今天看到一篇文章,收藏了很多大牛的博客,在这里分享一下 谦虚的天下 柳志超 ...

  9. Android之崩溃日志管理

    文章大纲 一.Android崩溃日志管理简介二.崩溃日志管理实战三.项目源码下载   一.Android崩溃日志管理简介 1. 什么是android崩溃日志管理   开发中有些地方未注意可能造成异常抛 ...

随机推荐

  1. QPS从0到4000请求每秒,谈达达后台架构演化之路

    达达是全国领先的最后三公里物流配送平台. 达达的业务模式与滴滴以及Uber很相似,以众包的方式利用社会闲散人力资源,解决O2O最后三公里即时性配送难题(目前达达已经与京东到家合并). 达达业务主要包含 ...

  2. http和https协议的区别

    内容摘自:转载注明Wosign 尊重知识产权 一 什么是HTTPS HTTPS全称为 安全超文本传输协议(Secure Hypertext Transfer Protocol)),是一个安全通信通道, ...

  3. react-intl

    internationalization standard https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob ...

  4. java中的标识符、修饰符、关键字

    关键字的特点:1.完全小写的字母,2,在增强版记事本中有特殊颜色 例如@是电子邮箱当中有特殊含义的.被保留的.不能随意使用的字符,关键字. 例如图中public.class.static.void是关 ...

  5. 在Windows2008r2 安装.net4.5

    WebApi 是比较高的环境下面开发 需要的环境是net4.5 或以上. Windows2008r2 里面没有这个环境必须自己安装.安装net4.5 必须sp1环境以上才能安装. 将Windows20 ...

  6. 通过FileReader和FileWriter实现复制文件的方法。

    public class CopyDemo { public static void main(String []args) { copyd(); } public static  void copy ...

  7. 微信中音乐播放在ios不能自动播放解决

    在微信中,ios手机下面音乐被自动禁掉无法自动播放,我们可以执行触发body上的元素,自动进行播放. //音乐 var x = document.getElementById("myAudi ...

  8. 云栖社区用机器人爬CSDN的文章?

    这个云栖社区的文章https://yq.aliyun.com/ziliao/539322 这篇文章是我13年写的,不知道咋插入图片,见谅. 下面是我的文件记录 分享XAML图标的网站 原创 2013年 ...

  9. 留恋 nyoj 854

    留恋 时间限制:1000 ms  |  内存限制:65535 KB 难度:2   描述 大家都知道,高中的时候,座位基本都是固定的,但是对于视力不好却又坐在后面的人是很不公平的. 念情的高中班主任安哥 ...

  10. ROS与C++

    构建工作空间 catkin_make 构建Catkin包 catkin_create_pkg # This is an example, do not try to run this # catkin ...