【Android】【源码分析】系统各类异常日志收集服务(DropBoxManagerService)

 

      DropBoxManagerService(简称DBMS) 统一收集管理各类系统关键日志和异常日志。

1.DropBoxManager &DropBoxManagerService简介

      Android系统启动过程SystemServer进程时,在startOtherServices()过程会启动DBMS服务。和大多数系统服务一样,DropBoxManager 和 DropBoxManagerService 通过aidl实现跨进程通信。客户端可以通过DropBoxManager 提供的接口来 写入 和 读取 日志。

(1)写入日志

     DropBoxManager写入日志提供了以下三种方式:

public void addText(String tag, String data) {
      。。。
        mService.add(new Entry(tag, 0, data));
      。。。
    }
}

public void addData(String tag, byte[] data, int flags) {
       。。。

        mService.add(new Entry(tag, 0, data, flags));
       。。。

}

public void addFile(String tag, File file, int flags) throws IOException {
       。。。
        mService.add(new Entry(tag, 0, file, flags));
       。。。

}

                                                             (代码1,来自android.os.DropBoxManager.java)

  将三种添加日志的方法进行了简化。

  可以看出支持String,byte[]以及file三种格式输入。在添加方法中并没有直接处理,而是交给了Entry 这个类的构造函数。这个Entry 是DropBoxManager的内部类。

(2)读取日志

/**
 * Gets the next entry from the drop box <em>after</em> the specified time.
 * Requires <code>android.permission.READ_LOGS</code>.  You must always call
 * {@link Entry#close()} on the return value!
 *
 * @param tag of entry to look for, null for all tags
 * @param msec time of the last entry seen
 * @return the next entry, or null if there are no more entries
 */
public Entry getNextEntry(String tag, long msec) {
    try {
        return mService.getNextEntry(tag, msec);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

                                                       (代码2,来自android.os.DropBoxManager.java)

  读取日志只有这一个方法,获取的就是Entry实例。需要注意的是,tag传null的话,就会返回所有的日志。

(3)DropBoxManagerService.aidl

我们知道, DropBoxManager 和 DropBoxManagerService 交互的接口都定义在DropBoxManagerService.aidl。

interface IDropBoxManagerService {
    /**
     * @see DropBoxManager#addText
     * @see DropBoxManager#addData
     * @see DropBoxManager#addFile
     */

//三种添加日志的方式汇和成了这一个
    void add(in DropBoxManager.Entry entry);
    /** @see DropBoxManager#getNextEntry */

//如果tag关闭了,写入不了
    boolean isTagEnabled(String tag);

    /** @see DropBoxManager#getNextEntry */
    DropBoxManager.Entry getNextEntry(String tag, long millis);
}

                                                                    (代码3,来自DropBoxManagerService.aidl)

          看到DropBoxManagerService.aidl的定义,很简洁,只包含三个方法。

(4)DropBoxManagerService


/**
 * Creates an instance of managed drop box storage using the default dropbox
 * directory.
 *
 * @param context to use for receiving free space & gservices intents
 */
public DropBoxManagerService(final Context context) {
    this(context, new File("/data/system/dropbox"));
}

                                                         (代码4,来自com.android.server.DropBoxManagerService)

          SystemServer默认会以反射方式得到(代码4)中的构造方法,并以之实例化。第二个参数也就是我们日志的存放位置 /data/system/dropbox。

public void add(DropBoxManager.Entry entry) {
         。。。
        // If we have at least one block, compress it -- otherwise, just write
        // the data in uncompressed form.
        //以文件方式保存日志到指定的目录
        temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
        。。。

    FileOutputStream foutput = new FileOutputStream(temp);
        output = new BufferedOutputStream(foutput, bufferSize);

//文件过大会保持为压缩包
        if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
            output = new GZIPOutputStream(output);
            flags = flags | DropBoxManager.IS_GZIPPED;
        }
        //写入文件略

。。。
        long time = createEntry(temp, tag, flags);
        temp = null;

        final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
        dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
        dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
        if (!mBooted) {
            dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        }
        // Call sendBroadcast after returning from this call to avoid deadlock. In particular
       // the caller may be holding the WindowManagerService lock but sendBroadcast requires a
        // lock in ActivityManagerService. ActivityManagerService has been caught holding that
        // very lock while waiting for the WindowManagerService lock.

//发送有新日志生成的广播
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
    }

。。。。。。。。 

}

                                                  (代码5,来自com.android.server.DropBoxManagerService)

