Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Google Analytics v4 to report uncaught exceptions (crashes) for my app?

I've been banging my head against a wall for hours on this one. The v4 docs for Google Analytics specify 2 different ways of getting your app to report uncaught exceptions. I can get neither to work. In both cases I see lines like this in LogCat when I trigger the uncaught exception to occur in my app (using dummy names for my own code):

08-17 17:33:30.248: V/GAV4(8968): Thread[main,5,main]: Tracking Exception: MyException (@MyClass:myMethod:143) {main}
08-17 17:33:30.248: V/GAV4(8968): Thread[main,5,main]: Dispatch call queued. Dispatch will run once initialization is complete.
08-17 17:33:30.248: V/GAV4(8968): Thread[main,5,main]: Passing exception to original handler.

...followed by the stacktrace for my exception and then finally:

08-17 17:33:44.282: I/Process(8968): Sending signal. PID: 8968 SIG: 9

From LogCat it looks like GA never actually dispatches the exception to Google's servers!

If someone has managed to get uncaught exceptions being reported in their Google Analytics Console it would be fantastic if they could share with us how they did it, please. I've seen other people on SO with queries like this (e.g., here and here) but no confirmation that uncaught exceptions are being reported.

I've got screen views showing up in the GA Console so I must be doing something right. But not uncaught exceptions. I'm assuming one should be looking for them under Behavior > Crashes and Exceptions, and I've set the end date to include today (it seems to be set to yesterday by default). Finally, in my analytics_global_config.xml I have:

<integer name="ga_dispatchPeriod">1</integer>

because otherwise the default is 1800 seconds, in which case I assume exceptions would not be seen in the GA console for at least 30 minutes after they occurred. ga_dryRun is false too.

like image 952
snark Avatar asked Aug 17 '14 17:08

snark


People also ask

How do I know if Google Analytics 4 is working?

Just to go Insights » Reports and click on the Realtime tab. Now, if you see there are active users on your site, then it means Google Analytics is tracking your visitors.

Can Google Analytics track apps?

With easy-to-use SDKs and reports designed with app developers in mind, Google Analytics for Mobile Apps enables you to: Understand the number of users in your app, their characteristics, and where they come from. Measure what actions your users are taking. Measure in-app payments and revenue.

What can Google Analytics 4 do?

Google Analytics 4 is an analytics service that enables you to measure traffic and engagement across your websites and apps. This documentation provides implementation instructions and reference materials geared towards a developer audience.

Is Google Analytics 4 still in beta?

