Skip to content

Commit fbf2ff6

Browse files
authored
refactor: introduce granular ObservabilityOptions (#323)
## Summary Refactor the monolithic `Options` data class into a more granular `ObservabilityOptions` structure. This change introduces nested classes for `TracesApi`, `MetricsApi`, and `Instrumentations`, and replaces boolean flags (`disableLogs`, `disableTraces`, `disableMetrics`, `disableErrorTracking`). - `disableLogs` is replaced by `logsApiLevel`, which allows filtering logs by severity. - `disableTraces` and `disableErrorTracking` are replaced by `tracesApi` to control spans and errors separately. - `disableMetrics` is replaced by the `metricsApi` object. - Automatic instrumentations like crash reporting and launch time can now be configured under the `instrumentations` property. ## How did you test this change? Unit tested ## Are there any deployment considerations? No <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces ObservabilityOptions with log-level filtering and per-signal/instrumentation toggles, updating SDK, tests, and sample app to the new API. > > - **Core (SDK)**: > - Replace `Options` with new ``ObservabilityOptions`` featuring ``TracesApi``, ``MetricsApi``, ``Instrumentations`` and ``LogLevel``. > - Update ``InstrumentationManager``/``ObservabilityClient``/``ObservabilityContext``/``Observability`` to use new options. > - Implement severity-based log exporting via ``logsApiLevel``; gate spans/errors/metrics via ``tracesApi``/``metricsApi``; toggle crash reporting, activity lifecycle, and launch time instrumentation via ``instrumentations``. > - Add activity instrumentation suppression and helper to add launch-time instrumentation; remove legacy disable-flag logic. > - **E2E/Tests**: > - Rewrite tests to assert behavior for ``logsApiLevel``, ``tracesApi`` includes (errors/spans), ``metricsApi`` enabled/disabled, and crash reporting; initialize sampling tests with new options. > - **Sample app**: > - Migrate to ``ObservabilityOptions`` in `e2e` app; adjust default log severity to ``INFO``. > - **Build**: > - Minor dependency organization for OTEL Android/Instrumentations. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d3d187b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 63dcf12 commit fbf2ff6

File tree

13 files changed

+350
-193
lines changed

13 files changed

+350
-193
lines changed

e2e/android/app/src/main/java/com/example/androidobservability/BaseApplication.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.example.androidobservability
22

33
import android.app.Application
4-
import com.launchdarkly.observability.api.Options
4+
import com.launchdarkly.observability.api.ObservabilityOptions
55
import com.launchdarkly.observability.client.TelemetryInspector
66
import com.launchdarkly.observability.plugin.Observability
77
import com.launchdarkly.observability.replay.PrivacyProfile
@@ -24,11 +24,16 @@ open class BaseApplication : Application() {
2424
const val LAUNCHDARKLY_MOBILE_KEY = "MOBILE_KEY_GOES_HERE"
2525
}
2626

27-
var pluginOptions = Options(
27+
var observabilityOptions = ObservabilityOptions(
2828
resourceAttributes = Attributes.of(
2929
AttributeKey.stringKey("example"), "value"
3030
),
3131
debug = true,
32+
tracesApi = ObservabilityOptions.TracesApi(includeErrors = false, includeSpans = false),
33+
metricsApi = ObservabilityOptions.MetricsApi.disabled(),
34+
instrumentations = ObservabilityOptions.Instrumentations(
35+
crashReporting = true, launchTime = true, activityLifecycle = false
36+
),
3237
logAdapter = LDAndroidLogging.adapter(),
3338
)
3439

@@ -39,7 +44,7 @@ open class BaseApplication : Application() {
3944
val observabilityPlugin = Observability(
4045
application = this@BaseApplication,
4146
mobileKey = LAUNCHDARKLY_MOBILE_KEY,
42-
options = testUrl?.let { pluginOptions.copy(backendUrl = it, otlpEndpoint = it) } ?: pluginOptions
47+
options = testUrl?.let { observabilityOptions.copy(backendUrl = it, otlpEndpoint = it) } ?: observabilityOptions
4348
)
4449

4550
val sessionReplayPlugin = SessionReplay(

e2e/android/app/src/main/java/com/example/androidobservability/ViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class ViewModel(application: Application) : AndroidViewModel(application) {
5454
fun triggerLog() {
5555
LDObserve.recordLog(
5656
"Test Log",
57-
Severity.DEBUG,
57+
Severity.INFO,
5858
Attributes.of(AttributeKey.stringKey("FakeAttribute"), "FakeVal")
5959
)
6060
}

e2e/android/app/src/test/java/com/example/androidobservability/DisablingConfigOptionsE2ETest.kt

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.app.Application
44
import androidx.test.core.app.ApplicationProvider
55
import com.example.androidobservability.TestUtils.TelemetryType
66
import com.example.androidobservability.TestUtils.waitForTelemetryData
7-
import com.launchdarkly.observability.api.Options
7+
import com.launchdarkly.observability.api.ObservabilityOptions
88
import com.launchdarkly.observability.interfaces.Metric
99
import com.launchdarkly.observability.sdk.LDObserve
1010
import io.opentelemetry.api.common.AttributeKey
@@ -27,12 +27,12 @@ class DisablingConfigOptionsE2ETest {
2727
private val application = ApplicationProvider.getApplicationContext<Application>() as TestApplication
2828

2929
@Test
30-
fun `Logs should NOT be exported when disableLogs is set to true`() {
31-
application.pluginOptions = getOptionsAllEnabled().copy(disableLogs = true)
30+
fun `Logs should NOT be exported when logsApiLevel is NONE`() {
31+
application.observabilityOptions = getOptionsAllEnabled().copy(logsApiLevel = ObservabilityOptions.LogLevel.NONE)
3232
application.initForTest()
3333
val logsUrl = "http://localhost:${application.mockWebServer?.port}/v1/logs"
3434

35-
triggerTestLog()
35+
triggerTestLog(severity = Severity.TRACE)
3636
LDObserve.flush()
3737
waitForTelemetryData(telemetryInspector = application.telemetryInspector, telemetryType = TelemetryType.LOGS)
3838
val logsExported = application.telemetryInspector?.logExporter?.finishedLogRecordItems
@@ -42,22 +42,56 @@ class DisablingConfigOptionsE2ETest {
4242
}
4343

4444
@Test
45-
fun `Logs should be exported when disableLogs is set to false`() {
46-
application.pluginOptions = getOptionsAllEnabled().copy(disableLogs = false)
45+
fun `Logs should NOT be exported when log severity is lower than logsApiLevel`() {
46+
application.observabilityOptions = getOptionsAllEnabled().copy(logsApiLevel = ObservabilityOptions.LogLevel.INFO)
4747
application.initForTest()
4848
val logsUrl = "http://localhost:${application.mockWebServer?.port}/v1/logs"
4949

50-
triggerTestLog()
50+
triggerTestLog(severity = Severity.TRACE)
51+
LDObserve.flush()
52+
waitForTelemetryData(telemetryInspector = application.telemetryInspector, telemetryType = TelemetryType.LOGS)
53+
val logsExported = application.telemetryInspector?.logExporter?.finishedLogRecordItems
54+
55+
assertTrue(logsExported?.isEmpty() == true)
56+
assertFalse(requestsContainsUrl(logsUrl))
57+
}
58+
59+
@Test
60+
fun `Logs should be exported when log severity is higher than logsApiLevel`() {
61+
application.observabilityOptions = getOptionsAllEnabled().copy(logsApiLevel = ObservabilityOptions.LogLevel.INFO)
62+
application.initForTest()
63+
val logsUrl = "http://localhost:${application.mockWebServer?.port}/v1/logs"
64+
65+
triggerTestLog(severity = Severity.WARN)
5166
LDObserve.flush()
5267
waitForTelemetryData(telemetryInspector = application.telemetryInspector, telemetryType = TelemetryType.LOGS)
5368

5469
assertNotNull(application.telemetryInspector?.logExporter)
5570
assertTrue(requestsContainsUrl(logsUrl))
5671
}
5772

73+
74+
@Test
75+
fun `Spans should NOT be exported when TracesApi is disabled`() {
76+
application.observabilityOptions = getOptionsAllEnabled().copy(tracesApi = ObservabilityOptions.TracesApi.disabled())
77+
application.initForTest()
78+
val tracesUrl = "http://localhost:${application.mockWebServer?.port}/v1/traces"
79+
80+
triggerTestSpan()
81+
LDObserve.flush()
82+
83+
waitForTelemetryData(telemetryInspector = application.telemetryInspector, telemetryType = TelemetryType.SPANS)
84+
val spansExported = application.telemetryInspector?.spanExporter?.finishedSpanItems
85+
86+
assertTrue(spansExported?.isEmpty() == true)
87+
assertFalse(requestsContainsUrl(tracesUrl))
88+
}
89+
5890
@Test
59-
fun `Spans should NOT be exported when disableTraces is set to true`() {
60-
application.pluginOptions = getOptionsAllEnabled().copy(disableTraces = true)
91+
fun `Spans should NOT be exported when TracesApi does not include spans`() {
92+
application.observabilityOptions = getOptionsAllEnabled().copy(
93+
tracesApi = ObservabilityOptions.TracesApi(includeSpans = false)
94+
)
6195
application.initForTest()
6296
val tracesUrl = "http://localhost:${application.mockWebServer?.port}/v1/traces"
6397

@@ -72,8 +106,8 @@ class DisablingConfigOptionsE2ETest {
72106
}
73107

74108
@Test
75-
fun `Spans should be exported when disableTraces is set to false`() {
76-
application.pluginOptions = getOptionsAllEnabled().copy(disableTraces = false)
109+
fun `Spans should be exported when TracesApi is enabled`() {
110+
application.observabilityOptions = getOptionsAllEnabled().copy(tracesApi = ObservabilityOptions.TracesApi.enabled())
77111
application.initForTest()
78112
val tracesUrl = "http://localhost:${application.mockWebServer?.port}/v1/traces"
79113

@@ -86,8 +120,9 @@ class DisablingConfigOptionsE2ETest {
86120
}
87121

88122
@Test
89-
fun `Metrics should NOT be exported when disableMetrics is set to true`() {
90-
application.pluginOptions = getOptionsAllEnabled().copy(disableMetrics = true)
123+
fun `Metrics should NOT be exported when disabled`() {
124+
application.observabilityOptions =
125+
getOptionsAllEnabled().copy(metricsApi = ObservabilityOptions.MetricsApi.disabled())
91126
application.initForTest()
92127
val metricsUrl = "http://localhost:${application.mockWebServer?.port}/v1/metrics"
93128

@@ -99,8 +134,9 @@ class DisablingConfigOptionsE2ETest {
99134
}
100135

101136
@Test
102-
fun `Metrics should be exported when disableMetrics is set to false`() {
103-
application.pluginOptions = getOptionsAllEnabled().copy(disableMetrics = false)
137+
fun `Metrics should be exported when enabled`() {
138+
application.observabilityOptions =
139+
getOptionsAllEnabled().copy(metricsApi = ObservabilityOptions.MetricsApi.enabled())
104140
application.initForTest()
105141
val metricsUrl = "http://localhost:${application.mockWebServer?.port}/v1/metrics"
106142

@@ -112,8 +148,10 @@ class DisablingConfigOptionsE2ETest {
112148
}
113149

114150
@Test
115-
fun `Errors should NOT be exported when disableErrorTracking is set to true`() {
116-
application.pluginOptions = getOptionsAllEnabled().copy(disableErrorTracking = true)
151+
fun `Errors should NOT be exported when TracesApi does not include errors`() {
152+
application.observabilityOptions = getOptionsAllEnabled().copy(
153+
tracesApi = ObservabilityOptions.TracesApi(includeErrors = false)
154+
)
117155
application.initForTest()
118156
val tracesUrl = "http://localhost:${application.mockWebServer?.port}/v1/traces"
119157

@@ -128,8 +166,10 @@ class DisablingConfigOptionsE2ETest {
128166
}
129167

130168
@Test
131-
fun `Errors should be exported as spans when disableErrorTracking is set to false and disableTraces set to true`() {
132-
application.pluginOptions = getOptionsAllEnabled().copy(disableTraces = true, disableErrorTracking = false)
169+
fun `Errors should be exported as spans when TracesApi include errors but not spans`() {
170+
application.observabilityOptions = getOptionsAllEnabled().copy(
171+
tracesApi = ObservabilityOptions.TracesApi(includeErrors = true, includeSpans = false)
172+
)
133173
application.initForTest()
134174
val tracesUrl = "http://localhost:${application.mockWebServer?.port}/v1/traces"
135175

@@ -148,8 +188,10 @@ class DisablingConfigOptionsE2ETest {
148188
}
149189

150190
@Test
151-
fun `Crashes should NOT be exported when disableErrorTracking is set to true`() {
152-
application.pluginOptions = getOptionsAllEnabled().copy(disableErrorTracking = true)
191+
fun `Crashes should NOT be exported when crashReporting instrumentation is disabled`() {
192+
application.observabilityOptions = getOptionsAllEnabled().copy(
193+
instrumentations = ObservabilityOptions.Instrumentations(crashReporting = false)
194+
)
153195
application.initForTest()
154196
val logsUrl = "http://localhost:${application.mockWebServer?.port}/v1/logs"
155197

@@ -163,8 +205,11 @@ class DisablingConfigOptionsE2ETest {
163205
}
164206

165207
@Test
166-
fun `Crashes should be exported as logs when disableErrorTracking is set to false and disableLogs set to true`() {
167-
application.pluginOptions = getOptionsAllEnabled().copy(disableLogs = true, disableErrorTracking = false)
208+
fun `Crashes should be exported as logs when crashReporting instrumentation is enabled logsApiLevel is NONE`() {
209+
application.observabilityOptions = getOptionsAllEnabled().copy(
210+
logsApiLevel = ObservabilityOptions.LogLevel.NONE,
211+
instrumentations = ObservabilityOptions.Instrumentations(crashReporting = true)
212+
)
168213
application.initForTest()
169214
val logsUrl = "http://localhost:${application.mockWebServer?.port}/v1/logs"
170215
val exceptionMessage = "Exception for testing"
@@ -189,10 +234,10 @@ class DisablingConfigOptionsE2ETest {
189234
}
190235
}
191236

192-
private fun triggerTestLog() {
237+
private fun triggerTestLog(severity: Severity = Severity.INFO) {
193238
LDObserve.recordLog(
194239
message = "test-log",
195-
severity = Severity.INFO,
240+
severity = severity,
196241
attributes = Attributes.empty()
197242
)
198243
}
@@ -216,13 +261,17 @@ class DisablingConfigOptionsE2ETest {
216261
LDObserve.recordMetric(Metric("test", 50.0))
217262
}
218263

219-
private fun getOptionsAllEnabled(): Options {
220-
return Options(
264+
private fun getOptionsAllEnabled(): ObservabilityOptions {
265+
return ObservabilityOptions(
221266
debug = true,
222-
disableTraces = false,
223-
disableLogs = false,
224-
disableMetrics = false,
225-
disableErrorTracking = false
267+
logsApiLevel = ObservabilityOptions.LogLevel.TRACE,
268+
tracesApi = ObservabilityOptions.TracesApi.enabled(),
269+
metricsApi = ObservabilityOptions.MetricsApi.enabled(),
270+
instrumentations = ObservabilityOptions.Instrumentations(
271+
crashReporting = true,
272+
activityLifecycle = true,
273+
launchTime = true
274+
)
226275
)
227276
}
228277
}

e2e/android/app/src/test/java/com/example/androidobservability/SamplingE2ETest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.app.Application
44
import androidx.test.core.app.ApplicationProvider
55
import com.example.androidobservability.TestUtils.TelemetryType
66
import com.example.androidobservability.TestUtils.waitForTelemetryData
7+
import com.launchdarkly.observability.api.ObservabilityOptions
78
import com.launchdarkly.observability.client.TelemetryInspector
89
import com.launchdarkly.observability.sdk.LDObserve
910
import io.opentelemetry.api.common.AttributeKey
@@ -49,6 +50,7 @@ class SamplingE2ETest {
4950

5051
@Before
5152
fun setUp() {
53+
application.observabilityOptions = getOptionsAllEnabled()
5254
application.initForTest()
5355
telemetryInspector = application.telemetryInspector
5456
}
@@ -203,4 +205,18 @@ class SamplingE2ETest {
203205
)
204206
span7.end()
205207
}
208+
209+
private fun getOptionsAllEnabled(): ObservabilityOptions {
210+
return ObservabilityOptions(
211+
debug = true,
212+
logsApiLevel = ObservabilityOptions.LogLevel.TRACE,
213+
tracesApi = ObservabilityOptions.TracesApi.enabled(),
214+
metricsApi = ObservabilityOptions.MetricsApi.enabled(),
215+
instrumentations = ObservabilityOptions.Instrumentations(
216+
crashReporting = true,
217+
activityLifecycle = true,
218+
launchTime = true
219+
)
220+
)
221+
}
206222
}

sdk/@launchdarkly/observability-android/lib/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ dependencies {
4747
// Testing exporters for telemetry inspection
4848
implementation("io.opentelemetry:opentelemetry-sdk-testing:1.51.0")
4949

50-
// Android instrumentation
50+
// OTEL Android
5151
implementation("io.opentelemetry.android:core:0.11.0-alpha")
52-
implementation("io.opentelemetry.android.instrumentation:activity:0.11.0-alpha")
5352
implementation("io.opentelemetry.android:session:0.11.0-alpha")
5453

55-
// Android crash instrumentation
54+
// OTEL Android Instrumentations
5655
implementation("io.opentelemetry.android.instrumentation:crash:0.11.0-alpha")
56+
implementation("io.opentelemetry.android.instrumentation:activity:0.11.0-alpha")
5757

5858
// TODO: O11Y-626 - move replay instrumentation and associated compose dependencies into dedicated package
5959
// Compose dependencies for capture functionality

0 commit comments

Comments
 (0)