android 系统/本地日志打印

android 本地日志打印

提示
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041
转载请注明出处

前言

  离上一篇博客大概有快四个月没有更新了,当然这段时间其实是遇到了一些事情,也是因为可能自己也比较懒散了点吧,不过渐渐的也就没有时间,没有精力去做一些总结相关的工作了,而且一直工作也比较忙了,给自己找了一些借口,一直想着坚持坚持,但是还是一次一次的放弃了。。。养成一个习惯只要21天,放弃只要一天。好了,废话不多说,直奔主题了


  现在越来越有感觉,基于安卓的智能设备也多如牛毛了,而且现在越来越多的原来做手机开发的人也渐渐转入到了系统应用开发中,两者嘛说一样,其实还是有一定区别的,说有很大区别吧,但是又都是有一个根。所以可以认为大概相似,因为在做系统应用开发的时候,可能考虑的业务深度会稍微深那么一点点。而且现在很多智能设备厂商都是对android系统进行了很多裁剪的操作,也就是动不动砍掉一个Phone模块啊,不编这个模块,改那个模块,并不是像手机方案商那么专业,所以很多时候可能有些问题是不是自己改出来的都不知道,所以系统日志的打印其实就相对比较重要了,做过mtk平台的都知道,mtk在debug版本的时候,是可以打开mtklog的,直接将日志一行一行的打印到本地,如果出现了一些crash,直接在sdcard的某个目录下面,其实就可以找到事发现场,包括framwork,kernel相关的日志都可以。对于问题的分析更加透彻

  现在基于android系统的系统的设备,比如手环,手表,车机,智能猫眼等等一系列的设备。如何打印日志,如何定位问题,甚至当设备已经在用户手中的时候出现的crash日志该如何收集,可能有人说可以用bugly,还有很多第三方的bug收集平台,这肯定是没问题的,但是那些貌似其实都是crash情况,只能定位问题点,但是无法出现问题出现的代码业务逻辑场景。所以根据我以前做过的一些相关工作,简单的写了一个日志收集的demo。姑且叫做Logger吧。


Logger 系统/本地收集日志

本地/系统打印策略

  根据以前使用过的一些日志打印工具,有的使用了Log4j这些开源的库打印的,有的直接使用IO流直接往文件里面写的,现在也有个很牛逼的Logger库等。这些都是可以做到本地打印的功能,但是不要忘了,android原生的logcat命令。本学徒的这个demo其实最主要其实就是基于logcat命令来使用的。 打印需要有一定策略的,比如,存储路径,比如写入的文件大小,写入的文件的数量等。是否是debug模式下。等等都是需要限定的吧。
  一个文件1G,你还有心思看么。一次性打印了100个文件,你还有心思看么???


直接上代码了,先看配置Sysconfig:

 mSysConfig = new SysAppLoggerConfiguration.SysAppLoggerConfigurationBuilder()
                .tag(LoggerConstant.TEST_TAG)
                .elogLevel(EnLogLevel.ALL)
                .fileSuffix(LoggerConstant.SUFFIX)
                .isExternalSdcard(true)
                .logPath("my/sys")
                .setDebug(false)
                .logThreadPrioperty(EnLogThreadPrioperty.NORM_PRIPERTY)
                .logTagPrioperty(EnLogTagPrioperty.NORM_PRIPERTY)
                .singleLogFileSize(1024 * 500 * 2)
                .logfileNums(10)
                .filterParameters(LoggerConstant.params)
                .build();

demo下的所有配置参数,当然每个参数其实都已经有自己的默认值,可以选择性的填写。有参数如下:
1:tag:作为整个应用的总的tag。用于logcat过滤的时候使用
2:elogLevel:选择打印的是哪一个等级,如V,D,I,等等,logcat下打印日志的等级
3:isExternalSdcard:是否在外置sd卡上打印,默认内置
4:logPath存储的路径
5:setDebug是否需要是deubg模式,默认的话,是非debug的,如果是debug的话,只会打印debug日志
6:logThreadPrioperty打印线程的优先级,参数存在意义小
7:fileSuffix 打印日志的后缀名
8:logTagPrioperty 这个优先级,其实是用户筛选的,即限定一个最低的打印优先级,低于此数值的话,则不会打印到日志文本中,只会打印高于此优先级的日志
9:singleLogFileSize 单个文件的大小,限制文件的大小
10:filterParameters:筛选的命令,具体可以参考logcat命令集

通过参数配置的形式然后进行初始化:

 Logger mLogger = Logger.getInstance();
 mLogger.init(mSysConfig, null);

日志打印的开关及日志清除

mLogger.startLogger();
mLogger.stopLogger();
mLogger.clearLogCache();

打印日志的使用方式:

final LogUtils mLogutils = Logger.getInstance().getLogger();
mLogutils.i(EnLogTagPrioperty.NORM_PRIPERTY.getValue(),TAG, "logger.i--------");
mLogutils.v(10,TAG, "logger.v--------");
mLogutils.d(8,TAG, "logger.d---------");
mLogutils.e(3,TAG, "logger.e---------");

使用方式很简单,其实主要其实就是在每个类中得到LogUtils,然后通过LogUtils直接进行打印。
最最重要的权限千万别忘了啊,不多谈权限:

<uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.READ_LOGS" />

原理分析,在这个demo中核心代码分析:

  Log.i(LoggerConstant.TEST_TAG,"SysEngineTask run");
        ShellUtils.execCommand(LoggerConstant.CLEAR_LOGCAT_CACHE, false);
        try {
            Log.i(LoggerConstant.TEST_TAG,mConfig.getBaseLogCommands());
            proc = Runtime.getRuntime().exec(mConfig.getBaseLogCommands());
            mBufferedReader = new BufferedReader(new InputStreamReader(proc.getInputStream()), 2048);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (null == mBufferedOutputStream) {
            Log.i(LoggerConstant.TEST_TAG, "mBufferOutputStream is null");
            return;
        }
        Log.i(LoggerConstant.TEST_TAG, "mBufferOutputStream is not null");
        String line = null;
        int writeLen = 0;
        int lineLen;
        int regexPrioperty = -1;
        while (currentThread().isAlive() && (!Thread.currentThread().isInterrupted())) {
            try {
                while (startFlag && (line = mBufferedReader.readLine()) != null) {
                    if (mBufferedOutputStream != null) {
                        lineLen = line.length();
                        if (lineLen == 0){
                            continue;
                        }
                        regexPrioperty = mRegexUtils.getPropNum(line);
                        if (regexPrioperty != -1){
                            if (regexPrioperty <  prioperty){
                                continue;
                            }
                        }
                        mBufferedOutputStream.write(line.getBytes());
                        writeLen += line.getBytes().length;
                        mBufferedOutputStream.write(LoggerConstant.SIGN_LINE_BREAK.getBytes());
                        writeLen += LoggerConstant.SIGN_LINE_BREAK.getBytes().length;
                        if (writeLen >= mConfig.mBuilder.getSingleLogFileSize()){
                            mBufferedOutputStream.write(LoggerConstant.LOG_END.getBytes());
                            Log.i(LoggerConstant.TEST_TAG,"=====||||");
                            mBufferedOutputStream.flush();
                            CloseUtils.closeIO(mBufferedOutputStream);
                            writeLen = 0;
                            mBufferedOutputStream = mFileHelper.changeNextFile();
                            ShellUtils.execCommand(LoggerConstant.CLEAR_LOGCAT_CACHE, false);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

SysEngineTask中的代码,其实原理很简单,使用的其实就是执行了logcat命令,将logcat将logcat的流定向,拿到logcat数据流之后,直接将其往一个文件中写,只是在里面加入了一些策略。

注意事项

1:如果你想把它作为系统应用的日志打印工具的话,那么那么一定要注意给你的应用添加系统权限,因为在4.0之后,
uses-permission android:name=”android.permission.READ_LOGS” 这个只能拿到自己当前应用的日志,拿不到系统的日志。
2:当然如果你想通过它拿到自己单个应用的日志,也可以使用,策略是一样的。它可以筛选出本应用下,同一个tag下的日志
3:筛选的命令需要自己选择写。我的demo里面主要是基于logcat -v time -s这个基础命令来使用的

Logger 本地收集日志的另外一种形式

有时候可能有人会觉得我只想打我自己想要的日志,我不想有那么多形式的日志都打印在本地,那样太多了。反正我就是不想要那么多形式的日志。在我的demo里面也提供了一些,使用队列和字符拼接的形式去打印。打印的策略其实与上面的形式类似吧,不过不会把系统的信息给打印出来

依然代码:

void initNatConfiguration(){
         mNatConnfig =
                new NatAppLoggerConfiguration.NatAppLoggerConfigurationBuilder()
                        .tag(LoggerConstant.TEST_TAG)
                        .elogLevel(EnLogLevel.ALL)
                        .fileSuffix(LoggerConstant.SUFFIX)
                        .isExternalSdcard(true)
                        .logPath("my/nat")
                        .setDebug(true)
                        .logThreadPrioperty(EnLogThreadPrioperty.NORM_PRIPERTY)
                        .logTagPrioperty(EnLogTagPrioperty.NORM_PRIPERTY)
                        .singleLogFileSize(1024 * 500 * 2)
                        .logfileNums(3)
                        .initCrashHander(getApplicationContext())
                        .build();
    }

上面的参数与系统打印的时候的一些参数都是类似的。可以看看,参数也是可缺省的。

初始化的过程也是一样的

mLogger.init(mNatConnfig, new Logger.GetAppNameListener() {
                    @Override
                    public String getName() {
                        return  AppUtils.getAppName(getApplicationContext());
                    }
                });

注意,因为在拼接的时候,需要用到appName,没办法,简单的使用这样方式来从外界进行获取操作。

启动打印的线程

mLogger.startLogger();

使用的过程:

  final LogUtils mLogutils = Logger.getInstance().getLogger();
  mLogutils.i(EnLogTagPrioperty.NORM_PRIPERTY.getValue(),TAG, "logger.i--------");
  mLogutils.v(10,TAG, "logger.v--------");
  mLogutils.d(8,TAG, "logger.d---------");
  mLogutils.e(3,TAG, "logger.e---------");

主要的业务逻辑重点分析:
1:队列创建

@Override
    public void prepare(BaseConfiguration mBaseConfiguration) {
        mconfig = mBaseConfiguration;
        if (mBaseConfiguration instanceof SysAppLoggerConfiguration){
            //如果是系统app的时候该怎么去初始化一些参数
            configType = 1;
            mTask = new SysEngineTask(this);
        }else if (mBaseConfiguration instanceof NatAppLoggerConfiguration){
            //如果是非系统应用的时候该怎么去初始化一些参数
            configType = 2;
            logMsgsQue = new LinkedBlockingQueue<>(1000); //创建里面有1000个容量的队列
            Log.i(LoggerConstant.TEST_TAG,"initLogMsgQueue");
            mTask = new NatEngineTask(this);
        }else {
            throw new RuntimeException("参数类型不匹配");
        }
    }

2:队列使用offer数据

 private void printToTextFile(LogMsg msg){
        if (null != mBlockQueue){
            try {
                Log.i(LoggerConstant.TEST_TAG,"put object:"+msg.getLoggerMsg());
                mBlockQueue.put(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

3:打印数据的拼接过程

//时间 + 进程ID + 进程名字 + TAG类型 +/tag 类型 + msg
    public synchronized String getLoggerMsg(){
        mStringBuffer = new StringBuffer();
        String date = new SimpleDateFormat(LoggerConstant.DATE_FORAT).format(new Date());
        int pid = AppUtils.getProcessId();
        int uid = AppUtils.getProcessUid();
        String type = null;
        if (level == EnLogLevel.DEBUG){
            type = "D";
        }else if (level == EnLogLevel.ERROR){
            type = "E";
        }else if (level == EnLogLevel.FATAL){
            type = "F";
        }else if ((level == EnLogLevel.INFO)){
            type = "I";
        }else if (level == EnLogLevel.WARN){
            type = "W";
        }else if (level == EnLogLevel.TRACE){
            type = "T";
        }else if (level == EnLogLevel.ALL){
            type = "V";
        }else {
            type = "V";
        }
        mStringBuffer
                .append(date)
                .append(" ")
                .append(uid)
                .append("-")
                .append(pid)
                .append("/")
                .append(appName)
                .append(" ")
                .append(type)
                .append("/")
                .append(tag)
                .append(":")
                .append(logmsg);
        return mStringBuffer.toString();
    }

4:将队列中的数据打印到本地的过程

@Override
    public void run() {
        while (currentThread().isAlive() && (!Thread.currentThread().isInterrupted())){
            int writeLen = 0;
            while (startFlag){
                try {
                    msg = mBlockQueue.take();
                    if (msg.getPrioperty() < prioperty){
                        continue;
                    }
                    writeLen = writeLen + msg.getLoggerMsg().getBytes().length;
                    if (mBufferOut != null){
                        Log.i(LoggerConstant.TEST_TAG,"loggerMsg:"+msg.getLoggerMsg());
                        mBufferOut.write(msg.getLoggerMsg().getBytes());
                        writeLen += LoggerConstant.SIGN_LINE_BREAK.getBytes().length;
                        mBufferOut.write(LoggerConstant.SIGN_LINE_BREAK.getBytes());
                        if (writeLen >= mConfig.mBuilder.getSingleLogFileSize()){
                            Log.i(LoggerConstant.TEST_TAG,"writelen is Equal LogFilesize:"+mConfig.mBuilder.getSingleLogFileSize());
                            mBufferOut.write(LoggerConstant.LOG_END.getBytes());
                            mBufferOut.flush();
                            CloseUtils.closeIO(mBufferOut);
                            writeLen = 0;

                            mBufferOut = mFileHelper.changeNextFile();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

从上面的大概代码的过程可以看到,先创建了一个队列,在执行打印的时候生成的msg,然后将msg放入到queue中。单独有一个线程来从queue中取出数据用来打印到本地。只是这种打印过程放入了一些策略性的东西而已

注意注意

权限问题不可避免:注册

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.READ_LOGS" />

注册完了在6.0的时候,还是需要进行申请的啊。

 @Override
    public void requestReadAndWriteSdCardPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                int checkSefPermiss = mContext.checkSelfPermission(WRITE_EXTERNAL_STORAGE);
                if (checkSefPermiss == PackageManager.PERMISSION_DENIED){
                    ((AppCompatActivity)mContext).requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},readAndWriteSdCardPermissionRequestCode);
                }
            }
    }

以上部分大概就是整个封装出来的日志的大概的主旨所在以及思想。有兴趣的话可以阅读下我传上去的代码部分。
https://gitee.com/906285288/Android_LoggerUtils-_Library

欢迎持续访问博客

喜欢的朋友们,点个关注撒,谢谢

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值