feat(snippets): port Android observability plugin docs to canonical snippets#536
feat(snippets): port Android observability plugin docs to canonical snippets#536kinyoklion wants to merge 7 commits into
Conversation
…real compile Port the Android observability plugin page's Kotlin blocks (15: install/ initialize, plugin options, session replay config + start/stop/flush, privacy profiles, distributed tracing) to canonical snippets, type-checked against the real com.launchdarkly:launchdarkly-observability-android 0.60.0 aar via compileDebugKotlin in the android-client validator. - Bump the baked observability plugin to 0.60.0; add opentelemetry-api 1.51.0 + okhttp to the validator so tracing fragments type-check. - New kotlin-observability scaffold. Imports the OTel types explicitly rather than by wildcard: the observability aar ships a com.launchdarkly.observability.replay.Attributes that would otherwise shadow io.opentelemetry.api.common.Attributes for the unqualified name the fragments use. - Reconcile two blocks to the current 0.60.0 API: Instrumentations dropped activityLifecycle (now userTaps/screens); ReplayOptions dropped capturePeriodMillis. Part of the observability section port (android tranche).
Java blocks (init, configure options, 3 distributed-tracing) type-checked against the real plugin via a new java-observability scaffold. Reconciled to the plugin's actual Java interop (confirmed against observability-sdk #605's pure-Java e2e): - LDObserve is a companion object with no @JvmStatic, so Java calls it as LDObserve.Companion.startSpan(...). - The Observability plugin constructor has no @jvmoverloads, so Java passes the full 4-arg form with ObservabilityOptions.builder().build() (the data class has no Java no-arg constructor) and a trailing null. Gradle install blocks are rendered from canonical but not validated: Android AAR coordinates can't be resolved by the shell-install plain-JVM gradle project. Part of the observability section port (android tranche).
…ated build/http blocks - Fix the truncated distributed-tracing 'log with context' fragment (span.en -> span.end()) and add a coroutine-scope scaffold variant so its launch(Dispatchers.IO) resolves a CoroutineScope receiver. - Render (unvalidated) the byte-buddy instrumentation gradle config blocks and the W3C trace-context header example (build config / headers, not compilable code). Part of the observability section port (android tranche).
…mpose validator New dedicated android-compose validator (Kotlin 2.0.21 + Compose 1.7.x) that type-checks the plugin's Compose Modifier.ldMask()/ldUnmask() and the View EditText.ldMask() masking fragments against the real plugin. Separate from the Kotlin-1.8 android-client validator because the plugin's Compose masking API carries Kotlin-2.0 metadata that the older compiler can't read. Completes the android observability tranche (29/29 blocks): 24 real type-checked across the two validators, 5 rendered-only (gradle install/config + http header). Adds android-observability-port-notes.md documenting fixes + the plugin's Java interop gaps. Part of the observability section port (android tranche).
Validates the observability group across both the android-client and android-compose validators (one group row drives both).
…ity constructor Bumping the shared android-client validator's observability plugin from 0.54.0 to 0.60.0 (for the observability port) added a 4th customSessionId param to the Observability constructor. With no @jvmoverloads, the existing initialize-the-client-android-sdk-v5-x-java example's 3-arg call no longer compiles from Java; add the trailing null to match the current 4-arg constructor. The Kotlin sibling is unaffected (default args).
|
|
||
| ```java | ||
| implementation 'com.launchdarkly:launchdarkly-android-client-sdk:5.+' | ||
| implementation 'com.launchdarkly:launchdarkly-observability-android:0.60.0' |
There was a problem hiding this comment.
Version updated to the current plugin (0.60.0). Original doc snippet:
implementation 'com.launchdarkly:launchdarkly-android-client-sdk:5.+'
implementation 'com.launchdarkly:launchdarkly-observability-android:0.21.0'|
|
||
| ```kotlin | ||
| implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.+") | ||
| implementation("com.launchdarkly:launchdarkly-observability-android:0.60.0") |
There was a problem hiding this comment.
Version updated to the current plugin (0.60.0). Original doc snippet:
implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.+")
implementation("com.launchdarkly:launchdarkly-observability-android:0.21.0")| instrumentations = ObservabilityOptions.Instrumentations( | ||
| crashReporting = false, | ||
| launchTime = true, | ||
| userTaps = true, |
There was a problem hiding this comment.
Reconciled to the 0.60.0 API: Instrumentations dropped activityLifecycle (now userTaps/screens). Original doc snippet:
val mobileKey = "example-mobile-key"
val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled)
.mobileKey(mobileKey)
.plugins(
Components.plugins().setPlugins(
listOf(
Observability(
this@BaseApplication,
mobileKey,
ObservabilityOptions(
serviceName = "my-android-app",
serviceVersion = "1.0.0",
debug = true,
logsApiLevel = ObservabilityOptions.LogLevel.WARN,
tracesApi = ObservabilityOptions.TracesApi(includeErrors = true, includeSpans = false),
metricsApi = ObservabilityOptions.MetricsApi.disabled(),
instrumentations = ObservabilityOptions.Instrumentations(
crashReporting = false,
activityLifecycle = true,
launchTime = true
),
resourceAttributes = Attributes.of(
AttributeKey.stringKey("environment"), "production",
AttributeKey.stringKey("team"), "mobile"
),
customHeaders = mapOf(
"X-Custom-Header" to "custom-value"
)
)
)
)
)
)
.build()| maskTextInputs = true, | ||
| maskText = true | ||
| ), | ||
| debug = false |
There was a problem hiding this comment.
Reconciled to 0.60.0: ReplayOptions.capturePeriodMillis was removed. Original doc snippet:
import com.launchdarkly.observability.plugin.Observability
import com.launchdarkly.observability.replay.plugin.SessionReplay
import com.launchdarkly.observability.replay.ReplayOptions
import com.launchdarkly.observability.replay.PrivacyProfile
val mobileKey = "example-mobile-key"
val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled)
.mobileKey(mobileKey)
.plugins(
Components.plugins().setPlugins(
listOf(
Observability(this@BaseApplication, mobileKey),
SessionReplay(
options = ReplayOptions(
privacyProfile = PrivacyProfile(
maskTextInputs = true,
maskText = true
),
capturePeriodMillis = 1000,
debug = false
)
)
)
)
)
.build()| ) | ||
| // Capture span context while still on the originating thread. | ||
| val capturedContext = span.makeCurrent().use { span.spanContext } | ||
| span.end() |
There was a problem hiding this comment.
Original was truncated at span.en; completed to span.end(). Original doc snippet:
import io.opentelemetry.context.Context
val parentSpan = LDObserve.startSpan("parentSpan", Attributes.empty())
// Capture the current context, which includes the active span
val context = Context.current()
launch(Dispatchers.IO) {
val span = LDObserve.startSpan(
name = "log-context-demo",
attributes = Attributes.of(
AttributeKey.stringKey("demo"), "log-with-context"
)
)
// Capture span context while still on the originating thread.
val capturedContext = span.makeCurrent().use { span.spanContext }
span.en
// Simulate a detached thread where OTel context is lost automatically.
// Span.current() here returns INVALID, so we pass the captured context explicitly.
Thread {
Span.wrap(capturedContext).makeCurrent().use {
val childSpan = LDObserve.startSpan("child of log-context-demo", Attributes.empty())
childSpan.end()
}
LDObserve.recordLog(
message = text,
severity = Severity.WARN,
attributes = Attributes.of(
AttributeKey.stringKey("source"), "detached-thread-demo"
),
spanContext = capturedContext
)
}.start()
}
parentSpan.end()| LDConfig ldConfig = new LDConfig.Builder(AutoEnvAttributes.Enabled) | ||
| .mobileKey(mobileKey) | ||
| .plugins(Components.plugins().setPlugins( | ||
| Collections.<Plugin>singletonList( |
There was a problem hiding this comment.
Rewritten to valid Java for the 0.60.0 constructor (Collections.<Plugin>singletonList + new + the 4-arg form). Original doc snippet:
String mobileKey = "example-mobile-key";
LDConfig ldConfig = new LDConfig.Builder(AutoEnvAttributes.Enabled)
.mobileKey(mobileKey)
.plugins(Components.plugins().setPlugins(
Collections.singletonList<Plugin>(Observability(this.getApplication(), mobileKey))
))
// other options
.build();
// You'll need this context later, but you can ignore it for now.
LDContext context = LDContext.create("example-context-key");
LDClient client = LDClient.init(this.getApplication(), ldConfig, context, 0);| new Observability( | ||
| this.getApplication(), | ||
| mobileKey, | ||
| ObservabilityOptions.builder() |
There was a problem hiding this comment.
Rewritten to valid Java (no Kotlin named/default args): builder + 4-arg constructor. Original doc snippet:
String mobileKey = "example-mobile-key";
LDConfig ldConfig = new LDConfig.Builder(AutoEnvAttributes.Enabled)
.mobileKey(mobileKey)
.plugins(
Components.plugins().setPlugins(
Collections.singletonList<Plugin>(
Observability(
this.getApplication(),
mobileKey,
ObservabilityOptions(
resourceAttributes = Attributes.of(
AttributeKey.stringKey("serviceName"), "example-service"
)
)
)
)
)
)
.build();| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.Scope; | ||
|
|
||
| Span parentSpan = LDObserve.Companion.startSpan("parentSpan", Attributes.empty()); |
There was a problem hiding this comment.
LDObserve is a companion object with no @JvmStatic, so Java calls LDObserve.Companion.startSpan(...). Original doc snippet:
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
Span parentSpan = LDObserve.startSpan("parentSpan", Attributes.empty());
try (Scope parentScope = parentSpan.makeCurrent()) {
Context context = Context.current();
new Thread(() -> {
try (Scope childScope = context.makeCurrent()) {
Span childSpan = LDObserve.startSpan("childSpan", Attributes.empty());
// do work
childSpan.end();
}
}).start();
} finally {
parentSpan.end();
}| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.Scope; | ||
|
|
||
| Span parentSpan = LDObserve.Companion.startSpan("parentSpan", Attributes.empty()); |
There was a problem hiding this comment.
LDObserve.Companion for Java, and removed a stray extra closing brace. Original doc snippet:
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
Span parentSpan = LDObserve.startSpan("parentSpan", Attributes.empty());
try (Scope parentScope = parentSpan.makeCurrent()) {
// Now parentSpan is active in Context.current()
Context ctx = Context.current();
// Later, in another thread, restore the context
executor.execute(() -> {
try (Scope scope = ctx.makeCurrent()) {
Span nestedSpan = LDObserve.startSpan("nestedSpan", Attributes.empty());
// do work — nestedSpan is a child of parentSpan
nestedSpan.end();
}
});
} finally {
parentSpan.end();
}}| .plugins(Components.plugins().setPlugins( | ||
| Collections.<Plugin>singletonList( | ||
| new Observability(this.getApplication(), "example-mobile-key", ObservabilityOptions.builder().build()) | ||
| new Observability(this.getApplication(), "example-mobile-key", ObservabilityOptions.builder().build(), null) |
There was a problem hiding this comment.
The 0.60.0 Observability constructor added a 4th customSessionId param (no @JvmOverloads), so the 3-arg call needed a trailing null. Original snippet:
LDConfig ldConfig = new LDConfig.Builder(AutoEnvAttributes.Enabled)
.mobileKey("example-mobile-key")
// optional observability plugin, requires LaunchDarkly Android Client SDK v5.9+
.plugins(Components.plugins().setPlugins(
Collections.<Plugin>singletonList(
new Observability(this.getApplication(), "example-mobile-key", ObservabilityOptions.builder().build())
)
))
// other options
.build();
// You'll need this context later, but you can ignore it for now.
LDContext context = LDContext.create("example-context-key");
LDClient client = LDClient.init(this.getApplication(), ldConfig, context, 0);Reconcile the android observability snippets with the current android.mdx, which grew a product-analytics section and a manual-instrumentation section and moved the distributed-tracing blocks to the plugin's map-based API. - Update tracing snippets to the map-based style (startSpan(name, properties=...), 1-arg startSpan). Java keeps the compilable LDObserve.Companion form since the companion has no @JvmStatic. - Add snippets: import-java/kotlin, configure-product-analytics-kotlin, record-logs[-typed]-kotlin, record-traces[-typed]-kotlin, track-event-kotlin, track-screen-view-kotlin. All type-check against observability-android 0.60.0. - Analytics omits pageViews (not a field on 0.60.0 or plugin main). - Scaffolds gain a performDatabaseQuery() stub and java.util Map imports.
Ports the Android observability plugin reference page (
fern/topics/sdk/observability/android.mdx, 29 code blocks) to canonical snippets — the first tranche of the observability section. Type-checked against the real published pluginlaunchdarkly-observability-android:0.60.0(the docs' install block pinned a stale 0.21.0).Validation (real type-check)
android-clientvalidator (compileDebug{Kotlin,JavaWithJavac}), with the observability plugin bumped to 0.60.0 andopentelemetry-api:1.51.0+okhttpadded. Newkotlin-observability,kotlin-observability-coroutine, andjava-observabilityscaffolds. OTel types are imported explicitly (not wildcard): the plugin aar ships acom.launchdarkly.observability.replay.Attributesthat otherwise shadowsio.opentelemetry.api.common.Attributesfor the unqualified name the fragments use.android-composevalidator (Kotlin 2.0.21 + Compose 1.7.x). The plugin's Compose masking API (Modifier.ldMask()) is built against Compose 1.7.x, which carries Kotlin-2.0 metadata the Kotlin-1.8android-clientvalidator can't read — so it gets its own project rather than risking a Kotlin bump of the shared validator. The View block'sR.layout.activity_login/R.id.passwordresolve against a layout baked into that validator.Snippet fixes (reconciled to 0.60.0 / valid code)
Instrumentations(activityLifecycle=…)→userTaps/screens(param removed in 0.60.0).ReplayOptions(capturePeriodMillis=…)→ removed (no such param).span.en→span.end().Collections.singletonList<Plugin>(Observability(...))(Kotlin syntax) →Collections.<Plugin>singletonList(new Observability(...)).Plugin Java-interop gaps found (SDK follow-up)
The Java examples only compile in non-idiomatic forms because the plugin lacks Java-friendly annotations:
LDObserveis a companion object with no@JvmStatic→ Java callsLDObserve.Companion.startSpan(...)(matches observability-sdk#605's own pure-Java e2e).Observabilityconstructor has no@JvmOverloads(andObservabilityOptionsis a Kotlin data class with no Java no-arg constructor) → Java init must use the full 4-argnew Observability(app, key, ObservabilityOptions.builder().build(), null). Adding@JvmOverloadswould let the docs use the clean 2-arg form.Phase B (ld-docs markers) follows once this merges + a snippets release is cut. Part of the docs-porting effort (SDK-2434).