Skip to content

Commit eb07178

Browse files
authored
refactor: O11Y-742 - Replaced ConditionalSpanExporter with SpansSampler (#289)
## Summary This change simplifies the trace exporting pipeline by moving the filtering responsibility from the exporter (ConditionalSpanExporter) to the sampler (SpansSampler), which is the more appropriate place for this logic. ## How did you test this change? Unit tests ## Are there any deployment considerations? No <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Moves span filtering to an OpenTelemetry sampler (`SpansSampler`), removes `ConditionalSpanExporter`, wires sampler into tracer provider, and updates sampling utilities and tests. > > - **Tracing/Sampling**: > - **Sampler Integration**: Set tracer provider sampler to new `SpansSampler` in `InstrumentationManager`, controlling normal vs error spans via options. > - **Exporter Pipeline**: Remove `ConditionalSpanExporter` usage; `createSpanExporter` now directly wraps base exporter with `SamplingTraceExporter`. > - **API Adjustments**: Update `spanProcessor` type to `SpanProcessor`. > - **Sampling Utilities**: > - Update `sampleSpans` to use `SpanData.spanId`/`parentSpanId` and adjust cloning to merge attributes. > - **Tests**: > - Add `SpansSamplerTest` covering sampling decisions and description. > - Remove `ConditionalSpanExporterTest`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f634859. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent c984f4b commit eb07178

File tree

6 files changed

+127
-303
lines changed

6 files changed

+127
-303
lines changed

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/client/ConditionalSpanExporter.kt

Lines changed: 0 additions & 43 deletions
This file was deleted.

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/client/InstrumentationManager.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.launchdarkly.observability.sampling.ExportSampler
1111
import com.launchdarkly.observability.sampling.SamplingConfig
1212
import com.launchdarkly.observability.sampling.SamplingLogProcessor
1313
import com.launchdarkly.observability.sampling.SamplingTraceExporter
14+
import com.launchdarkly.observability.sampling.SpansSampler
1415
import com.launchdarkly.observability.coroutines.DispatcherProviderHolder
1516
import io.opentelemetry.android.OpenTelemetryRum
1617
import io.opentelemetry.android.config.OtelRumConfig
@@ -41,6 +42,7 @@ import io.opentelemetry.sdk.metrics.export.MetricExporter
4142
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader
4243
import io.opentelemetry.sdk.resources.Resource
4344
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder
45+
import io.opentelemetry.sdk.trace.SpanProcessor
4446
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor
4547
import io.opentelemetry.sdk.trace.export.SpanExporter
4648
import kotlinx.coroutines.CoroutineScope
@@ -73,7 +75,7 @@ class InstrumentationManager(
7375
private val graphqlClient = GraphQLClient(options.backendUrl)
7476
private val samplingApiService = SamplingApiService(graphqlClient)
7577
private var telemetryInspector: TelemetryInspector? = null
76-
private var spanProcessor: BatchSpanProcessor? = null
78+
private var spanProcessor: SpanProcessor? = null
7779
private var logProcessor: LogRecordProcessor? = null
7880
private var metricsReader: PeriodicMetricReader? = null
7981
private var launchTimeInstrumentation: LaunchTimeInstrumentation? = null
@@ -169,7 +171,14 @@ class InstrumentationManager(
169171

170172
private fun configureTracerProvider(sdkTracerProviderBuilder: SdkTracerProviderBuilder): SdkTracerProviderBuilder {
171173
val primarySpanExporter = createOtlpSpanExporter()
172-
sdkTracerProviderBuilder.setResource(resources)
174+
sdkTracerProviderBuilder
175+
.setResource(resources)
176+
.setSampler(
177+
SpansSampler(
178+
allowNormalSpans = !options.disableTraces,
179+
allowErrorSpans = !options.disableErrorTracking
180+
)
181+
)
173182

174183
val finalExporter = createSpanExporter(primarySpanExporter)
175184
val processor = createBatchSpanProcessor(finalExporter)
@@ -218,13 +227,7 @@ class InstrumentationManager(
218227
primaryExporter
219228
}
220229

221-
val conditionalExporter = ConditionalSpanExporter(
222-
delegate = baseExporter,
223-
allowNormalSpans = !options.disableTraces,
224-
allowErrorSpans = !options.disableErrorTracking
225-
)
226-
227-
return SamplingTraceExporter(conditionalExporter, customSampler)
230+
return SamplingTraceExporter(baseExporter, customSampler)
228231
}
229232

230233
private fun createMetricExporter(primaryExporter: MetricExporter): MetricExporter {

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/sampling/SampleSpans.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ fun sampleSpans(
2020
// The first pass we sample items which are directly impacted by a sampling decision.
2121
// We also build a map of children spans by parent span id, which allows us to quickly traverse the span tree.
2222
for (item in items) {
23-
item.parentSpanContext?.spanId?.let { parentSpanId ->
23+
item.parentSpanId?.let { parentSpanId ->
2424
childrenByParentId.getOrPut(parentSpanId) { mutableListOf() }
25-
.add(item.spanContext.spanId)
25+
.add(item.spanId)
2626
}
2727
val sampleResult = sampler.sampleSpan(item)
2828
if (sampleResult.sample) {
2929
if (sampleResult.attributes != null) {
30-
spanById[item.spanContext.spanId] = cloneSpanDataWithAttributes(item, sampleResult.attributes)
30+
spanById[item.spanId] = cloneSpanDataWithAttributes(item, sampleResult.attributes)
3131
} else {
32-
spanById[item.spanContext.spanId] = item
32+
spanById[item.spanId] = item
3333
}
3434
} else {
35-
omittedSpansIds.addLast(item.spanContext.spanId)
35+
omittedSpansIds.addLast(item.spanId)
3636
}
3737
}
3838

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.launchdarkly.observability.sampling
2+
3+
import com.launchdarkly.observability.client.InstrumentationManager
4+
import io.opentelemetry.api.common.Attributes
5+
import io.opentelemetry.api.trace.SpanKind
6+
import io.opentelemetry.context.Context
7+
import io.opentelemetry.sdk.trace.data.LinkData
8+
import io.opentelemetry.sdk.trace.samplers.Sampler
9+
import io.opentelemetry.sdk.trace.samplers.SamplingResult
10+
11+
/**
12+
* OpenTelemetry sampler that differentiates between normal spans and error spans.
13+
*
14+
* The constructor flags determine whether each span category is recorded or not.
15+
*/
16+
class SpansSampler(
17+
private val allowNormalSpans: Boolean,
18+
private val allowErrorSpans: Boolean
19+
) : Sampler {
20+
21+
override fun shouldSample(
22+
parentContext: Context,
23+
traceId: String,
24+
spanName: String,
25+
spanKind: SpanKind,
26+
attributes: Attributes,
27+
parentLinks: List<LinkData>
28+
): SamplingResult {
29+
return if (shouldRecordSpan(spanName)) {
30+
SamplingResult.recordAndSample()
31+
} else {
32+
SamplingResult.drop()
33+
}
34+
}
35+
36+
override fun getDescription(): String = "LaunchDarklySpansSampler"
37+
38+
private fun shouldRecordSpan(spanName: String): Boolean {
39+
val isErrorSpan = spanName == InstrumentationManager.ERROR_SPAN_NAME
40+
41+
return when {
42+
isErrorSpan -> allowErrorSpans
43+
else -> allowNormalSpans
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)