(5)自动瘦身

  DropBoxManagerService在一些条件下可以自动清理日志。以下是可配置的条件:

private static final int DEFAULT_AGE_SECONDS = 3 * 86400;//文件最长可存活时长为3天
private static final int DEFAULT_MAX_FILES = 1000;//最大dropbox文件个数为1000
private static final int DEFAULT_QUOTA_KB = 5 * 1024;//分配dropbox空间的最大值5M
private static final int DEFAULT_QUOTA_PERCENT = 10;//是指dropbox目录最多可占用空间比例10%
private static final int DEFAULT_RESERVE_PERCENT = 10;//是指dropbox不可使用的存储空间比例10%
private static final int QUOTA_RESCAN_MILLIS = 5000;//重新扫描retrim时长为5s

                                                       (代码6,来自com.android.server.DropBoxManagerService)

2.Crash异常如何被收集至DropBoxManagerService

    DBMS会收集各种系统异常日志,接下来以Crash异常为例进行分析。  

    由《应用内Crash异常日志收集(UncaughtExceptionHandler)》中的最后部分,有讲到,在RuntimeInit.java中的默认Crash拦截器内,会通过方法handleApplicationCrash将Crash信息报给ActivityManagerNative。

     ActivityManagerNative只提供接口功能,中间代码就不贴了,最终调的是AMS的handleApplicationCrash方法。

public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
    ProcessRecord r = findAppProcess(app, "Crash");
//获取进程名,用于生成tag
    final String processName = app == null ? "system_server"
            : (r == null ? "unknown" : r.processName);
    handleApplicationCrashInner("crash", r, processName, crashInfo);
}

/* Native crash reporting uses this inner version because it needs to be somewhat
 * decoupled from the AM-managed cleanup lifecycle
 */
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
        ApplicationErrorReport.CrashInfo crashInfo) {
    。。。
    addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
    。。。
}

                                                (代码7,来自com.android.server.am.ActivityManagerService)

       由上面可知,最终是调用addErrorToDropBox来实现向DropBoxManager写入日志的。接下来主要看看addErrorToDropBox的逻辑:

