OneAgent SDK for Android
OneAgent SDK for Android can be used to report additional details about the mobile user sessions of your app. OneAgent SDK for Android enables you to create custom user actions, measure web requests, report errors, and tag specific users. This topic explains how to enable these capabilities.
OneAgent SDK is automatically added by the DESK Android Gradle plugin. Alternatively, you can add the OneAgent SDK when you want to use standalone manual instrumentation for your Android application project.
Note: All technical information is available at JavaDoc for OneAgent SDK.
Start OneAgent
If you've disabled auto-start with the property autoStart.enabled
or you're using standalone manual instrumentation instead of auto-instrumentation, start OneAgent manually in the Application.onCreate method.
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DESK.startup(this, new DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconUrl>")
... // additional configuration
.buildConfiguration());
}
}
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
DESK.startup(this, DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconUrl>")
... // additional configuration
.buildConfiguration())
}
}
If your app supports Direct Boot, ensure that DESK.startup
is never called from a Direct Boot aware component. You should also see Adjust OneAgent communication to ensure that OneAgent is able to transmit the data to the cluster.
Configure OneAgent
Use the DESKConfigurationBuilder class to customize all advanced OneAgent settings with different API methods.
new DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withUserOptIn(true)
.withCrashReporting(true)
.buildConfiguration();
DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withUserOptIn(true)
.withCrashReporting(true)
.buildConfiguration()
Note:
If you use a combination of manual and auto-instrumentation, the auto-instrumentation injects a DESK.startup
call into the Application.onCreate
method. In this case, you will lose your manual configuration because the DESK.startup
call from the auto-instrumentation is called before your DESK.startup
call.
The autoStart.enabled
property allows you to deactivate the auto-start feature from the auto-instrumentation. You can then define a manual DESK.startup
call in the Application.onCreate
method. In this case, you can override the values preconfigured from the auto-instrumentor.
User action monitoring
With user action monitoring you can define and report your own custom user actions. These user actions can be enriched with the following monitoring operations:
- report an event
- report a value
- report an error
- attach a web request to the user action
- create a child action
Manually created user actions are different from user actions created with the DESK Android Gradle plugin. OneAgent not only adds additional events (such as web requests) to your user action, it will also not automatically close your user actions after a specific time period.
Create custom user actions
You can define your own custom user actions. First, you have to create them. Then you can enhance them with additional information, and finally, you must close them. OneAgent discards all action-related monitoring data when the action isn't closed.
You must call enterAction
to start each action and leaveAction
to close each action. Timing is measured automatically.
// start user action
DTXAction action = DESK.enterAction("exampleName");
// ...do some work here...
// end the action after the search completed
action.leaveAction();
val action = DESK.enterAction("exampleName")
// ...do some work here...
// end the action after the search completed
action.leaveAction()
Child actions
Child actions are similar to parent actions. When the parent action is closed, OneAgent automatically closes all nested/child actions of the parent action.
Child actions are generated using the method DESK.enterAction(String, DTXAction)
.
// start parent user action
DTXAction parentAction = DESK.enterAction("parent_user_action_name");
// ...do some work here...
// start child user action
DTXAction childAction = DESK.enterAction("child_user_action_name", parentAction);
// ...do some work here...
// end the child action
childAction.leaveAction();
// ...do some work here...
// end the parent action
parentAction.leaveAction();
// start parent user action
val parentAction = DESK.enterAction("parent_user_action_name")
// ...do some work here...
// start child user action
val childAction = DESK.enterAction("child_user_action_name", parentAction)
// ...do some work here...
// end the child action
childAction.leaveAction()
// ...do some work here...
// end the parent action
parentAction.leaveAction()
User action sample
The following code snippet shows a sample instrumentation of the fictional method search, which makes a web request to an instrumented server and parses the received result. The following instrumentation actions are part of the code snippet:
- creates a user action
- reports a custom metric
- reports a handled exception
- monitors a web request
- creates a child action
public boolean search(String query) {
// [1a] start outer/parent action
DTXAction searchAction = DESK.enterAction("search");
// [2] report your own metric
searchAction.reportValue("query", query);
URL url;
try {
url = new URL("https://www.example.com/?query=" + query);
} catch (MalformedURLException e) {
// [3] report an error
searchAction.reportError("invalid url", e);
// [1b] end outer action
searchAction.leaveAction();
return false;
}
// [4.1] Generate a new unique tag associated with the user action "search"
String uniqueRequestTag = searchAction.getRequestTag();
// [4.2] Generate a WebRequestTiming object based on the unique tag
WebRequestTiming timing = DESK.getWebRequestTiming(uniqueRequestTag);
Request request = new Request.Builder()
.url(url)
// [4.3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build();
// [4.4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
// [4.5] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, response.code(), response.message());
return false;
}
String body = response.body().string();
// [4.5] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code(), response.message());
// [5a] start inner action
DTXAction parseAction = DESK.enterAction("parse result", searchAction);
parseResult(body);
// [5b] end inner action
parseAction.leaveAction();
return true;
} catch (IOException e) {
// [4.5] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString());
return false;
}
finally {
// [1b] end outer action
searchAction.leaveAction();
}
}
fun search(query: String): Boolean {
// [1a] start outer/parent action
val searchAction = DESK.enterAction("search")
// [2] report your own metric
searchAction.reportValue("query", query)
var url: URL? = null
try {
url = URL("https://www.example.com/?query=$query")
} catch (e: MalformedURLException) {
// [3] report an error
searchAction.reportError("invalid url", e)
// [1b] end outer action
searchAction.leaveAction()
return false
}
// [4.1] Generate a new unique tag associated with the user action "search"
val uniqueRequestTag = searchAction.requestTag
// [4.2] Generate a WebRequestTiming object based on the unique tag
val timing = DESK.getWebRequestTiming(uniqueRequestTag)
val request = Request.Builder()
.url(url)
// [4.3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build()
try {
// [4.4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// [4.5] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, response.code, response.message)
return false
}
val body = response.body!!.string()
// [4.5] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code, response.message)
// [5a] start inner action
val parseAction = DESK.enterAction("parse result", searchAction)
parseResult(body)
// [5b] end inner action
parseAction.leaveAction()
}
return true
} catch (e: IOException) {
// [4.5] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString())
return false
} finally {
// [1b] end outer action
searchAction.leaveAction()
}
}
Custom value reporting
Report event
The report event feature allows you to report the time point of a specific event. The reported event must be part of a user action.
action.reportEvent("event_name");
action.reportEvent("event_name")
Report value
The report value feature allows you to report your own metrics. These metrics must be part of a user action. The OneAgent SDK allows you to report
// report int
action.reportValue("int_metrics_key", 5);
// report double
action.reportValue("double_metrics_key", 5.6);
// report string
action.reportValue("string_metrics_key", "exampleValue");
// report int
action.reportValue("int_metrics_key", 5)
// report double
action.reportValue("double_metrics_key", 5.6)
// report string
action.reportValue("string_metrics_key", "exampleValue")
Report errors
The reporting error feature is different from the reporting value feature in that it is specifically identified as an error type event. The OneAgent SDK allows you to report:
- error codes with the
reportError(String, int)
method - handled exceptions with the
reportError(String, Throwable)
method
// report error code
action.reportError("error_code_key", -1);
// report exception
action.reportError("exception_key", exception);
// report error code
action.reportError("error_code_key", -1)
// report exception
action.reportError("exception_key", exception)
You can also report errors as stand-alone error events via the class DESK
:
// report error code
DESK.reportError("error_code_key", -1);
// report exception
DESK.reportError("exception_key", exception);
// report error code
DESK.reportError("error_code_key", -1)
// report exception
DESK.reportError("exception_key", exception)
Web request monitoring
To track web requests, add the x-desk
HTTP header with a unique value to the web request. The tag correlates the server-side monitoring data to the corresponding mobile web request. Additionally, the timing values from the mobile side must be measured.
Perform the following steps to successfully monitor a web request:
Generate a new unique tag.
Generate a WebRequestTiming
object based on the tag.
Place the DESK HTTP header on your web request.
Start web request timing before the HTTP request is sent.
Stop web request timing.
The HTTP response is received and the response body is obtained.
A connection exception occurs.
Note: Do not manually and auto-instrument the same web requests. This behavior can lead to incorrect monitoring data.
Web requests can either be:
- attached to a user action by generating a unique tag with the
DTXAction.getRequestTag()
method - or monitored as a standalone web request by generating a unique tag with the
DESK.getRequestTag()
method
Note: For monitoring standalone web requests, OneAgent automatically tries to find an appropriate user action. If it finds one, the web request is attached to the user action. The web request is only reported as a standalone web request when no appropriate user action is found.
Attach a web request to the user action
The following sample shows how a synchronous OkHttp web request could be monitored:
URL url = new URL("https://www.example.com");
// First, create a user action
DTXAction webAction = DESK.enterAction("search request");
// [1] Generate a new unique tag associated with the user action
String uniqueRequestTag = webAction.getRequestTag();
// [2] Generate a WebRequestTiming object based on the unique tag
WebRequestTiming timing = DESK.getWebRequestTiming(uniqueRequestTag);
// Define your OkHttp request, this varies greatly depending on your implementation
Request request = new Request.Builder()
.url(url)
// Define your headers for the OkHttp request
.addHeader(yourKey1, yourValue1)
.addHeader(yourKey2, yourValue2)
// [3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build();
// [4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
// handle response
String body = response.body().string();
}
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code(), response.message());
} catch (IOException e) {
// [5.2] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString());
// user-defined exception handling
}
finally {
// Lastly, leave the action
webAction.leaveAction();
}
val url = URL("https://www.example.com")
// First, create a user action
val webAction = DESK.enterAction("search request")
// [1] Generate a new unique tag associated with the user action
val uniqueRequestTag = webAction.requestTag
// [2] Generate a WebRequestTiming object based on the unique tag
val timing = DESK.getWebRequestTiming(uniqueRequestTag)
// Define your OkHttp request, this varies greatly depending on your implementation
val request = Request.Builder()
.url(url)
// Define your headers for the OkHttp request
.addHeader(yourKey1, yourValue1)
.addHeader(yourKey2, yourValue2)
// [3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build()
try {
// [4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming()
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
// handle response
val body = response.body!!.string()
}
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code, response.message)
}
} catch (e: IOException) {
// [5.2] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString())
// user-defined exception handling
} finally {
// Lastly, leave the action
webAction.leaveAction()
}
Standalone web request
The following sample shows how a synchronous OkHttp
web request can be monitored:
URL url = new URL("https://www.example.com");
// [1] Generate a new unique tag
String uniqueRequestTag = DESK.getRequestTag();
// [2] Generate a WebRequestTiming object based on the unique tag
WebRequestTiming timing = DESK.getWebRequestTiming(uniqueRequestTag);
// Define your OkHttp request, this varies greatly depending on your implementation
Request request = new Request.Builder()
.url(url)
// Define your headers for the OkHttp request
.addHeader(yourKey1, yourValue1)
.addHeader(yourKey2, yourValue2)
// [3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build();
// [4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
// handle response
String body = response.body().string();
}
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code(), response.message());
} catch (IOException e) {
// [5.2] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString());
// user-defined exception handling
}
val url = URL("https://www.example.com")
// [1] Generate a new unique tag
val uniqueRequestTag = DESK.getRequestTag()
// [2] Generate a WebRequestTiming object based on the unique tag
val timing = DESK.getWebRequestTiming(uniqueRequestTag)
// Define your OkHttp request, this varies greatly depending on your implementation
val request = Request.Builder()
.url(url)
// Define your headers for the OkHttp request
.addHeader(yourKey1, yourValue1)
.addHeader(yourKey2, yourValue2)
// [3] Place the DESK HTTP header on your web request
.addHeader(DESK.getRequestTagHeader(), uniqueRequestTag)
.build()
try {
// [4] Start web request timing before the HTTP request is sent
timing.startWebRequestTiming()
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
// handle response
val body = response.body!!.string()
}
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming(url, response.code, response.message)
}
} catch (e: IOException) {
// [5.2] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString())
// user-defined exception handling
}
HttpURLConnection
For monitoring HttpURLConnection
web requests, OneAgent SDK contains an additional API method: DESK.getWebRequestTiming(HttpURLConnection)
. It automatically generates a unique tag and places it on the HttpURLConnection
connection. You can also use the WebRequestTiming.stopWebRequestTiming()
method, because OneAgent can automatically determine the status code and message from the specified HttpURLConnection
.
URL url = new URL("https://www.example.com");
HttpURLConnection conn = null;
WebRequestTiming timing = null;
// First, create an action
DTXAction webAction = DESK.enterAction("search request");
try {
conn = (HttpURLConnection) url.openConnection();
// [1], [2], [3] Once the connection object is obtained, tag it automatically and receive a WebRequestTiming instance
timing = DESK.getWebRequestTiming(conn);
// [4] Stop web request timing when the HTTP response is received and the response body was obtained
timing.startWebRequestTiming();
InputStream stream = new BufferedInputStream(conn.getInputStream());
readStream(stream);
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming();
} catch (Exception e) {
if(timing != null) {
// [5.2] Stop web request timing when a connection exception occurs
timing.stopWebRequestTiming(url, -1, e.toString());
}
// user-defined exception handling
} finally {
if (conn != null) {
conn.disconnect();
}
// Lastly, leave the action
webAction.leaveAction();
}
val url = URL("https://www.example.com")
var conn: HttpURLConnection? = null
var timing: WebRequestTiming? = null
// First, create an action
val webAction = DESK.enterAction("search request")
try {
conn = url.openConnection() as HttpURLConnection
// [1], [2], [3] Once the connection object is obtained, tag it automatically and receive a WebRequestTiming instance
timing = DESK.getWebRequestTiming(conn)
// [4] Stop web request timing when the HTTP response is received and the response body was obtained
timing.startWebRequestTiming()
val stream = BufferedInputStream(conn.getInputStream())
readStream(stream)
// [5.1] Stop web request timing when the HTTP response is received and the response body was obtained
timing.stopWebRequestTiming()
} catch (e: Exception) {
timing?.stopWebRequestTiming(url, -1, e.toString())
// user-defined exception handling
} finally {
conn?.disconnect()
// Lastly, leave the action
webAction.leaveAction()
}
Other samples
Crash reporting
OneAgent captures all unhandled exceptions and errors and sends the crash report immediately to the server. The Android crash report includes the occurrence time and the full stack trace of the exception.
Disable crash reporting
You can also deactivate crash reporting with the withCrashReporting
method:
new DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withCrashReporting(false)
.buildConfiguration();
DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withCrashReporting(false)
.buildConfiguration()
Tag specific users
You can tag each user of your mobile apps with a unique user name. This enables you to search and filter specific user sessions and analyze individual user behavior over time. The following steps explain how to manually tag an individual user via the DESK API.
DESK.identifyUser("john.doe@example.com");
DESK.identifyUser("john.doe@example.com")
Data privacy
The OneAgent SDK enables you to dynamically adjust data-privacy settings so that you can build your apps in compliance with GDPR data-privacy regulations. To activate this feature, enable the userOptIn
flag via the DSL from the DESK Android Gradle plugin or use the ConfigurationBuilder.withUserOptIn
method.
The privacy API methods allow you to dynamically activate/deactivate crash reporting and to change the data-collection level based on the individual preferences of your end users. Each end user can select from three data-privacy levels:
off
: OneAgent won't capture any monitoring data.performance
: OneAgent will only capture anonymous performance data. Monitoring data that can be used to identify individual users, such as user tags and custom values, won't be captured.user behavior
: OneAgent will capture both performance and user data. In this mode, OneAgent recognizes and reports on users who re-visit in future sessions.
When OneAgent starts for the first time, OneAgent deactivates crash reporting and sets the data-collection level to off
. You should change the privacy settings, based on each user's individual preference when your app starts for the first time. OneAgent doesn't provide a privacy settings dialog or any similar UI component. You must integrate a privacy dialog into your app. We recommend that you display the privacy dialog before your app is displayed and then apply the user's privacy preference. You should also allow your users to change their privacy settings in the future.
OneAgent persists the privacy setting and automatically applies it when the app is restarted. Each time the user changes the data-collection level, a new session with the new privacy settings will be generated by OneAgent. Don't wrap this API method with a user action. Otherwise, OneAgent won't be able to attach the user action to the correct session.
With the DESK.setCrashReportingOptedIn(boolean)
method, you can activate or deactivate crash reporting. The data collection level can be changed with the DESK.setDataCollectionLevel(DataCollectionLevel)
method.
You can retrieve the privacy settings with the DESK.getDataCollectionLevel()
and DESK.isCrashReportingOptedIn()
methods. But you can't use the privacy API methods before OneAgent is started. Otherwise, OneAgent will throw an exception.
Hybrid apps that use the RUM JavaScript tag inside a WebView
For hybrid applications that use the RUM JavaScript tag, cookies must be set for each instrumented domain or server that the application communicates with. When the hybrid app monitoring feature is enabled, OneAgent generates these cookies for every specified domain and stores them in CookieManager
.
You can activate the hybrid app monitoring feature with the withHybridMonitoring
method. All used domains, hosts, and IP addresses must be specified via the withMonitoredDomains
method. Domains and sub-domains must start with a dot.
new DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withHybridMonitoring(true)
.withMonitoredDomains("<domain1>", "<domain2>")
.buildConfiguration();
DESKConfigurationBuilder("<YourApplicationID>", "<ProvidedBeaconURL>")
.withHybridMonitoring(true)
.withMonitoredDomains("<domain1>", "<domain2>")
.buildConfiguration()