Android动态抓取Log

在实际开发过程中,当应用遇到问题,崩溃,Logcat常常能帮助开发者快速定位问题。在开发过程中,一款好的logcat设计,能让以后的维护变得简单快速。网络上也有很多开源的日志项目,log4j等等,也有很多第三方服务商提供抓取log的功能。当用户在使用app过程中应用发生崩溃,anr等事件时,快速抓取现场log并上报。下面我们就自己实现这一功能!

原理简单介绍:

利用adb命令:logcat,能从手机的log缓存文件中读取logcat。这样读出来缓存区所有的log,并不是我们想要的,这时参数便派上用场!

关于参数的详解,大家可以参照这篇博客:adb logcat命令

我们这里关键利用 “-t”: 输出最近的几行日志, 输出完退出, 不阻塞;

当崩溃发生时,取得最近的N行log就行。(注:log里并不包含错误崩溃的堆栈日志)

1、Android 4.1以下如果manifest文件中没有配置READ_LOGS权限将没有log输出

2、由于很多手机厂商做了修改,某些机型需要在设置中打开logcat开关才能取到log,本人实际测试中华为机型就是如此。具体操作:拨号界面输入:*#*#2846579#*#*进入测试菜单界面

异常监控:

我们需要先加入监控程序崩溃的代码,触发我们的捕获logcat操作。直接上代码:

currentHandler= Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
      @Override
      public void uncaughtException(Thread thread, Throwable ex) {
          //开始捕获logcat操作
          readLogs();

          //必须交还异常操作
          currentHandler.uncaughtException(thread,ex);
      }
  });

这样,当系统发生崩溃时就会回调我们的uncaughtException接口,如果想获取堆栈等信息,也可以在这里面进行!

捕获logcat

现在就是拿现场logcat的操作了。同理,上代码:

public static String readLogs() {
  Process localProcess = null;
  final StringBuilder stringBuilder = new StringBuilder();
  //这里读取50行。第四个string可以拿来做过滤条件,只取想要的层级log
  //可以是 *:V, *:D ,*:I ,*:W ,*:E ,*:F, *:S
  //也可以指定标签式的过滤(如:tagName:D *:S),":D"表示log的层级!!
  //当然也可以多个过滤器(如:tagName1:D tagName2:W tagName3:I *:S)
  String[] pa = {"logcat", "-t", "50", ""};
  try {
      localProcess = Runtime.getRuntime().exec(pa);

      final InputStream is = localProcess.getInputStream();
      final InputStream is1 = localProcess.getErrorStream();

      new Thread(new Runnable() {
          @Override
          public void run() {
              String logMessage;

              BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(is));
              try {
                  while ((logMessage = localBufferedReader.readLine()) != null) {
                      stringBuilder.append(logMessage);
                      stringBuilder.append("\n");
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  try {
                      if (is != null) {
                          is.close();
                      }
                      if (localBufferedReader != null) {
                          localBufferedReader.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }).start();

      new Thread(new Runnable() {
          @Override
          public void run() {
              BufferedReader br2 = new BufferedReader(
                      new InputStreamReader(is1));
              try {

                  String lineC;
                  while ((lineC = br2.readLine()) != null) {
                      stringBuilder.append(lineC);
                      stringBuilder.append("\n");
                  }

              } catch (IOException e) {
                  e.printStackTrace();
              } finally {

                  try {
                      if (is1 != null) {
                          is1.close();
                      }
                      if (br2 != null) {
                          br2.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }).start();
      localProcess.waitFor();

  } catch (Exception localException) {
      return localException.getMessage();
  } finally {
      try {
          if (localProcess != null) {
              localProcess.destroy();
          }
      } catch (Exception e) {
      }
  }

  return stringBuilder.toString();

代码比较糙,建议大家自己手动敲一遍,然后重构一下,还可以对取到的字节数做限定。比如:只取5kb的logcat数据等等。虽然是两个线程去读取流和错误流,但这个进程是阻塞式读取。建议大家读取行数的参数不要设置太大,如2000,10000……这样不但没有意义,而且在系统底层C操作logcat的时候会写入很多error信息到logcat中。所以实际开发中应该对最大行数做限定!