当在Java或Android中记录未经处理的异常的堆栈跟踪时(例如通过ACRA),您通常会以纯长字符串的形式获得堆栈跟踪。
现在,所有提供崩溃报告和分析的服务(例如Google Play开发者控制台,Crashlytics)都会将这些堆栈跟踪分组到唯一的存储桶中。这显然很有帮助 - 否则,您的列表中可能有数以万计的崩溃报告,但其中只有十几个可能是唯一的。
例:
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:200)
at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
at java.lang.Thread.run(Thread.java:1027)
Caused by: java.lang.ArrayIndexOutOfBoundsException
at com.my.package.MyClass.i(SourceFile:1059)
...
上面的堆栈跟踪可能以多种变体出现,例如,由于平台版本不同,像AsyncTask
这样的平台类可能会出现不同的行号。
获取每个崩溃报告的唯一标识符的最佳技术是什么?
显而易见的是,对于您发布的每个新应用程序版本,崩溃报告都应单独处理,因为编译的源代码是不同的。在 ACRA 中,您可以考虑使用字段APP_VERSION_CODE
。
但除此之外,您如何识别具有独特原因的报告?通过获取第一行并搜索自定义(非平台)类的第一个匹配项并查找文件和行号?
如果您正在寻找一种方法来获取异常的唯一值,同时忽略特定于操作系统的类,则可以迭代getStackTrace()
并哈希处理不是来自已知操作系统类的每个帧。我认为将原因异常添加到哈希中也是有意义的。它可能会产生一些漏报,但如果您散列的异常是像 ExecutionException
这样的通用异常,那将比误报更好。
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
public class Test
{
// add more system packages here
private static final String[] SYSTEM_PACKAGES = new String[] {
"java.",
"javax.",
"android."
};
public static void main( String[] args )
{
Exception e = new Exception();
HashCode eh = hashApplicationException( e );
System.out.println( eh.toString() );
}
private static HashCode hashApplicationException( Throwable exception )
{
Hasher md5 = Hashing.md5().newHasher();
hashApplicationException( exception, md5 );
return md5.hash();
}
private static void hashApplicationException( Throwable exception, Hasher hasher )
{
for( StackTraceElement stackFrame : exception.getStackTrace() ) {
if( isSystemPackage( stackFrame ) ) {
continue;
}
hasher.putString( stackFrame.getClassName(), Charsets.UTF_8 );
hasher.putString( ":", Charsets.UTF_8 );
hasher.putString( stackFrame.getMethodName(), Charsets.UTF_8 );
hasher.putString( ":", Charsets.UTF_8 );
hasher.putInt( stackFrame.getLineNumber() );
}
if( exception.getCause() != null ) {
hasher.putString( "...", Charsets.UTF_8 );
hashApplicationException( exception.getCause(), hasher );
}
}
private static boolean isSystemPackage( StackTraceElement stackFrame )
{
for( String ignored : SYSTEM_PACKAGES ) {
if( stackFrame.getClassName().startsWith( ignored ) ) {
return true;
}
}
return false;
}
}
我想你已经知道答案了,但你也许正在寻找一个确认。你已经暗示过了...
如果您承诺明确区分异常及其原因/堆栈跟踪,那么答案可能会变得更容易掌握。
为了仔细检查我的答案,我查看了Crittercism中的Android应用程序崩溃报告 - 这是一家我尊重并与之合作的分析公司。(顺便说一句,我在PayPal工作,我曾经领导过他们的一个Android产品,Crittercism是我们报告和分析崩溃的首选方式之一)。
我所看到的正是你在问题中暗示的。 在同一行代码(意味着相同的应用程序版本)上发生的相同异常,但是在不同版本的平台(意味着不同的Java/Android编译)上被记录为两个独特的崩溃。我认为这就是你要找的。
我希望我可以在这里复制粘贴崩溃报告,但我想我会因此而被解雇:)相反,我会给你审查的数据:
在我们应用程序的2.4.8版本第117行ICantSayTheControllerName.java
类中发生了java.lang.NullPointerException
;但是在此崩溃状态的两个不同(唯一)分组中,对于那些使用Android 4.4.2设备的用户,原因在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2540)
但是对于那些使用Android 4.4.4的用户,原因是android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404)
。 *请注意,由于平台的编译不同.java ActivityThread中行号的细微差异。
这确保了应用程序版本号、异常和原因/堆栈跟踪是构成特定崩溃唯一标识符的三个值;换句话说,崩溃报告的分组是根据这三个信息的唯一值完成的。 我几乎想做一个数据库和一个主键类比,但我跑题了。
另外,我以 Crittercism 为例,因为这就是他们所做的; 它们几乎是一个行业标准;我相信他们所做的至少与崩溃报告和分析方面的其他领导者相当。 (不,我不为他们工作)。
我希望这个真实世界的例子能澄清或证实你的想法。
-塞尔坎
我知道这不是灵丹妙药,而只是我的2美分:
- "我的项目"中的所有异常都扩展
abstract class AppException
- 所有其他平台异常(RuntimeException,IOException...)在报告发送或记录到文件之前由
AppException
包装。
AppException 类如下所示:
public abstract class AppException extends Exception {
private AppClientInfo appClientInfo; // BuildVersion, AndroidVersion etc...
[...] // other stuff
}
然后我从
AppException
创建一个ExceptionReport
并将其发送到我的服务器(作为 JSON/XML)异常报告包含以下数据:- appClientInfo
- 异常类型//UI, 数据库, 网络服务, 首选项...
- origin//从堆栈跟踪获取源: 主活动:154
- Stacktrace 为 HTML//所有以"com.mycompany.myapp"开头的行都会突出显示。
现在在服务器端,我可以排序、分组(忽略重复项)并发布报告。如果异常类型很严重,则可以创建新票证。
如何识别重复项?
例:
- appClientInfo:
"android" : "4.4.2", "appversion" : "2.0.1.542"
- 异常类型:
"type" : "database"
- 产地:
"SQLiteProvider.java:423"
现在我可以用这种天真的方式计算唯一 ID:
UID = HASH("4.4.2" + "2.0.1.542" + "database" + "SQLiteProvider.java:423")