Updated: October 11th, 2022. In late 2020, Google renamed its App+Web properties to Google Analytics 4 and officially graduated from beta (at least, that's what Google is saying).


2 Answers

As @xitx kindly pointed out, it looks like there's a bug in later versions of the Google Play Services library installed on people's devices. Because when I ran my existing code on an emulator for a relatively old device (API 9, and presumably running an old Google Play Services library) the crash did get reported to GA's console automatically. Here's what LogCat said when the crash occurred:

08-31 12:27:59.522: V/GAV4(335): Thread[GAThread,5,main]: Sending hit to store  PATH: https:  PARAMS: ul=en-us,  ht=1409484468454,  sr=480x800,  a=746864705,  sf=100.0,  aid=com.redula.vsavings,  cid=32da19a1-7e15-4c83-aaa9-f3f1d502b775,  av=1.2,  v=1,  t=exception,  an=Savings Organiser,  tid=UA-XXXXXXXX,  exd=NullPointerException (@HomeActivity:connectToInAppBillingService:202) {main},  _u=.2nKKhAAAL,  exf=1,  
08-31 12:27:59.622: V/GAV4(335): Thread[GAThread,5,main]: PowerSaveMode initiated.
08-31 12:27:59.652: V/GAV4(335): Thread[GAThread,5,main]: PowerSaveMode terminated.
08-31 12:27:59.652: V/GAV4(335): Thread[GAThread,5,main]: Dispatch running...
08-31 12:27:59.702: V/GAV4(335): Thread[GAThread,5,main]: User-Agent: GoogleAnalytics/3.0 (Linux; U; Android 2.3.1; en-us; sdk Build/GSI11)
08-31 12:27:59.702: V/GAV4(335): Host: ssl.google-analytics.com
08-31 12:27:59.702: V/GAV4(335): GET /collect?ul=en-us&ht=1409484468454&sr=480x800&a=746864705&sf=100.0&aid=com.redula.vsavings&cid=32da19a1-7e15-4c83-aaa9-f3f1d502b775&av=1.2&v=1&t=exception&an=Savings+Organiser&tid=UA-XXXXXXX&exd=NullPointerException+%28%40HomeActivity%3AconnectToInAppBillingService%3A202%29+%7Bmain%7D&_u=.2nKKhAAAL&_v=ma4.0.2&exf=1&qt=11249&z=2 HTTP/1.1
08-31 12:28:00.482: V/GAV4(335): Thread[GAThread,5,main]: sent 1 of 1 hits
08-31 12:28:00.492: V/GAV4(335): Thread[GAThread,5,main]: PowerSaveMode initiated.
08-31 12:32:48.562: I/Process(335): Sending signal. PID: 335 SIG: 9

And this is what the GA Console showed within a minute or so of the crash:

enter image description here

I know you only get the first line of the stacktrace but that will do for me for now. Various other people have their own approaches for getting the whole stacktrace (see @xitx's comments above and here for example).

So I plan to use my existing code and wait for Google to ship the fix to their bug in a later version of Google Play Service. My app should then work as-is once their fix is released.

For the record, I am using this in my tracker's XML file (res/xml/app_tracker_config.xml):

<bool name="ga_reportUncaughtExceptions">true</bool>

My getTracker() implementation:

public class MyApp extends Application {

 private static Tracker tracker = null;

 synchronized Tracker getTracker() {
    if (tracker == null) {
        GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
        analytics.enableAutoActivityReports(this);
        tracker = analytics.newTracker(R.xml.app_tracker_config);
        tracker.enableAdvertisingIdCollection(true);
    }
    return tracker;
 }
}

Then in onCreate() for my home screen's activity I just do this to initialise GA:

((MyApp) getApplication()).getTracker();

EDIT (extra info): Global config file, res/xml/analytics_global_config.xml:

<?xml version="1.0" encoding="utf-8"?>
  <resources>
    <string name="ga_logLevel">verbose</string>
    <integer name="ga_dispatchPeriod">1</integer>
    <bool name="ga_dryRun">false</bool>
</resources>

My app's manifest refers to this file in this child tag of the application element:

    <meta-data
        android:name="com.google.android.gms.analytics.globalConfigResource"
        android:resource="@xml/analytics_global_config" />
like image 66
snark Avatar answered Oct 11 '22 05:10

snark


Just add this in your manifest

<meta-data 
    android:name="com.google.android.gms.analytics.globalConfigResource"              
    android:resource="@xml/tracker.xml" />

After that you should start to receive in this logcat:

V/GAV4﹕ Thread[main,5,main]: Tracking Exception: ArithmeticException (@MyActivity:onResume:111) {main}
V/GAV4﹕ Thread[main,5,main]: Dispatch call queued. Dispatch will run once initialization is complete.
V/GAV4﹕ Thread[main,5,main]: Passing exception to original handler.

After restarting of application:

V/GAV4﹕ Thread[client_id_fetcher,5,main]: Loaded client id from disk.
V/GAV4﹕ Thread[main,5,main]: Loading Tracker config values.
V/GAV4﹕ Thread[main,5,main]: [Tracker] trackingId loaded: UA-XXXXXXXX-X
V/GAV4﹕ Thread[main,5,main]: [Tracker] session timeout loaded: 300000
V/GAV4﹕ Thread[main,5,main]: ExceptionReporter created, original handler is com.android.internal.os.RuntimeInit$UncaughtHandler
V/GAV4﹕ Thread[main,5,main]: Uncaught exceptions will be reported to Google Analytics.
V/GAV4﹕ Thread[GAThread,5,main]: connecting to Analytics service
V/GAV4﹕ Thread[main,5,main]: service connected, binder: android.os.BinderProxy@42175d30
V/GAV4﹕ Thread[main,5,main]: bound to service
V/GAV4﹕ Thread[GAThread,5,main]: connect: bindService returned true for Intent { act=com.google.android.gms.analytics.service.START cmp=com.google.android.gms/.analytics.service.AnalyticsService (has extras) }
V/GAV4﹕ Thread[main,5,main]: Connected to service
I/GAV4﹕ Thread[GAThread,5,main]: No campaign data found.
V/GAV4﹕ Thread[GAThread,5,main]: Initialized GA Thread
V/GAV4﹕ Thread[GAThread,5,main]: Loaded clientId
V/GAV4﹕ Thread[GAThread,5,main]: Loaded clientId
V/GAV4﹕ Thread[GAThread,5,main]: putHit called
V/GAV4﹕ Thread[GAThread,5,main]: Sending hit to service   PATH: https:  PARAMS: ul=en-us,  ht=1408554996959,  sr=720x1184,  a=194292074,  aid=<app package>,  cid=9639b81c-b17a-4c3a-a43e-0c1f43a6d5c1,  av=1.5.0.1,  v=1,  t=exception,  an=<app name>,  tid=UA-XXXXXXXX-X,  exd=SQLiteBindOrColumnIndexOutOfRangeException (@MyApp:onCreate:185) {main},  _u=.nKhAAAL,  exf=0

Currently in my project I'm using this code (in Application.onCreate() method), it works just fine:

GoogleAnalytics googleAnalytics = GoogleAnalytics.getInstance(this);
applicationPreferences = new ApplicationPreferences(getApplicationContext());
tracker = googleAnalytics.newTracker(R.xml.tracker);
tracker.enableExceptionReporting(true);
String lastErrorString = applicationPreferences.getLastErrorString();
if (lastErrorString != null) {
    applicationPreferences.clearLastErrorString();
    tracker.send(new HitBuilders.ExceptionBuilder().setDescription(lastErrorString).setFatal(true).build());
}
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        applicationPreferences.setLastErrorString(Throwables.getStackTraceAsString(throwable));
        defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
    }
});

Note: Throwables is class from guava

Variation without Guava but without full stacktrace, only original line number and method included into report:

GoogleAnalytics googleAnalytics = GoogleAnalytics.getInstance(this);
applicationPreferences = new ApplicationPreferences(getApplicationContext());
tracker = googleAnalytics.newTracker(R.xml.tracker);
tracker.enableExceptionReporting(true);
String lastErrorString = applicationPreferences.getLastErrorString();
if (lastErrorString != null) {
    applicationPreferences.clearLastErrorString();
    tracker.send(new HitBuilders.ExceptionBuilder().setDescription(lastErrorString).setFatal(true).build());
}
final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        applicationPreferences.setLastErrorString(
            new StandardExceptionParser(getApplicationContext(), null).getDescription(thread.getName(), throwable)
        );
        defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
    }
});
like image 40
xitx Avatar answered Oct 11 '22 06:10

xitx