需求
将系统异常日志上报到服务器便于开发统计分析问题。
实现
在DropBoxManagerService.java 中add 添加一个文件时,进行拦截处理,上传到服务器。
public void add(DropBoxManager.Entry entry) { temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); + if (temp != null) + temp.setReadable(true, false); + long time = createEntry(temp, tag, flags); + if (temp != null) { + uploadLogFile(tag, time, flags); + } }
完整修改记录:
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 ListENABLED_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); + } 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() + + "nn"; + } + + 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; + } }
控制是否输出DropBox文件
public void add(DropBoxManager.Entry entry) { ... int dropBoxEnable = Settings.Global.getInt(mContentResolver, DROPBOX, DROPBOX_DISABLED); if (DROPBOX_DISABLED == dropBoxEnable) { return; } ...
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中, 并d出 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中,并d出 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(ArrayListmemInfos) { ... 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(); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)