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

将Java/Android堆栈跟踪分组到唯一的存储桶中,java,android,stack-trace,acra,Java,Android,Stack Trace,Acra,在Java或Android中记录未处理异常的堆栈跟踪时(例如通过ACRA),通常会将堆栈跟踪作为一个普通的长字符串 现在,所有提供崩溃报告和分析的服务(如Google Play开发者控制台、Crashlytics)都将这些堆栈跟踪分组到唯一的存储桶中。这显然很有帮助——否则,您的列表中可能有成千上万的崩溃报告,但其中可能只有十几个是唯一的 例如: java.lang.RuntimeException: An error occured while executing doInBackground

在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中,您可以考虑使用字段<代码> AppyVulnOracle代码< /C> > < /P>
但除此之外,如何识别具有独特原因的报告?通过使用第一行并搜索自定义(非平台)类的第一个匹配项并查找文件和行号?

我认为您已经知道答案,但您可能正在寻找确认。你已经暗示过了

如果您致力于明确区分异常及其原因/堆栈跟踪,那么答案可能会变得更容易掌握

为了再次核实我的答案,我在Critercism中查看了我们的Android应用程序崩溃报告,Critercism是一家我尊重并与之合作的分析公司。(顺便说一句,我为PayPal工作,曾经领导过他们的一款Android产品,Critercism是我们报告和分析崩溃的首选方式之一)

我看到的正是你在问题中暗示的相同的异常发生在同一行代码上(意味着相同的应用程序版本),但在不同版本的平台上(意味着不同的Java/Android编译),被记录为两个独特的崩溃。我想这就是您要寻找的

我希望我能复制并粘贴坠机报告,但我想我会因此被解雇:)相反,我会给你经过审查的数据:

java.lang.NullPointerException
发生在我们的应用程序2.4.8版本的第117行的
ICANTsayteControllerName.java
类中;但在两种不同(唯一)的崩溃状态分组中,对于使用安卓4.4.2设备的用户,原因是
Android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2540)
但是对于使用安卓4.4.4的用户,原因是
Android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404)
*请注意,由于平台的编译不同,ActivityThread.java中的行号存在细微差异

这确保了应用程序版本号、异常和原因/堆栈跟踪是构成特定崩溃唯一标识符的三个值;换句话说,崩溃报告的分组是基于这三个信息的唯一值完成的。我几乎想做一个数据库和主键类比,但我离题了

<> P>也是,我把CRITTICCIST作为一个例子,因为这是他们所做的,它们几乎是一个行业标准;我相信他们做的事情至少和其他领导在碰撞报告和分析中是一致的。(而且我不为他们工作)。 我希望这个真实的例子能澄清或证实你的想法


-serkan

如果您正在寻找一种方法来获取异常的唯一值,同时忽略操作系统特定的类,那么您可以迭代
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;
    }
}

我知道那不是银弹,只是我的2美分:

  • 我的项目中的所有异常都扩展了
    抽象类AppException
  • 在将报告发送或记录到文件之前,所有其他平台异常(RuntimeException、IOException…)都由
    AppException
    包装
  • AppException类如下所示:

    public abstract class AppException extends Exception {
    
        private AppClientInfo appClientInfo; // BuildVersion, AndroidVersion etc...
    
        [...] // other stuff
    }
    
  • 然后我从
    AppException
    创建一个
    ExceptionReport
    ,并将其发送到我的服务器(作为json/xml) 例外报告包含以下数据:

    • appClientInfo
    • 异常类型//用户界面、数据库、Web服务、首选项
    • origin//从stacktrace:main获取origin活动:154
    • stacktrace作为html//所有以“com.mycompany.myapp”开头的行都高亮显示
  • 现在,在服务器端,我可以排序、分组(忽略重复项)并发布报告。如果异常类型很关键,则可以创建新的记录单


    我如何识别重复的

    例如:

    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)
    ...
    
    • appClientInfo:
      “android”:“4.4.2”,“appversion”:“2.0.1.542”
    • 异常类型:
      “类型”:“数据库”
    • 来源:
      “SQLiteProvider.java:423”
    现在我可以用这种简单的方式计算唯一ID:

    UID = HASH("4.4.2" + "2.0.1.542" + "database" + "SQLiteProvider.java:423")
    

    好问题。如果你得到一个好答案,我们可以把它折叠成ACRA@William那太好了!您可以检查这里的任何答案或是否适用于ACRA。对我来说,没有一个答案是“开箱即用”的,但这些想法足以让我的库工作:
    JavaCrashId.from(例外)
    加上应用程序版本代码似乎对我来说是一个崩溃指纹。谢谢,serkan!你是对的,我已经很确定这些因素是唯一的崩溃报告的组成部分。因此问题更多的是如何真正获得这些