-
-
Notifications
You must be signed in to change notification settings - Fork 45
Logs all network requests to Analytics #3352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,4 +40,8 @@ public class CCAnalyticsParam { | |
| static final String PARAM_API_SUCCESS = "ccc_api_success"; | ||
| static final String PARAM_API_TOTAL_JOBS = "ccc_api_total_jobs"; | ||
| static final String PARAM_API_NEW_JOBS = "ccc_api_new_jobs"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting the overlap with the ccc_api_* events. These can log additional info in some cases though, such as number of new jobs received. I think fine to leave them all in for now, but I wonder if there's a future world we could get to where network_request events could be the single event and optionally include additional analytics info depending on the call? Recognizing this may be difficult with the more-generic NetworkAnalyticsInterceptor...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, I think based on the overall feedback , I am going to move away from interceptor approach and instead switch to have this logic as part of generalised network callbacks. |
||
|
|
||
| static final String RESPONSE_CODE = "response_code"; | ||
| static final String REQUEST_DURATION_MS = "request_duration_ms"; | ||
| static final String URL = "url"; | ||
|
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainConsider URL sanitization for privacy. The Example sanitization approaches:
static final String RESPONSE_CODE = "response_code";
static final String REQUEST_DURATION_MS = "request_duration_ms";
-static final String URL = "url";
+static final String URL = "url"; // Note: URLs should be sanitized before logging to remove sensitive dataYou may want to implement sanitization in 🏁 Script executed: #!/bin/bash
# Locate references to the URL analytics parameter
rg -n "CCAnalyticsParam\.URL" -C3
# Find and inspect the logNetworkAnalytics method
rg -n "logNetworkAnalytics" -C3
# Find and inspect the reportNetworkRequest method
rg -n "reportNetworkRequest" -C3Length of output: 3419 Sanitize URLs before logging
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, might need to remove PII or token There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Jignesh-dimagi Thank you for confirming! URL sanitization is indeed important to prevent exposing sensitive data in analytics. If you'd like, I can help design the sanitization logic. Common approaches include:
Would you like me to suggest specific implementation code for the |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,17 +1,30 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| package org.commcare.google.services.analytics; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.CORRUPT_APP_STATE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.RSA_KEYSTORE_KEY_RETRIEVAL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.STAGE_UPDATE_FAILURE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.UPDATE_RESET; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_FULL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_IMMEDIATE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_LENGTH_UNKNOWN; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_MOST; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_OTHER; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_PARTIAL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.os.Bundle; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.os.Environment; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.text.TextUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.NavController; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.NavDestination; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.fragment.FragmentNavigator; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.firebase.analytics.FirebaseAnalytics; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.CommCareApplication; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.DiskUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.android.database.connect.models.ConnectUserRecord; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.android.logging.ReportingUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.connect.PersonalIdManager; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.connect.database.ConnectUserDatabaseUtil; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.dalvik.BuildConfig; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.preferences.MainConfigurablePreferences; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.commcare.suite.model.OfflineUserRestore; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -21,21 +34,6 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Date; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.NavController; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.NavDestination; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.navigation.fragment.FragmentNavigator; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.CORRUPT_APP_STATE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.RSA_KEYSTORE_KEY_RETRIEVAL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.STAGE_UPDATE_FAILURE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.UPDATE_RESET; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_FULL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_IMMEDIATE; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_LENGTH_UNKNOWN; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_MOST; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_OTHER; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.commcare.google.services.analytics.AnalyticsParamValue.VIDEO_USAGE_PARTIAL; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Created by amstone326 on 10/13/17. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -586,4 +584,12 @@ public static void reportNavDrawerItemSelected(String selectedItem) { | |||||||||||||||||||||||||||||||||||||||||||||||||
| new String[]{selectedItem}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| public static void reportNetworkRequest(String url, int responseCode, long durationMs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Bundle bundle = new Bundle(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bundle.putInt(CCAnalyticsParam.RESPONSE_CODE, responseCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bundle.putLong(CCAnalyticsParam.REQUEST_DURATION_MS, durationMs); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bundle.putString(CCAnalyticsParam.URL, url); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reportEvent(CCAnalyticsEvent.NETWORK_REQUEST, bundle); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+587
to
+593
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: URLs may contain sensitive data (auth tokens, PII, PHI). Logging full URLs to analytics can expose:
Additionally, Firebase Analytics truncates parameter values to 100 characters (line 61-62), which may:
Recommendations:
public static void reportNetworkRequest(String url, int responseCode, long durationMs) {
Bundle bundle = new Bundle();
bundle.putInt(CCAnalyticsParam.RESPONSE_CODE, responseCode);
bundle.putLong(CCAnalyticsParam.REQUEST_DURATION_MS, durationMs);
- bundle.putString(CCAnalyticsParam.URL, url);
+ bundle.putString(CCAnalyticsParam.URL, sanitizeUrl(url));
reportEvent(CCAnalyticsEvent.NETWORK_REQUEST, bundle);
}
+
+private static String sanitizeUrl(String url) {
+ try {
+ java.net.URL parsed = new java.net.URL(url);
+ // Return only protocol, host, and path (no query params)
+ return parsed.getProtocol() + "://" + parsed.getHost() + parsed.getPath();
+ } catch (Exception e) {
+ return "invalid_url";
+ }
+}
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Jignesh-dimagi Thank you for acknowledging this security concern! Given the critical nature of potentially exposing sensitive data (auth tokens, PII, PHI) through URL logging, I wanted to check: are you planning to address the URL sanitization in this PR, or would you like me to create a follow-up issue to track this separately? If you'd like an issue created, I can include a reference to this discussion and the suggested sanitization approach. |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package org.commcare.network | ||
|
|
||
| import okhttp3.Interceptor | ||
| import okhttp3.Response | ||
| import org.commcare.google.services.analytics.FirebaseAnalyticsUtil | ||
| import org.javarosa.core.services.Logger | ||
|
|
||
| /** | ||
| * OkHttp interceptor that logs analytics events for all HTTP requests/responses | ||
| */ | ||
| class NetworkAnalyticsInterceptor : Interceptor { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shubham1g5 We are trying to log all network calls, which might be available in AWS EC2 instance with corresponding delays. These might be too much data to handle and might not be useful at the end?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are making a shift to log more and more data to analytics (now that we don't have sumologic) , so I would not be worried about this data size untill it starts creating practical issues for us. I am also not sure how will we filter what requests we want to log and what not. |
||
|
|
||
| override fun intercept(chain: Interceptor.Chain): Response { | ||
| val request = chain.request() | ||
| val startTime = System.currentTimeMillis() | ||
| var response: Response? | ||
| var responseCode = -1 | ||
|
|
||
| try { | ||
| response = chain.proceed(request) | ||
| responseCode = response.code | ||
| return response | ||
| } finally { | ||
| val duration = System.currentTimeMillis() - startTime | ||
| logNetworkAnalytics(request.url.toString(), responseCode, duration) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting that any exceptions sending the request will result in a -1 responseCode with no additional exception info. I wonder if we should attempt to distinguish the various possible exceptions with different negative error codes? |
||
| } | ||
| } | ||
|
Comment on lines
+13
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainVerify FirebaseAnalyticsUtil.reportNetworkRequest implementation. The interceptor will invoke
🏁 Script executed: #!/bin/bash
# Find and examine the FirebaseAnalyticsUtil.reportNetworkRequest implementation
# Search for reportNetworkRequest method definition
ast-grep --pattern $'class FirebaseAnalyticsUtil {
$$$
reportNetworkRequest($$$) {
$$$
}
$$$
}'
# Also search with ripgrep as fallback
rg -n -A 10 'fun reportNetworkRequest|void reportNetworkRequest|public.*reportNetworkRequest' --type=java --type=kotlinLength of output: 1100 Enhance reportNetworkRequest to sanitize URLs, use async/batching, and respect opt-out
🤖 Prompt for AI Agents |
||
|
|
||
| private fun logNetworkAnalytics( | ||
| url: String, | ||
| responseCode: Int, | ||
| duration: Long, | ||
| ) { | ||
| try { | ||
| FirebaseAnalyticsUtil.reportNetworkRequest(url, responseCode, duration) | ||
| } catch (e: Exception) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to adding safety to our analytics calls and silently reporting exceptions, I think we've seen a crash or two come from such calls in the past. But I think we should make it comprehensive, rather than the caller's responsibility as done here. Looking in FirebaseAnalyticsUtil it seems we may be close to accomplishing this already.
The first calls the second, but there are 16 additional usages of the second. Seems a little cleanup here would globally guarantee safety for analytics calls.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for flagging, I was under assumption that all calls are fail safe but I can get that in as part of this work. |
||
| Logger.exception("Error logging network analytics", e) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition