Pytanie Grupowy stos Java / Android śledzi unikatowe wiadra


Podczas rejestrowania śledzenia stosu dla nieobsługiwanych wyjątków w Javie lub systemie Android (na przykład za pośrednictwem ACRA), zwykle śledzenie stosu jest zwykłym długim ciągiem.

Teraz wszystkie usługi oferujące raportowanie i analizę awarii (np. Konsola programisty Google Play, Crashlytics) grupują te stosy w unikalne segmenty. Jest to oczywiście pomocne - w przeciwnym razie na liście mogą znajdować się dziesiątki tysięcy raportów o awariach, ale tylko kilkanaście z nich może być unikatowych.

Przykład:

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)
...

Powyższy ślad stosu może pojawić się w wielu wariantach, np. klasy platformy jak AsyncTask mogą pojawiać się ze zmiennymi numerami linii ze względu na różne wersje platform.

Jaka jest najlepsza technika, aby uzyskać unikalny identyfikator dla każdego raportu awarii?

Jasne jest to, że przy każdej nowej wersji aplikacji, którą publikujesz, raporty o awariach powinny być traktowane oddzielnie, ponieważ skompilowane źródło jest inne. W ACRA możesz rozważyć użycie tego pola APP_VERSION_CODE.

Ale poza tym, w jaki sposób identyfikujesz raporty z wyjątkowymi przyczynami? Biorąc pierwszą linię i szukając pierwszego wystąpienia niestandardowej (nie-platformowej) klasy i sprawdzając numer pliku i linii?


11
2018-03-15 16:43


pochodzenie


Dobre pytanie. Jeśli uzyskasz dobrą odpowiedź, możemy złożyć ją w ACRA - William
@William To byłoby wspaniale! Możesz sprawdzić, czy któraś z odpowiedzi tutaj lub moja Biblioteka praca dla ACRA. Dla mnie żadna z odpowiedzi nie zadziałała "po wyjęciu z pudełka", ale pomysły były wystarczające, aby moja biblioteka działała: JavaCrashId.from(exception) plus kod wersji aplikacji wydaje się działać dla mnie jako odcisk palca. - caw


Odpowiedzi:


Jeśli szukasz sposobu na uniknięcie wyjątków przy ignorowaniu klas specyficznych dla systemu operacyjnego, możesz iterować getStackTrace() i miesza każdą ramkę, która nie pochodzi ze znanej klasy OS. Myślę, że sensowne jest dodanie wyjątku przyczyny do skrótu. Może tworzyć fałszywe negatywy, ale byłoby to lepsze niż posiadanie fałszywych trafień, jeśli wyjątek hash jest czymś generycznym 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;
    }
}

5
2018-03-24 00:31



Dzięki! Chociaż to nie działa tak jak jest (np continue w pętli zagnieżdżonej) okazał się najcenniejszym punktem początkowym. Stworzyłem bibliotekę dla Javy i PHP z pomysłami z twojej odpowiedzi: github.com/delight-im/Java-Crash-ID - caw
Dzięki. Zaktualizowałem kod na wypadek, gdyby ktoś inny próbował go użyć. - kichik


Myślę, że znasz już odpowiedź, ale prawdopodobnie szukasz potwierdzenia. Wspomniałeś już o tym ...

Jeśli zobowiążesz się do wyraźnego rozróżnienia między Wyjątkiem a Przyczyną / Stosunkiem, wtedy odpowiedź może być łatwiejsza do uchwycenia.

Aby jeszcze raz sprawdzić odpowiedź, przejrzałem raporty o awariach aplikacji na Androida w Crittercism - firmie analitycznej, z którą szanuję i pracuję. (Btw, pracuję dla PayPala i prowadziłem jeden z ich produktów na Androida, a Crittercism był jednym z naszych preferowanych sposobów zgłaszania i analizowania awarii).

