NB: APIs are subject to change until a 1.x version is released.
The Android observability plugin automatically instruments:
- Activity Lifecycle: App lifecycle events and transitions
- HTTP Requests: OkHttp and HttpURLConnection requests (requires setup of ByteBuddy compile time plugin and additional dependencies)
- Crash Reporting: Automatic crash reporting and stack traces
- Feature Flag Evaluations: Evaluation events added to your spans.
- Session Management: User session tracking and background timeout handling
A complete example application is available in the e2e/android directory.
Add the dependency to your app's Gradle file:
dependencies {
implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.+")
implementation("com.launchdarkly:launchdarkly-observability-android:0.19.1")
}Add the observability plugin to your LaunchDarkly Android Client SDK configuration:
import com.launchdarkly.observability.plugin.Observability
import com.launchdarkly.sdk.android.LDConfig
import com.launchdarkly.sdk.android.Components
import com.launchdarkly.sdk.android.LDClient
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val mobileKey = "your-mobile-key"
val ldConfig = LDConfig.Builder(LDConfig.Builder.AutoEnvAttributes.Enabled)
.mobileKey(mobileKey)
.plugins(
Components.plugins().setPlugins(
listOf(
Observability(this@MyApplication, mobileKey)
)
)
)
.build()
val context = LDContext.builder(ContextKind.DEFAULT, "user-key")
.build()
LDClient.init(this@MyApplication, ldConfig, context)
}
}To enable HTTP request instrumentation and user interaction instrumentation, add the following plugin and dependencies to your top level application's Gradle file.
plugins {
id 'net.bytebuddy.byte-buddy-gradle-plugin' version '1.+'
}
dependencies {
// Android HTTP Url instrumentation
implementation 'io.opentelemetry.android.instrumentation:httpurlconnection-library:0.11.0-alpha'
byteBuddy 'io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.11.0-alpha'
// OkHTTP instrumentation
implementation 'io.opentelemetry.android.instrumentation:okhttp3-library:0.11.0-alpha'
byteBuddy 'io.opentelemetry.android.instrumentation:okhttp3-agent:0.11.0-alpha'
}You can customize the observability plugin with various options:
import com.launchdarkly.observability.api.ObservabilityOptions
import com.launchdarkly.sdk.android.LDAndroidLogging
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
val mobileKey = "your-mobile-key"
val observabilityPlugin = Observability(
application = this@MyApplication,
mobileKey = mobileKey,
options = ObservabilityOptions(
serviceName = "my-android-app",
serviceVersion = "1.0.0",
debug = true,
logAdapter = LDAndroidLogging.adapter(),
resourceAttributes = Attributes.of(
AttributeKey.stringKey("environment"), "production",
AttributeKey.stringKey("team"), "mobile"
),
customHeaders = mapOf(
"X-Custom-Header" to "custom-value"
)
)
)Additional ObservabilityOptions settings:
logsApiLevel: Minimum log severity to export (defaults toINFO). Set toObservabilityOptions.LogLevel.NONEto disable log exporting.tracesApi: Controls trace recording (defaults to enabled). UseObservabilityOptions.TracesApi.disabled()to disable all tracing, or setincludeErrors/includeSpans.metricsApi: Controls metric export (defaults to enabled). UseObservabilityOptions.MetricsApi.disabled()to disable metrics.instrumentations: Enables/disables specific automatic instrumentations likecrashReporting,activityLifecycle, andlaunchTime.
Example:
val options = ObservabilityOptions(
logsApiLevel = ObservabilityOptions.LogLevel.WARN,
tracesApi = ObservabilityOptions.TracesApi(includeErrors = true, includeSpans = false),
metricsApi = ObservabilityOptions.MetricsApi.disabled(),
instrumentations = ObservabilityOptions.Instrumentations(
crashReporting = false,
activityLifecycle = true,
launchTime = true
)
)After initialization of the LaunchDarkly Android Client SDK, use LDObserve to record metrics, logs, errors, and traces:
import com.launchdarkly.observability.sdk.LDObserve
import com.launchdarkly.observability.interfaces.Metric
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.logs.Severity
// Record metrics
LDObserve.recordMetric(Metric("user_actions", 1.0))
LDObserve.recordCount(Metric("api_calls", 1.0))
LDObserve.recordIncr(Metric("page_views", 1.0))
LDObserve.recordHistogram(Metric("response_time", 150.0))
LDObserve.recordUpDownCounter(Metric("active_connections", 1.0))
// Record logs
LDObserve.recordLog(
"User performed action",
Severity.INFO,
Attributes.of(
AttributeKey.stringKey("user_id"), "12345",
AttributeKey.stringKey("action"), "button_click"
)
)
// Record errors
LDObserve.recordError(
Exception("Something went wrong"),
Attributes.of(
AttributeKey.stringKey("component"), "payment",
AttributeKey.stringKey("error_code"), "PAYMENT_FAILED"
)
)
// Create spans for tracing
val span = LDObserve.startSpan(
"api_request",
Attributes.of(
AttributeKey.stringKey("endpoint"), "/api/users",
AttributeKey.stringKey("method"), "GET"
)
)
span.makeCurrent().use {
// Your code here
}
span.end()Add the Session Replay plugin after Observability when configuring the LaunchDarkly SDK:
import com.launchdarkly.observability.plugin.Observability
import com.launchdarkly.observability.replay.plugin.SessionReplay
val ldConfig = LDConfig.Builder(LDConfig.Builder.AutoEnvAttributes.Enabled)
.mobileKey("your-mobile-key")
.plugins(
Components.plugins().setPlugins(
listOf(
Observability(this@MyApplication, "your-mobile-key"),
SessionReplay() // depends on Observability being present first
)
)
)
.build()Notes:
- SessionReplay depends on Observability. If Observability is missing or listed after SessionReplay, the plugin logs an error and stays inactive.
- Observability runs fine without SessionReplay; adding SessionReplay extends the Observability pipeline to include session recording.
Use ldMask() to mark views that should be masked in session replay. There are helpers for both XML-based Views and Jetpack Compose.
If you want to configure masking globally (instead of calling ldMask() on each element), pass a PrivacyProfile to ReplayOptions:
import com.launchdarkly.observability.replay.PrivacyProfile
import com.launchdarkly.observability.replay.ReplayOptions
import com.launchdarkly.observability.replay.view
import com.launchdarkly.observability.replay.plugin.SessionReplay
val sessionReplay = SessionReplay(
ReplayOptions(
privacyProfile = PrivacyProfile(
// New settings:
maskViews = listOf(
// Masks targets by *exact* Android View class (does not match subclasses).
view(android.widget.ImageView::class),
// You can also provide the class name as a string (FQCN).
view("android.widget.EditText"),
),
maskXMLViewIds = listOf(
// Masks by resource entry name (from resources.getResourceEntryName(view.id)).
// Accepts either "@+id/foo" or "foo".
"@+id/password",
"credit_card_number",
),
)
)
)Notes:
maskViewsmatches ontarget.view.javaClassequality (exact class only).maskXMLViewIdsapplies only to Views with a non-View.NO_IDid that resolves to a resource entry name.
Import the masking API and call ldMask() on any View (for example, after inflating the layout in an Activity or Fragment).
import com.launchdarkly.observability.api.ldMask
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
val password = findViewById<EditText>(R.id.password)
password.ldMask() // mask this field in session replay
}
}With View Binding or Data Binding:
import com.launchdarkly.observability.api.ldMask
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = SettingsPageBinding.inflate(inflater, container, false)
binding.nestedScrollView.systemBarsPadding()
viewModel.toggleBackgroundAccess(requireContext().isIgnoreBatteryEnabled())
val toolbar = binding.toolbar
toolbar.ldMask()
}Optional: use ldUnmask() to explicitly clear masking on a view you previously masked.
Add the masking Modifier to any composable you want masked in session replay.
import com.launchdarkly.observability.api.ldMask
@Composable
fun CreditCardField() {
...
var zipCode by remember { mutableStateOf("") }
OutlinedTextField(
value = zipCode,
onValueChange = { zipCode = it },
label = { Text("ZIP Code") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier
.fillMaxWidth()
.ldMask()
)
}Optional: use Modifier.ldUnmask() to explicitly clear masking on a composable you previously masked.
Notes:
- Masking marks elements so their contents are obscured in recorded sessions.
- You can apply masking to any
Viewor composable where sensitive data may appear.
We encourage pull requests and other contributions from the community. Check out our contributing guidelines for instructions on how to contribute to this SDK.
- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
- Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
- Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
- Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
- Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read our documentation for a complete list.
- Explore LaunchDarkly
- launchdarkly.com for more information
- docs.launchdarkly.com for our documentation and SDK reference guides
- apidocs.launchdarkly.com for our API documentation
- launchdarkly.com/blog for the latest product updates