Mobile RUM Deployment & Configuration
vuSmartMaps Configuration: Enabling Mobile RUM
Enabling Mobile RUM O11ySource
To enable Mobile Real User Monitoring (MRUM) in vuSmartMaps, follow these steps:
- Navigate to O11ySources:
- From the vuSmartMaps left navigation menu, go to (Data Ingestion > O11ySources)
- Locate Mobile RUM O11ySource on the landing page.

- Enable the O11ySource:
- Click the Enable button to activate Mobile RUM O11ySource.

Data Collection and Processing
Mobile RUM collects real user monitoring data using VuNet’s Mobile SDKs. The recommended approach is to deploy a proxy server in the DMZ to receive telemetry from users' mobile applications. This proxy serves as a secure intermediary between the end-user mobile application (where the RUM agent executes) and the RUM data collector of the observability platform hosted within the internal network.
Compatibility
Mobile RUM supports the following minimum target versions:
- Android – Android 7.0 (API level 24) or higher
- Flutter – Flutter SDK version 3.29.2 and Dart 3.7.2 or higher
Prerequisites
-
Network Configuration for vuSmartMaps:
- Ideally, vuSmartMaps is not directly connected to the internet. Therefore, a reverse proxy is required to route all Real User Monitoring (RUM) API calls from mobile devices to the vuSmartMaps server.
-
Reverse Proxy Setup:
-
Use a reverse proxy solution of your choice (e.g., Nginx, Apache, HAProxy).
-
Configure API keys for authentication directly within the reverse proxy server to secure the communication.
-
For a sample Nginx reverse proxy setup, refer to Server Connectivity and Firewall.
-
Use a valid SSL certificate for the reverse proxy.
-
To configure the data source, you need the following parameters:
Parameter Description Application Name The name of the application you want to monitor. -
Firewall Requirements
To ensure smooth and secure data collection for Mobile RUM, the following firewall rules must be configured:
-
Allow inbound traffic on TCP port 443 (HTTPS) to the proxy server. This enables mobile applications to send RUM data to the proxy server securely.
-
Allow outbound traffic from the proxy server to the vuSmartMaps OTEL Collector on ports 4321 and 4322. The proxy uses these ports to forward telemetry data to the internal OTEL Collector for processing.
The port numbers (4321, 4322) may vary depending on your specific environment or deployment configuration.
Certificate
-
Mobile-to-Proxy SSL Certificate
This certificate is used to establish a secure HTTPS connection between the mobile application and the proxy server. It ensures that RUM data sent from mobile devices is encrypted and trusted during transmission. -
vuSmartMaps Certificate
This certificate is required for secure communication between the proxy server and the vuSmartMaps OTEL Collector. It ensures encrypted and authenticated forwarding of telemetry data to the backend observability platform.
Configuring Data Sources
- Navigate to the Sources tab and click the + button to add new services to be monitored.
- Fill in the Following Details in the Wizard:
- Application Name: Specify the name of the mobile application you want to monitor.

- Download Instrumentation SDK (if applicable)
- For Flutter: Click Save and Continue to proceed with downloading the Flutter instrumentation SDK package.
- For Android Native: No download is needed. The SDK will be automatically pulled from the Maven repository once configured as per the instrumentation guide.

