This post was written by Matthew Laux, with contributions from Martin Hock.
As you may have seen, FullStory recently announced the launch of Network Capture functionality for FullStory for Mobile Apps. We’re very proud of the innovative approach taken to deliver this amazing functionality, and here on the engineering blog we thought it would be interesting to dive into some of the details of how Network Capture was built.
While a network capture system should, of course, capture network traffic, it’s much more challenging to capture that traffic in a manner that safeguards your user privacy, integrates automatically, and has no performance impact on your apps. We take these challenges seriously because, just like you, we care about creating a more perfect digital experience for your users.
In this first of a two-part series, we’re going to first introduce how Network Capture works with a real world example, then go under the hood to show we built a truly zero-configuration network capture tool for both iOS and Android apps.
Network Capture in action
Imagine a mobile app development team just launched an account creation flow in one of their apps. This team worked closely with their Identity and Authentication team to develop new APIs to support that creation flow. User sentiment for their new UX has been positive, but a few bug reports began surfacing that users were hitting errors creating accounts.
The development team researched the bugs and found the reports to be credible, but they were unable to reproduce the defects and could not find any supporting anomalies in their crash reporting and logging tools.
As a next step, the team then loaded FullStory to analyze real user sessions from their app to attempt to isolate and reproduce the problem. To do this, they created a segment inside FullStory that showed all sessions where a user visited the /signup URL path and received a network error.
Interestingly enough, the team found several sessions that matched that criteria! Knowing they could be on to something, they then began watching those sessions to find more detail.
In several of those sessions, the team noticed that the user entered their username and password in the signup form, but every time the user clicked the “Create Account” button, a network error was returned by one of the API endpoints. Clicking into that error for more detail, the team noticed a 422 “unprocessable entity” error was returned every time. That’s not good!
Armed with this knowledge and a reliable way of reproducing the problem, the team creates a note inside FullStory showing the exact problem, and then automatically creates a Jira ticket assigned to the Identity and Authentication team to reproduce and fix the issue.
After the fix deploys, the mobile team monitors this segment for future recurrences of the problem, and successfully verifies the problem has indeed been fixed. Signups are now occurring as desired, success!
Solving technical challenges with Android
Given the previous example of how Network Capture can be useful, let’s now dive deeper into how it was built, starting with our Android implementation.
The initial release of our Android implementation supports the OkHttp HTTP client library, which is by far the dominant library on Android for network communication. In future versions we will be looking to extend support for other frameworks in a manner that provides the most value to our customers.
Seamlessly detecting OkHttp traffic inside a customer's APK was an interesting technical challenge. For starters, we made sure that OkHttp classes, methods, and fields that were needed were excluded from ProGuard. To ensure that FullStory Network Capture did not cause any crashes or performance issues, we also checked that every class and method we needed to hook into was available at runtime.
At app runtime, if Network Capture is enabled in the FullStory admin panel, we use OkHttp's built-in network interceptor functionality to capture details about each request to log for FullStory’s own network tracing.
As we previewed earlier, though, it is not enough to simply capture the network traffic—we must also do so in a manner that respects user privacy and has negligible impact on the performance of the app itself. In our next post, we will dive deeper into how this was accomplished.
Simple implementation, zero configuration
Seamless integration was an important goal for the project. If enabled, we wanted Network Capture to “just work.” No extra coding, no extensive configuration, no headaches.
Turns out we were able to accomplish this with a relatively simple implementation. At build time, we instrument the APK to find calls to OkHttpClient.Builder's constructor. From here, it was simply a matter of injecting an additional call to a FullStory method that in turn invokes OkHttpClient.Builder's addNetworkInterceptor method to provide all the context needed to capture the needed diagnostic information, while still respecting rigorous privacy safeguards for that data.
Addressing constraints within iOS
For our iOS implementation, we decided to focus on hooking into NSURLSession, which has been the recommended way to perform networking on iOS for several years. Hooking into NSURLSession also unlocks other frameworks such as React Native, which also implement their networking APIs using NSURLSession.
As an aside, you may wonder if Apple provides support for a built-in way to monitor NSURLSession. In general on iOS, delegates are used for this task, and NSURLSession provides such a delegate. Unfortunately, only one delegate is allowed per NSURLSession, and the customer may already have one assigned. Further complicating matters is the fact that many apps make use of the default session object which cannot have a delegate assigned to it.
Above all else we strive to “do no harm” in your application, and could not risk a collision with other libraries using this approach, so we decided on a different, less invasive solution.
If you dig into the details of NSURLSession, you’ll find that it breaks up network requests into individual instances of NSURLSessionTask. To capture the network traffic we needed for our diagnostics, we needed to observe these tasks in a safe, zero-touch manner. Fortunately, Objective-C provides such an approach.
Objective-C is a dynamic language that provides several mechanisms for introspection that prove very useful. One of these is called method swizzling. Swizzling allows us to replace or augment existing methods with additional implementation logic of our own, which gives us an entry point to access much of the needed information.
Additionally, another useful mechanism utilized is called key-value observing (KVO). KVO is quite handy in providing much of the needed context, but must be used with caution as not all properties are KVO compliant.
Through judicious use of both of these approaches, we were able to swizzle the method that an app uses to resume a NSURLSessionTask, then observe the property that changes when the task is completed. This opened the door to all the needed data required to capture the network traffic in a seamless, performant manner.
BUT… as we mentioned before, whenever accessing this data it is of paramount importance that user privacy is always respected. Sensitive information must never leave the device! Towards that end, in our next post we will look more closely into our privacy-first approach to Network Capture, how it’s built, and how your data is safeguarded with FullStory.
FullStory for Mobile Apps’ Network Capture is a powerful addition to any mobile development team’s toolkit. If you want to try it, adopt the newest version of the SDK in your app.