public void addErrorToDropBox(String eventType,
        ProcessRecord process, String processName, ActivityRecord activity,
        ActivityRecord parent, String subject,
        final String report, final File dataFile,
        final ApplicationErrorReport.CrashInfo crashInfo) {

   。。。
//获取DropBoxManager 服务
    final DropBoxManager dbox = (DropBoxManager)
            mContext.getSystemService(Context.DROPBOX_SERVICE);

    // Exit early if the dropbox isn't configured to accept this report type.
    //如果tag被关闭,那么不写入
    if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
    //生成log具体内容
    final StringBuilder sb = new StringBuilder(1024);
    appendDropBoxProcessHeaders(process, processName, sb);
    if (process != null) {
        sb.append("Foreground: ")
                .append(process.isInterestingToUserLocked() ? "Yes" : "No")
                .append("\n");
    }
    if (activity != null) {
        sb.append("Activity: ").append(activity.shortComponentName).append("\n");
    }
    if (parent != null && parent.app != null && parent.app.pid != process.pid) {
        sb.append("Parent-Process: ").append(parent.app.processName).append("\n");
    }
    if (parent != null && parent != activity) {
        sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n");
    }
    if (subject != null) {
        sb.append("Subject: ").append(subject).append("\n");
    }
    sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
    if (Debug.isDebuggerConnected()) {
        sb.append("Debugger: Connected\n");
    }
    sb.append("\n");


  //Do the rest in a worker thread to avoid blocking the caller on I/O
  // (After this point, we shouldn't access AMS internal data structures.)
  //在工作线程中执行其余操作,以避免阻塞调用程序的I/O操作
  //(在此之后,我们不应该访问AMS内部数据结构。)
    Thread worker = new Thread("Error dump: " + dropboxTag) {
        @Override
        public void run() {
            if (report != null) {
                sb.append(report);
            }
            String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
            int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
            int maxDataFileSize = DROPBOX_MAX_SIZE - sb.length()
                    - lines * RESERVED_BYTES_PER_LOGCAT_LINE;
            if (dataFile != null && maxDataFileSize > 0) {
                try {
                    sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
                                "\n\n[[TRUNCATED]]"));
                } catch (IOException e) {
                    Slog.e(TAG, "Error reading " + dataFile, e);
                }
            }
            if (crashInfo != null && crashInfo.stackTrace != null) {
                sb.append(crashInfo.stackTrace);
            }

            if (lines > 0) {
                sb.append("\n");

                // Merge several logcat streams, and take the last N lines
//合并几个logcat流,取最后N行
                InputStreamReader input = null;
                try {
                    java.lang.Process logcat = new ProcessBuilder(
                            "/system/bin/timeout", "-k", "15s", "10s",
                            "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
                            "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
                                    .redirectErrorStream(true).start();

                    try { logcat.getOutputStream().close(); } catch (IOException e) {}
                    try { logcat.getErrorStream().close(); } catch (IOException e) {}
                    input = new InputStreamReader(logcat.getInputStream());

                    int num;
                    char[] buf = new char[8192];
                    while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
                } catch (IOException e) {
                    Slog.e(TAG, "Error running logcat", e);
                } finally {
                    if (input != null) try { input.close(); } catch (IOException e) {}
                }
            }
            //调用(代码1)处提供的写入日志接口
            dbox.addText(dropboxTag, sb.toString());
        }
    };

    if (process == null) {
        // If process is null, we are being called from some internal code
        // and may be about to die -- run this synchronously.
//如果进程为空,那么直接在当前线程执行
        worker.run();
    } else {
//否则用新线程执行
        worker.start();
    }
}

                                                 (代码8,来自com.android.server.am.ActivityManagerService)

        至此,Crash异常通过(代码一)处逻辑被收集至DropBoxManagerService。

3.DropBoxManagerService收集那些数据

       图上是以我自身设备为例。

       DropBoxManagerService并没有将日志TAG提前定义死。我想这是为了方便后期扩展自定义的日志。

以下是一些常见的日志类型:

system_app_crash 系统app崩溃

system_app_anr 系统app无响应

data_app_crash 普通app崩溃

data_app_anr 普通app无响应

system_server_anr system进程无响应

system_server_watchdog system进程发生watchdog

system_server_crash system进程崩溃

system_server_native_crash system进程native出现崩溃

system_server_wtf system进程发生严重错误

system_server_lowmem system进程内存不足

SYSTEM_TOMBSTONE Native 进程的崩溃

SYSTEM_RECOVERY_LOG 系统恢复

StrictMode:

比正常模式检测得更严格, 通常用来监测不应当在主线程执行的网络, 文件等操作

内核错误相关:

SYSTEM_LAST_KMSG, 如果 /proc/last_kmsg 存在.
APANIC_CONSOLE, 如果 /data/dontpanic/apanic_console 存在.
APANIC_THREADS, 如果 /data/dontpanic/apanic_threads 存在

参考:Android DropBoxManager服务分析:https://blog.csdn.net/linliang815/article/details/72842311

           Android DropboxManager介绍:https://www.jianshu.com/p/f9174a8d0a10

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值