To, co zobaczyłem, było dokładnie tym, co sugerowałeś w swoim pytaniu. Ten sam wyjątek występujący na tym samym wierszu kodu (co oznacza tę samą wersję aplikacji), ale w różnych wersjach platformy (co oznacza różne kompilacje Java / Android), jest rejestrowany jako dwa wyjątkowe awarie. I myślę, że tego właśnie szukasz.

Chciałbym skopiować kopie raportów o awarii tutaj, ale myślę, że zostałbym zwolniony za to :) Zamiast tego dam wam ocenzurowane dane:

ZA java.lang.NullPointerException wydarzyło się w ICantSayTheControllerName.java klasa na linii 117 w wersji 2.4.8 naszej aplikacji; ale w dwóch różnych (unikalnych) grupach tych awarii stwierdza, że ​​dla użytkowników korzystających z urządzenia Android 4.4.2 przyczyna była włączona android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2540) jednak dla użytkowników korzystających z Androida 4.4.4 przyczyna była włączona android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404). * zanotuj subtelne różnice w liczbie wierszy w ActivityThread.java z powodu innej kompilacji platformy.

Zapewniło to, że numer wersji aplikacji, wyjątek i przyczyna / stacktrace to trzy wartości określające unikalny identyfikator konkretnej awarii; innymi słowy, grupowanie raportów o awariach odbywa się w oparciu o unikalne wartości tych trzech informacji. Prawie chcę utworzyć bazę danych i analogię klucza głównego, ale dygresja.

Jako przykład podjąłem także Crittercism, ponieważ to właśnie robią; są prawie standardem branżowym; Uważam, że to, co robią, jest co najmniej na równi z innymi liderami w raportach i analizach awarii. (i nie, nie pracuję dla nich).

Mam nadzieję, że ten rzeczywisty przykład wyjaśni lub utwierdzi twoje myśli.

-serkan


4
2018-03-19 07:03



Dzięki, serkan! Masz rację, byłem już całkiem pewien, że to są czynniki, które składają się na unikalny raport o awariach. Tak więc pytanie dotyczyło raczej tego, jak faktycznie otrzymać te wartości, które decydują, czy raport o awarii jest wyjątkowy, czy nie. Oznacza to: różne wersje aplikacji generują różne raporty o awariach, to oczywiste. Ale od samego śladu stosu, jak można wywnioskować, czy ten ślad jest nowy, czy nie? Jakie linie na które patrzysz? Te na górze? Lub tych w Caused by ...? A może tylko te z twojej paczki? - caw


Wiem, że to nie srebrna kula, ale tylko moje 2 centy:

  1. wszystkie wyjątki w moich projektach się przedłużają abstract class AppException
  2. wszystkie inne wyjątki platformy (RuntimeException, IOException ...) są pakowane przez AppException zanim raport zostanie wysłany lub zalogowany do pliku.

Klasa AppException wygląda następująco:

public abstract class AppException extends Exception {

    private AppClientInfo appClientInfo; // BuildVersion, AndroidVersion etc...

    [...] // other stuff
}
  1. następnie tworzę ExceptionReport od AppException i wyślij go na mój serwer (jako json / xml) ExceptionReport zawiera następujące dane:

    • appClientInfo
    • typ wyjątku  // ui, baza danych, usługa internetowa, preferencje ...
    • pochodzenie  // pobierz pochodzenie ze stacktrace: MainActivity: 154 
    • stacktrace jako html  // wszystkie linie zaczynające się od "com.mycompany.myapp" są podświetlone. 

Teraz po stronie serwera mogę sortować, grupować (ignorować duplikaty) i publikować raport. Jeśli typ wyjątku jest krytyczny, można utworzyć nowy bilet.


Jak rozpoznać duplikaty?

Przykład:

  • appClientInfo: "android" : "4.4.2", "appversion" : "2.0.1.542" 
  • typ wyjątku: "type" : "database"
  • pochodzenie: "SQLiteProvider.java:423"

Teraz mogę obliczyć unikalny identyfikator w ten naiwny sposób:

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

0
2018-03-24 18:28



Dobra odpowiedź, to wyjaśnia głęboko