需求
将系统异常日志上报到服务器便于开发统计分析问题。
实现
在DropBoxManagerService.java 中add 添加一个文件时,进行拦截处理,上传到服务器。
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index a44cb72..eeccd80 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -18,6 +18,7 @@ package com.android.server;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,9 +33,11 @@ import android.os.Handler;
import android.os.Message;
import android.os.StatFs;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.Time;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.os.IDropBoxManagerService;
@@ -49,7 +52,11 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;
@@ -69,9 +76,22 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
// mHandler 'what' value.
private static final int MSG_SEND_BROADCAST = 1;
+ private static final int MSG_UPDATE_DATABASE = 2;
private static final boolean PROFILE_DUMP = false;
+ private static final String DROPBOX = "dropbox";
+ private static final int DROPBOX_DISABLED = 0;
+ private static final String mUploadUrl = "xxx";
+ private static final Uri CONTENT_URI = Uri.parse("content://xxxxx");
+ private final static String UNKNOWN_MAC_ADDRESS = "00:00:00:00:00:00";
+ private static final List<String> ENABLED_TAGS = Arrays.asList(
+ "system_server_crash",
+ "data_app_crash",
+ "system_server_watchdog");
+ private static final int EARLIEST_SUPPORTED_TIME = 2007;
+ private static final int LAST_MODIFIED = 7 * 24 * 60 * 60 * 1000;
+
// TODO: This implementation currently uses one file per entry, which is
// inefficient for smallish entries -- consider using a single queue file
// per tag (or even globally) instead.
@@ -103,8 +123,9 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ if (intent != null && Intent.ACTION_FUNTV_2_BOOT_COMPLETED.equals(intent.getAction())) {
mBooted = true;
+ uploadLogFile();
return;
}
@@ -143,7 +164,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+ filter.addAction(Intent.ACTION_COMPLETED);
context.registerReceiver(mReceiver, filter);
mContentResolver.registerContentObserver(
@@ -161,6 +182,14 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
if (msg.what == MSG_SEND_BROADCAST) {
mContext.sendBroadcastAsUser((Intent)msg.obj, UserHandle.OWNER,
android.Manifest.permission.READ_LOGS);
+ } else if (msg.what == MSG_UPDATE_DATABASE) {
+ try {
+ String path = (String)msg.obj;
+ ContentValues cv = new ContentValues();
+ cv.put("path", path);
+ cv.put("url", mUploadUrl);
+ mContext.getContentResolver().insert(CONTENT_URI, cv);
+ } catch (Exception e){}
}
}
};
@@ -215,6 +244,10 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
flags = flags | DropBoxManager.IS_GZIPPED;
}
+ byte[] header = getFileHeader().getBytes();
+ output.write(header, 0, header.length);
+ output.flush();
+
do {
output.write(buffer, 0, read);
@@ -242,10 +275,13 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
}
} while (read > 0);
+ if (temp != null)
+ temp.setReadable(true, false);
+
long time = createEntry(temp, tag, flags);
temp = null;
- final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
+ /*final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
if (!mBooted) {
@@ -255,7 +291,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
// 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));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));*/
} catch (IOException e) {
Slog.e(TAG, "Can't write: " + tag, e);
} finally {
@@ -265,6 +301,42 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
}
}
+ private String getFileHeader() {
+ return "Version:" + getRomVersion()
+ + "\n"
+ + "ChipType:" + getChipType()
+ + "\n"
+ + "Mac:" + getMac()
+ + "\n"
+ + "Time:" + new Date().toString()
+ + "\n\n";
+ }
+
+ private void uploadLogFile() {
+ try {
+ if (mStatFs == null) init();
+ Calendar calendar = Calendar.getInstance();
+ File[] files = mDropBoxDir.listFiles();
+ for (File file : files) {
+ calendar.setTimeInMillis(file.lastModified());
+ if (calendar.get(Calendar.YEAR) == EARLIEST_SUPPORTED_TIME) continue;
+ if (System.currentTimeMillis() < file.lastModified() + LAST_MODIFIED) {
+ updateDatabase(file.getAbsolutePath());
+ long oldTime = System.currentTimeMillis() - LAST_MODIFIED;
+ file.setLastModified(oldTime > 0 ? oldTime : 0);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void updateDatabase(String path) {
+ Message message = mHandler.obtainMessage(MSG_UPDATE_DATABASE);
+ message.obj = path;
+ mHandler.sendMessage(message);
+ }
+
public boolean isTagEnabled(String tag) {
final long token = Binder.clearCallingIdentity();
try {
@@ -501,7 +573,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
this.tag = tag;
this.timestampMillis = timestampMillis;
this.flags = flags;
- this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis +
+ this.file = new File(dir, getFilePrefix() + "@" + Uri.encode(tag) + "@" + timestampMillis +
((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""));
@@ -522,7 +594,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
this.tag = tag;
this.timestampMillis = timestampMillis;
this.flags = DropBoxManager.IS_EMPTY;
- this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost");
+ this.file = new File(dir, getFilePrefix() + "@" + Uri.encode(tag) + "@" + timestampMillis + ".lost");
this.blocks = 0;
new FileOutputStream(this.file).close();
}
@@ -537,6 +609,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
String name = file.getName();
+ int prefixIndex = name.indexOf('@');
int at = name.lastIndexOf('@');
if (at < 0) {
this.tag = null;
@@ -546,7 +619,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
}
int flags = 0;
- this.tag = Uri.decode(name.substring(0, at));
+ this.tag = Uri.decode(name.substring(prefixIndex + 1, at));
if (name.endsWith(".gz")) {
flags |= DropBoxManager.IS_GZIPPED;
name = name.substring(0, name.length() - 3);
@@ -592,6 +665,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {
throw new IOException("Can't mkdir: " + mDropBoxDir);
}
+ mDropBoxDir.setExecutable(true, false);
try {
mStatFs = new StatFs(mDropBoxDir.getPath());
mBlockSize = mStatFs.getBlockSize();
@@ -676,6 +750,8 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
tagFiles.blocks -= late.blocks;
}
if ((late.flags & DropBoxManager.IS_EMPTY) == 0) {
+ if (late.timestampMillis > (t + 60 * 1000))
+ continue;
enrollEntry(new EntryFile(
late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize));
} else {
@@ -704,13 +780,22 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
int maxFiles = Settings.Global.getInt(mContentResolver,
Settings.Global.DROPBOX_MAX_FILES, DEFAULT_MAX_FILES);
long cutoffMillis = System.currentTimeMillis() - ageSeconds * 1000;
+ int renameCounts = 0;
while (!mAllFiles.contents.isEmpty()) {
EntryFile entry = mAllFiles.contents.first();
if (entry.timestampMillis > cutoffMillis && mAllFiles.contents.size() < maxFiles) break;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(entry.timestampMillis);
+ int year = calendar.get(Calendar.YEAR);
FileList tag = mFilesByTag.get(entry.tag);
if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;
if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+ if (year == EARLIEST_SUPPORTED_TIME && renameCounts < 5) {
+ rename(entry);
+ renameCounts++;
+ continue;
+ }
if (entry.file != null) entry.file.delete();
}
@@ -783,4 +868,42 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
return mCachedQuotaBlocks * mBlockSize;
}
+
+ private void rename(EntryFile entry) {
+ int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+ if (currentYear == EARLIEST_SUPPORTED_TIME) return;
+ String tag = entry.tag;
+ int flags = entry.flags;
+ long currentTimeMillis = System.currentTimeMillis();
+ entry.file.setLastModified(currentTimeMillis);
+ File empty = new File(mDropBoxDir, getFilePrefix() + "@" + Uri.encode(tag) + "@" + currentTimeMillis +
+ ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
+ ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""));
+ entry.file.renameTo(empty);
+ }
+
+ private static String getFilePrefix() {
+ return getMac() + "_" + getChipType() + "_" + getRomVersion();
+ }
+
+ private static String getMac() {
+ String mac = UNKNOWN_MAC_ADDRESS;
+ try {
+ mac = SystemProperties.get("ro.boot.mac");
+ if (!TextUtils.isEmpty(mac)) {
+ mac = mac.replaceAll(":", "").toLowerCase();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return mac;
+ }
+
+ private static String getChipType() {
+ return SystemProperties.get("persist.sys.chiptype", "unknown");
+ }
+
+ private static String getRomVersion() {
+ return android.os.Build.VERSION.INCREMENTAL;
+ }
}
DropBoxManager
DropBoxManager 是 Android 用来持续化存储系统数据的机制, 主要用于记录 Android 运行过程中, 内核, 系统进程, 用户进程等出现严重问题时的 log, 可以认为这是一个可持续存储的系统级别的 logcat.
console:/data/system/dropbox # ls -l
total 848
-rw------- 1 system system 584 2021-12-20 11:29 SYSTEM_AUDIT@1639626755142.txt
-rw------- 1 system system 584 2021-12-16 11:52 SYSTEM_BOOT@1639626761116.txt
-rw------- 1 system system 1176 2021-12-20 11:29 SYSTEM_LAST_KMSG@1639626755141.txt
-rw------- 1 system system 15870 2021-12-20 11:58 SYSTEM_TOMBSTONE@1639626755154.txt.gz
-rw------- 1 system system 2792 2021-12-20 11:22 system_app_strictmode@1639626755091.txt
-rw------- 1 system system 960 2021-12-20 11:26 system_server_strictmode@1639626755092.txt
-rw------- 1 system system 1625 2021-12-16 11:52 system_server_strictmode@1639626761115.txt
-rw------- 1 system system 1281 2021-12-20 11:29 system_server_wtf@1639626755114.txt
-rw------- 1 system system 1174 2021-12-16 11:52 system_server_wtf@1639626761105.txt
console:/data/system/dropbox #
DropBox文件分类及生成
1)crash
Java层遇到未被 catch 的例外时, ActivityManagerService 会记录一次 crash到 DropBoxManager中, 并弹出 Force Close对话框提示用户
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
void handleApplicationCrashInner(String eventType, ProcessRecord r, String
...
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
crashApplication(r, crashInfo);
...
}
2)anr
当应用程序的主线程(UI线程)长时间未能得到响应时, ActivityManagerService 会记录一次 anr到 DropBoxManager中,并弹出 Application Not Responding对话框提示用户.
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
...
addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null);
...
}
3)wtf
应用程序可以在代码中用来主动报告一个不应当发生的情况. 依赖于系统设置, 这个函数会通过 ActivityManagerService 增加一个 wtf 记录到 DropBoxManager中, 并/或终止当前应用程序进程.
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
ProcessRecord handleApplicationWtfInner() {
...
addErrorToDropBox("wtf", r, processName, null, null, tag, null, null, crashInfo);
...
}
4)strict_mode
在比正常模式检测得更严格, 通常用来监测不应当在主线程执行的网络, 文件等操作. 任何 StrictMode 违例都会被 ActivityManagerService 在 DropBoxManager 中记录为一次 strict_mode违例。
public void handleApplicationStrictModeViolation(
IBinder app,
int violationMask,
StrictMode.ViolationInfo info) {
...
logStrictModeViolationToDropBox(r, info);
}
5)lowmem
在内存不足的时候, Android 会终止后台应用程序来释放内存, 但如果没有后台应用程序可被释放时,ActivityManagerService 就会在 DropBoxManager 中记录一次 lowmem.
void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) {
...
addErrorToDropBox("lowmem", null, "system_server", null,
null, tag.toString(), dropBuilder.toString(), null, null);
...
}
6)watchdog
WatchDog 监测到系统进程(system_server)出现问题, 会增加一条 watchdog记录到 DropBoxManager 中, 并终止系统进程的执行.
frameworks/base/services/core/java/com/android/server/Watchdog.java
Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
public void run() {
mActivity.addErrorToDropBox(
"watchdog", null, "system_server", null, null,
name, null, stack, null);
}
};
dropboxThread.start();
7)netstats_error
8)BATTERY_DISCHARGE_INFO
9)SYSTEM_TOMBSTONE
10)System Serve 启动完成后的检测
包括:
A.每次开机都会增加一条 SYSTEM_BOOT 记录.
B. System Server 重启
C. Kernel Panic
D.系统恢复
private void logBootEvents(Context ctx) throws IOException {
if (recovery != null && db != null) {
db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);//系统恢复
}
if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
String now = Long.toString(System.currentTimeMillis());
SystemProperties.set("ro.runtime.firstboot", now);
if (db != null) db.addText("SYSTEM_BOOT", headers);//SYSTEM_BOOT
// Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
//
addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
-LOG_SIZE, "SYSTEM_LAST_KMSG");
addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
-LOG_SIZE, "SYSTEM_RECOVERY_LOG");
addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
-LOG_SIZE, "APANIC_CONSOLE");
addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
-LOG_SIZE, "APANIC_THREADS");
} else {
if (db != null) db.addText("SYSTEM_RESTART", headers);
}
// Scan existing tombstones (in case any new ones appeared)
File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(),
LOG_SIZE, "SYSTEM_TOMBSTONE");
}
// Start watching for new tombstone files; will record them as they occur.
// This gets registered with the singleton file observer thread.
sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
@Override
public void onEvent(int event, String path) {
try {
String filename = new File(TOMBSTONE_DIR, path).getPath();
addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
}
}
};
sTombstoneObserver.startWatching();
}