Click Finish to close the data source wizard and complete the configuration. Follow the instructions provided in the Client Side Mobile RUM Instrumentation.
Frontend Setup (Mobile App)
SDK Integration and Configuration
- SDK Download and Setup:
- For Native Android: Follow the instructions to configure the MRUM in the mobile app (No binary download required, fetch the libraries from Maven).
- For Flutter: Download the Flutter SDK through O11ySource Page and follow the instructions provided for configuration.
- Add to Mobile App:
- Integrate the SDK into your mobile application's codebase.
- Instrument your mobile app with the SDK as per the instructions in the documents above
- Once the SDK is configured and the application is built, the mobile application will start sending the RUM data to vuSmartMaps. Refer this MRUM Metrics Overview for understanding the RUM data being collected.
- Once the SDK is configured few metrics are collected immediately once the SDK is initialized. For a few metrics Manual instrumentation is required. The details about the Auto Instrumentation and Manual Instrumentation are as follows
- Auto Instrumentation: Configure the SDK to automatically capture key performance and error data.
- Errors/Crashes: Enable automatic detection and reporting of application errors and crashes.
- Screen Lifecycle: Track the lifecycle of screens/views within the app (e.g., load times, view transitions).
- HTTP Tracing: Automatically monitor and trace outgoing HTTP requests made by the application.
- Manual Instrumentation: Implement custom tracking for specific user interactions and application events that are not covered by auto-instrumentation.
- Click Events: Manually instrument code to capture user clicks on important buttons or UI elements.
- Activity Events: Track specific user activities or custom events within the application flow (e.g., feature usage).
The Debug Symbol Upload capability, enabling stack trace de-obfuscation in vuSmartMaps, is planned for a future update.
Backend Setup
Enable MRUM O11ySource
- Ensure that the MRUM O11ySource is enabled and configured to receive and process data from the mobile SDK.
Understanding the O11ySource Data Flow
The data collected by the MRUM O11ySource follows this general pipeline:
- Data Ingestion (OtelCollector):
- Mobile SDK sends telemetry data (errors, traces, events) to the OpenTelemetry Collector (OtelCollector). In case vuSmartMaps is not accessible over the public internet. Telemetry data will be sent through the proxy server to the OTEL collector
- Queuing (Kafka):
- The OtelCollector processes the incoming data and writes it to a designated Kafka topic. This acts as a resilient buffer.
- Further Processing/Storage:
- The data is then written back to another Kafka topic or directly to a data store for analysis, alerting, and visualization in vuSmartMaps.
The Debug Symbol Upload and automated Desymbolization features used for retrieving symbols and converting obfuscated stack traces into readable ones will be available in a future release.
Client Side Mobile RUM Instrumentation
Mobile RUM supports instrumentation for both Flutter and Android Native applications. Follow the relevant steps below to integrate VuNet's telemetry SDK into your mobile application and begin collecting performance and usage data.
Flutter Instrumentation
Installation
- Download the
MRUM Flutter Instrumentation PackagePlugin from o11ySources page- For more detailed information refer to Configuring the datsource section
- You will get the file named “mrum_Flutter_instrumentation_package.zip”
- Once extracted there will be two folders, among which vuTelemetry-Flutter contains the SDK required to instrument your flutter application.
- There will be another folder called opentelemetry-android
- Add the plugin to your App’s
pubspec.yamlfile and refer to the path where you placed the downloaded plugin. - Then run
flutter pub get
dependencies:
flutter:
sdk: flutter
vutelemetry:
path: ../../Projects/vuTelemetry-Flutter
Android specific setup
- Supports from minimum android version of
7orAPI 24 - In your
android/settings.gradle.ktsfile use the following, replace the path for opentelemetry-android which you have downloaded in the previous steps. Use the relative path instead of the absolute path.
includeBuild("../../<path-to>/opentelemetry-android"){
dependencySubstitution {
substitute(module("io.opentelemetry:android-agent"))
.using(project(":android-agent"))
substitute(module("io.opentelemetry:services"))
.using(project(":services"))
substitute(module("io.opentelemetry:session"))
.using(project(":session"))
}
}
dependencies {
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.4")
}
- In your
android/app/build.gradleorandroid/app/build.gradle.ktsfile- Enable
coreLibraryDesugaring,In projects with file name build.gradle instead of build.gradle.kts use coreLibraryDesugaringEnabled = true else use isCoreLibraryDesugaringEnabled = true itself - Add coreLibraryDesugaring SDK
- Enable
android {
...
compileOptions {
...
isCoreLibraryDesugaringEnabled = true
}
defaultConfig {
minSdk = 24
}
...
}
dependencies {
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.4")
}
iOS specific setup
- Open the iOS project in XCode
- In the AppDelegate.swift under ios/Runner, Import
vutelemetryand PastePLCrashReporterInitializer.initializePLCrashReporter()inside the application callback like the example code to initialize the crash reporting function.
import Flutter
import UIKit
import vutelemetry
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// Initialize PLCrashReporter
PLCrashReporterInitializer.initializePLCrashReporter()
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Initialisation
-
Import the vuTelemetry flutter library with VuTelemetry.initialize in the start of the main function before calling the runApp. The main function is usually found in the lib/main.dart file by default. Make sure to call the WidgetsFlutterBinding.ensureInitialized() before calling the VuTelemetry.initialize function.
-
Initialise with the following params
- logsIngestUrl - Given by MRUM o11ySource
- tracesIngestUrl - Given by MRUM o11ySource
- appName - Name of you application
- appType - Like Flutter , iOS or Android
- enableSlowFrameTracking - Optional param - defaults to false - Used to track slow rendering of flutter screens - If a screen took more than 16ms to render then it is treated as slow rendering
import 'package:flutter/material.dart';
import 'package:vutelemetry/flutter_sdk.dart';
import 'package:http/io_client.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize OpenTelemetry SDK
await VuTelemetry.initialize(
params: InitialisationParams(
logsIngestUrl: 'http://10.0.2.2:4318/v1/logs',
tracesIngestUrl: 'http://10.0.2.2:4318/v1/traces',
appName: '<your-app-name>',
appType: 'Flutter',
enableSlowFrameTracking: true,
apiKey: 'xxxxxxxxxxxx',
buildType: 'Prod',
),
httpClient: IOClient(),
);
runApp(const MainApp());
}
- This will trace all the errors and crashes
Tracing Screen Navigation
Use the RouteObserverService provided by the SDK to automatically trace all the screen navigation events. If there are any other navigation observers already specified, append this RouteObserverService to the list in the MaterialApp or CupertinoApp.
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
navigatorObservers: [RouteObserverService()],
);
}
}
Monitoring Network Calls
Use TrackedHttpOverrides Provided by the SDK
import 'dart:io';
void main() async {
...
HttpOverrides.global = TrackedHttpOverrides();
runApp(const MainApp());
}
This will automatically track Network events and add trace header to every network call in order to help distributed tracing. This http override will work for the http libraries which use Dart's HttpClient under the hood like http and dio.
Tracking Click Events (Optional)
- Use the method
VuTelemetry.logClickEventwith an event name to record any click event. You can provide additional attributes to the click event. - For example, when you click on a button or select an item from the drop down list, you can log the click event using
VuTelemetry.logClickEventin theonChangecallBack of the button or dropdown.
DropdownButton<Account>(
items: [...],
value: provider.selectedAccout,
hint: const Text('Select Account'),
onChanged: (v) {
VuTelemetry.logClickEvent(
'Account Switch',
attributes: {
'account_number': v?.accountNumber ?? '',
},
);
provider.switchAccount(v!);
},
)
Tracking Activity Events(Optional)
- Tracking activity events are to be manually instrumented if needed. It is mostly used to measure the time span between an activity start event and the activity end event as per your use case.
- Example: You can start the activity event when you click on a button and end it with
activity.end()when a reaction happens like a Dialog box has been shown in the UI. This kind of span can be used to measure the time taken to finish an activity. - Use the method
VuTelemetry.logActivitywith an event name to record any Activity event.
// Define the activity variable
var activity;
// Define the Button where you want to start the activity
TextButton(
child: Text("Fund Transfer")
onPressed: (){
// Assigning value to the activity variable defined above
activity = VuTelemetry.logActivity('FundTransfer', attributes: {
'fromAccount': provider.selectedAccount?.accountNumber ?? '',
'toAccount': provider.selectedPayee?.payeeAccountNumber ?? '',
'amount': provider.amountController.text,
});
}
)
// Below is the callBack section on the FundTransfer is done, where you want to show the transfer success/failure dialog
if (provider.state is FinishedTransfer) {
// Show dialog with transfer result
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
// Ending the activity once the dialog is rendered
activity.end();
return Dialog(...);
},
);
}
Setting Custom Global Attributes
You can set any global identifier like userId by using the methods
VuTelemetry.setCustomAttributeVuTelemetry.setCustomAttributes
VuTelemetry.setCustomAttribute(
'userId',
getUserId(), // Implement the function to get the actualUserId of the current user
);
Android Native Instrumentation
Add the following in app/build.gradle.kts or app/build.gradle
OTEL Dependencies
dependencies{
…
// Explicit OpenTelemetry dependencies
implementation ("io.opentelemetry:opentelemetry-api")
implementation ("io.opentelemetry:opentelemetry-sdk")
implementation ("io.opentelemetry:opentelemetry-context")
implementation ("io.opentelemetry:opentelemetry-exporter-otlp")
implementation ("io.opentelemetry:opentelemetry-exporter-logging")
implementation ("io.opentelemetry:opentelemetry-extension-kotlin")
// Remove or align API Incubator version with BOM
implementation ("io.opentelemetry:opentelemetry-api-incubator")
// Android-specific dependencies
implementation("io.opentelemetry.android:android-agent:0.9.1-alpha")
implementation("io.opentelemetry.android:instrumentation-sessions:0.9.1-alpha")
implementation("io.opentelemetry.android:services:0.9.1-alpha")
implementation("androidx.lifecycle:lifecycle-process:2.9.2")
}
Desugaring dependency
When Latest Java Language Features are used in building the application. Desugaring is used to compile the latest language features for older versions as well. So that the older android devices can also be supported. Add the following dependency for enabling desugaring.
dependencies{
...
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.4")
}
Also for the coreLibrary desugaring to work use add coreLibraryDesugaringEnabled = true in compileOptions as shown below
android {
compileSdk = 36
compileOptions {
coreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
...
}
In case if you are using .gradle.kts file use isCoreLibraryDesugaringEnabled = true.
OKHttp Instrumentation Dependency
implementation("io.opentelemetry.android:okhttp-3.0-library:0.9.1-alpha")
For OKHttp AutoInstrumention using byteBuddy
For auto instrumentation, along with the dependency "io.opentelemetry.android: okhttp-3.0-library: 0.9.1-alpha". Add the below dependencies
byteBuddy("io.opentelemetry.android:okhttp-3.0-agent:0.9.1-alpha")
Add the byteBuddy as Plugin
plugins {
...
id("net.bytebuddy.byte-buddy-gradle-plugin") version "1.15.10"
}
Initialising RUM in Android (Kotlin)
- Create an Object Class in kotlin named VuTelemetry and paste the following code
- To Create the Kotlin class: Right-click on your app’s package in app/src/main/java, select New → Kotlin Class/File, name it VuTelemetry, and paste the provided code.
import android.app.Application
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import io.opentelemetry.android.OpenTelemetryRum
import io.opentelemetry.android.OpenTelemetryRumBuilder
import io.opentelemetry.android.config.OtelRumConfig
import io.opentelemetry.android.features.diskbuffering.DiskBufferingConfiguration
import io.opentelemetry.android.instrumentation.AndroidInstrumentation
import io.opentelemetry.android.instrumentation.AndroidInstrumentationLoader
import io.opentelemetry.android.instrumentation.InstallationContext
import io.opentelemetry.android.instrumentation.crash.CrashReporterInstrumentation
import io.opentelemetry.android.internal.services.CacheStorage
import io.opentelemetry.android.internal.services.Preferences
import io.opentelemetry.android.internal.services.ServiceManager
import io.opentelemetry.android.internal.services.applifecycle.AppLifecycleService
import io.opentelemetry.android.internal.services.network.CurrentNetworkProvider
import io.opentelemetry.android.internal.services.periodicwork.PeriodicWorkService
import io.opentelemetry.android.internal.services.visiblescreen.VisibleScreenService
import io.opentelemetry.android.session.SessionManager
import io.opentelemetry.android.session.SessionObserver
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.common.AttributeKey.stringKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.context.Context
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
import io.opentelemetry.instrumentation.library.okhttp.v3_0.OkHttpInstrumentation
import io.opentelemetry.sdk.common.CompletableResultCode
import io.opentelemetry.sdk.logs.LogRecordProcessor
import io.opentelemetry.sdk.logs.ReadWriteLogRecord
import io.opentelemetry.sdk.trace.ReadWriteSpan
import io.opentelemetry.sdk.trace.ReadableSpan
import io.opentelemetry.sdk.trace.SpanProcessor
object VuTelemetry {
private const val TAG = "VuTelemetry"
var rum: OpenTelemetryRum? = null
// A variable to store global attributes as map
var globalAttribute : MutableMap<String, String> = mutableMapOf()
// To be used in manual instrumentation
fun tracer(name: String): Tracer? {
return rum?.openTelemetry?.tracerProvider?.get(name)
}
fun errorSpan(): Span? {
return tracer("io.opentelemetry.android.error")
?.spanBuilder("Error")
?.startSpan()
}
fun addActivity(activityName: String): Span? {
return tracer("io.opentelemetry.android.activity")
?.spanBuilder("Activity")
?.setAllAttributes(Attributes.of(stringKey("event.name"), activityName))
?.startSpan()
}
fun addClickEvent(eventName: String, attributes : MutableMap<String, String>? = null){
val tracer = tracer("io.opentelemetry.android.clicks")
?.spanBuilder("Click")
?.setAllAttributes(Attributes.of(stringKey("event.name"), eventName))
attributes?.forEach { (key, value) ->
tracer?.setAllAttributes(Attributes.of(stringKey(key), value))
}
tracer?.startSpan()?.end()
}
private fun getAppVersionName(application: Application): String {
return try {
val packageInfo = application.packageManager.getPackageInfo(application.packageName, 0)
return packageInfo.versionName.toString()
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"Unknown"
}
}
private fun getAppVersionCode(application: Application): Long {
return try {
val packageInfo = application.packageManager.getPackageInfo(application.packageName, 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode
} else {
@Suppress("DEPRECATION")
packageInfo.versionCode.toLong()
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
-1
}
}
fun initialise(application : Application, appName : String, appType: String, logsIngestUrl: String, spansIngestUrl: String) {
if(rum == null){
val diskBufferingConfig =
DiskBufferingConfiguration.builder()
.setEnabled(true)
.setMaxCacheSize(10_000_000)
.build()
val appVersionName = getAppVersionName(application)
val versionCode = getAppVersionCode(application).toString()
val config =
OtelRumConfig()
.setGlobalAttributes(
Attributes.builder()
.put(stringKey("app.version.name"), appVersionName)
.put(stringKey("app.name"), appName,)
.put(stringKey("app.version.code"), versionCode,)
.put(stringKey("android.type"), appType,)
.put(stringKey("device.manufacturer"), Build.MANUFACTURER,)
.put(stringKey("device.model.identifier"), Build.MODEL,)
.put(stringKey("device.model.name"), Build.MODEL,)
.put(stringKey("os.description") , "Android Version ${Build.VERSION.RELEASE} (Build ${Build.DISPLAY} API level ${Build.VERSION.SDK_INT})",)
.put(stringKey("os.name") , "Android",)
.put(stringKey("os.type") , "linux",)
.put(stringKey("os.version") , Build.VERSION.RELEASE,)
.put(stringKey("os.security.patch") , Build.VERSION.SECURITY_PATCH)
.build()
)
.setDiskBufferingConfiguration(diskBufferingConfig)
val instrumentation: OkHttpInstrumentation? =
AndroidInstrumentationLoader.getInstrumentation(
OkHttpInstrumentation::class.java
)
val crashInstrumentation: CrashReporterInstrumentation? =
AndroidInstrumentationLoader.getInstrumentation(
CrashReporterInstrumentation::class.java
)
val lifeCycleInstrumentation: LifecycleListener? =
AndroidInstrumentationLoader.getInstrumentation(
LifecycleListener::class.java
)
val sessionAwareLogProcessor = SessionAwareLogProcessor {
Log.d(TAG, "onCreate: SessionAwareLogProcessor")
rum?.rumSessionId
}
val otelRumBuilder: OpenTelemetryRumBuilder =
OpenTelemetryRum.builder(application, config)
.addSpanExporterCustomizer {
OtlpHttpSpanExporter.builder()
.setEndpoint(spansIngestUrl)
.build()
}.addTracerProviderCustomizer { sdkTracerProviderBuilder, _ ->
sdkTracerProviderBuilder.addSpanProcessor(GlobalAttributesSpanProcessor())
}
.addLogRecordExporterCustomizer {
OtlpHttpLogRecordExporter.builder()
.setEndpoint(logsIngestUrl)
.build()
}
.addLoggerProviderCustomizer { sdkLoggerProviderBuilder, _ ->
sdkLoggerProviderBuilder.addLogRecordProcessor(sessionAwareLogProcessor)
}
try {
rum = otelRumBuilder.build()
class MySessionManager : SessionManager {
override fun addObserver(observer: SessionObserver) {
TODO("Not yet implemented")
}
override fun getSessionId(): String {
TODO("Not yet implemented")
}
}
class MyServiceManager : ServiceManager{
override fun getAppLifecycleService(): AppLifecycleService {
TODO("Not yet implemented")
}
override fun getCacheStorage(): CacheStorage {
TODO("Not yet implemented")
}
override fun getCurrentNetworkProvider(): CurrentNetworkProvider {
TODO("Not yet implemented")
}
override fun getPeriodicWorkService(): PeriodicWorkService {
TODO("Not yet implemented")
}
override fun getPreferences(): Preferences {
TODO("Not yet implemented")
}
override fun getVisibleScreenService(): VisibleScreenService {
TODO("Not yet implemented")
}
override fun start() {
TODO("Not yet implemented")
}
}
val installationContext = InstallationContext(
application,
rum!!.openTelemetry,
MySessionManager(),
MyServiceManager(),
)
instrumentation?.install(installationContext)
crashInstrumentation?.install(installationContext)
lifeCycleInstrumentation?.install(installationContext)
Log.d(TAG, "RUM session started: " + rum!!.rumSessionId)
} catch (e: Exception) {
Log.e(TAG, "Oh no!", e)
}
}
}
}
class AppLifeCycleListener(openTelemetry: OpenTelemetry) : DefaultLifecycleObserver {
private val TAG = "AppLifeCycleListener"
private val tracer = openTelemetry.tracerProvider
.tracerBuilder("io.opentelemetry.applifecycle")
.build()
override fun onStart(owner: LifecycleOwner) {
Log.d(TAG, "AppForeground")
val span = tracer.spanBuilder("AppForeground").startSpan()
span.end()
}
override fun onStop(owner: LifecycleOwner) {
Log.d(TAG, "AppBackground")
val span = tracer.spanBuilder("AppBackground").startSpan()
span.end()
}
}
class LifecycleListener : AndroidInstrumentation{
override fun install(ctx: InstallationContext) {
val observer = AppLifeCycleListener(ctx.openTelemetry)
ProcessLifecycleOwner.get().lifecycle.addObserver(observer)
}
}
class SessionAwareLogProcessor(
private val sessionIdProvider: () -> String?
) : LogRecordProcessor {
override fun onEmit(context: Context, logRecord: ReadWriteLogRecord){
Log.d("TAG", "onCreate: SessionAwareLogProcessor")
val span = Span.current()
span.makeCurrent()
span.setAttribute(stringKey("test"), "test")
// Add session ID to the log record's attributes
val currentAttributes = logRecord.toLogRecordData().attributes
val updatedAttributes = Attributes.builder()
.putAll(currentAttributes)
.put(stringKey("session.id"), sessionIdProvider().toString())
.build()
logRecord.setAllAttributes(updatedAttributes)
span.end()
}
override fun shutdown(): CompletableResultCode {
return CompletableResultCode.ofSuccess()
}
override fun forceFlush(): CompletableResultCode {
return CompletableResultCode.ofSuccess()
}
}
class GlobalAttributesSpanProcessor : SpanProcessor{
override fun onStart(parentContext: Context, span: ReadWriteSpan) {
VuTelemetry.globalAttribute.forEach { (key, value) ->
span.setAttribute(key, value)
}
}
override fun isStartRequired(): Boolean {
return true
}
override fun onEnd(span: ReadableSpan) {}
override fun isEndRequired(): Boolean {
return false
}
}
After this initialise the code RUM using VuTememetry.initialise function inside the onCreate callback of the Application class or the Launcher Activity(Usually the MainActivity) .
-
YourApplication.kt can usually be found in app/src/main/java/<your-package-name>/
-
If there is no Application class found, you can create it by extending the new class name with Application class.
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
VuTelemetry.initialise(
application = this,
appName = "Wikkipedia",
appType = "Kotlin",
logsIngestUrl = "https://ngdemo.vunetsystems.com/rum/v1/logs",
spansIngestUrl = "https://ngdemo.vunetsystems.com/rum/v1/traces",
)
...
}
}
- In case if you are creating the application class yourself, Make sure it is registered in the Manifest file as shown below
<application
android:name=".MyApp"
... >
...
</application>
CORS
To ensure successful communication between the mobile application and the proxy server, the following CORS-related requirements must be met:
- Restrict Origins
- Only trusted application domains should be allowed to send data. The proxy server must be configured to whitelist specific origins using the following headers:
Access-Control-Allow-Origin: https://<appname.com>
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, logcategory
- Replace
https://<appname.com>with the actual domain(s) of your frontend application.
- Security Mechanism Compatibility
- If the application uses security mechanisms such as Content Security Policy (CSP) or firewalls, these must be configured to allow communication with the proxy.
- CSP headers must permit
connect-srcaccess to the proxy domain. For example:
Content-Security-Policy: script-src 'self'; connect-src 'self' https://<proxy_address>;
- Ensure that the proxy's hostname (e.g.,
https://otelproxy.com) is included in theconnect-srcdirective.
- Preflight Request Support
- The proxy must correctly handle preflight
OPTIONSrequests, which are automatically issued by mobile apps before sending aPOSTrequest to verify CORS permissions. - Make sure your NGINX or proxy server is configured to return the appropriate CORS headers in response to these
OPTIONSrequests.
- The proxy must correctly handle preflight
Role of the Proxy Server in Mobile RUM
The proxy server is a critical component in a Real User Monitoring (RUM) deployment. It acts as a secure intermediary between end-user mobile applications (where the RUM agent runs) and the backend RUM data collector. Integrating a proxy server provides the following technical benefits:
- SSL/TLS Termination: The proxy server terminates HTTPS connections, ensuring encrypted and trusted transmission of RUM data from mobiles.
- CORS Enforcement: It manages Cross-Origin Resource Sharing (CORS) headers, allowing only requests from approved frontend domains to be processed, which protects against cross-site data leaks.
- API Key and Authorization Validation: Incoming requests are checked for valid API keys or authorization tokens. Unauthorized requests are rejected, ensuring only trusted clients can send monitoring data.
- Request Routing and Load Balancing: The proxy server forwards accepted telemetry data to the correct backend RUM collector endpoint and can distribute load across multiple backend instances for high availability.
- Network Security: Only the proxy is exposed to the internet. The backend RUM collector stays protected within a private network, and firewall rules only need to allow access to the proxy.
- Client IP Forwarding: To enable accurate user attribution, the proxy passes the original client IP address to the backend using headers such as
X-Forwarded-For. - Preflight and OPTIONS Handling: The proxy handles mobile CORS preflight (
OPTIONS) requests, which is essential for seamless communication from modern mobile applications. - Security Headers Management: Additional security headers (like
Strict-Transport-SecurityandX-Frame-Options) can be injected to enhance security.
