欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-07-02 09:17:13
DropBoxManagerService(简称DBMS) 统一收集管理各类系统关键日志和异常日志。1.DropBoxManager &DropBoxManagerService简介 Android系统启动过程SystemServer进程时,在startOtherServices()过程会启动DBMS服务。和大多数系统服务一样,DropBoxManager 和 DropBoxManagerService 通过aidl实现跨进程通信。客户端可以通过DropBoxMa......

 

      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收集那些数据

【Android】【源码分析】系统各类异常日志收集服务(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

 

本文地址:https://blog.csdn.net/a953210725/article/details/107562158