将Java/Android堆栈跟踪分组到唯一存储桶中

caw*_*caw 12 java android stack-trace acra

在Java或Android中记录未处理异常的堆栈跟踪时(例如通过ACRA),通常会将堆栈跟踪视为普通长字符串.

现在,所有提供崩溃报告和分析的服务(例如Google Play Developer Console,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)
...
Run Code Online (Sandbox Code Playgroud)

上面的堆栈跟踪可能出现在多个变体中,例如,AsyncTask由于不同的平台版本,平台类可能会出现不同的行号.

为每个崩溃报告获取唯一标识符的最佳技术是什么?

很清楚的是,对于您发布的每个新应用程序版本,崩溃报告应该分开处理,因为编译的源代码不同.在ACRA中,您可以考虑使用该字段APP_VERSION_CODE.

但除此之外,您如何识别具有独特原因的报告?通过获取第一行搜索第一次出现的自定义(非平台)类并查找文件和行号?

kic*_*hik 5

如果您正在寻找一种方法来获取异常的唯一值而忽略特定于操作系统的类,则可以迭代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;
    }
}
Run Code Online (Sandbox Code Playgroud)