Skip to content

Commit ea885e6

Browse files
committed
feat(android): Add conditional exporters for logs and traces
This change introduces conditional exporters for both logs and spans on the Android SDK. These new exporters, `ConditionalLogRecordExporter` and `ConditionalSpanExporter`, allow for separate filtering of normal application telemetry versus error and crash reports. Key changes: - Added `ConditionalLogRecordExporter.kt` to filter logs, distinguishing between normal logs and crash reports from OpenTelemetry's CrashReporter. - Added `ConditionalSpanExporter.kt` to filter spans, distinguishing between normal traces and error spans created by `recordError`. - Updated `InstrumentationManager.kt` to use these conditional exporters, enabling independent control over sending normal logs/traces and error/crash data based on the configuration options (`disableLogs`, `disableTraces`, `disableErrorTracking`).
1 parent 1496df7 commit ea885e6

File tree

3 files changed

+116
-9
lines changed

3 files changed

+116
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.launchdarkly.observability.client
2+
3+
import io.opentelemetry.sdk.common.CompletableResultCode
4+
import io.opentelemetry.sdk.logs.data.LogRecordData
5+
import io.opentelemetry.sdk.logs.export.LogRecordExporter
6+
7+
/**
8+
* A log record exporter that conditionally forwards logs based on their source.
9+
* This allows different filtering rules for crashes vs normal logs.
10+
*
11+
* @property delegate The underlying exporter to forward logs to
12+
* @property allowNormalLogs Whether to allow normal application logs
13+
* @property allowCrashes Whether to allow crash logs from OpenTelemetry's CrashReporter
14+
*/
15+
class ConditionalLogRecordExporter(
16+
private val delegate: LogRecordExporter,
17+
private val allowNormalLogs: Boolean,
18+
private val allowCrashes: Boolean
19+
) : LogRecordExporter {
20+
21+
override fun export(logs: Collection<LogRecordData>): CompletableResultCode {
22+
val filteredLogs = logs.filter { logRecord ->
23+
// Check if this is a crash log (from OpenTelemetry's CrashReporter)
24+
val instrumentationScopeName = logRecord.instrumentationScopeInfo.name
25+
val isCrashLog = instrumentationScopeName == "io.opentelemetry.crash"
26+
27+
when {
28+
isCrashLog -> allowCrashes
29+
else -> allowNormalLogs
30+
}
31+
}
32+
33+
return if (filteredLogs.isNotEmpty()) {
34+
delegate.export(filteredLogs)
35+
} else {
36+
CompletableResultCode.ofSuccess()
37+
}
38+
}
39+
40+
override fun flush(): CompletableResultCode = delegate.flush()
41+
42+
override fun shutdown(): CompletableResultCode = delegate.shutdown()
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.launchdarkly.observability.client
2+
3+
import io.opentelemetry.sdk.common.CompletableResultCode
4+
import io.opentelemetry.sdk.trace.data.SpanData
5+
import io.opentelemetry.sdk.trace.export.SpanExporter
6+
7+
/**
8+
* A span exporter that conditionally forwards spans based on their source.
9+
* This allows different filtering rules for error spans vs normal spans.
10+
*
11+
* @property delegate The underlying exporter to forward spans to
12+
* @property allowNormalSpans Whether to allow normal application spans
13+
* @property allowErrorSpans Whether to allow error spans created by recordError method
14+
*/
15+
class ConditionalSpanExporter(
16+
private val delegate: SpanExporter,
17+
private val allowNormalSpans: Boolean,
18+
private val allowErrorSpans: Boolean
19+
) : SpanExporter {
20+
21+
override fun export(spans: Collection<SpanData>): CompletableResultCode {
22+
val filteredSpans = spans.filter { spanData ->
23+
// Check if this is an error span created by recordError method
24+
val spanName = spanData.name
25+
val isErrorSpan = spanName == InstrumentationManager.ERROR_SPAN_NAME
26+
27+
when {
28+
isErrorSpan -> allowErrorSpans
29+
else -> allowNormalSpans
30+
}
31+
}
32+
33+
return if (filteredSpans.isNotEmpty()) {
34+
delegate.export(filteredSpans)
35+
} else {
36+
CompletableResultCode.ofSuccess()
37+
}
38+
}
39+
40+
override fun flush(): CompletableResultCode = delegate.flush()
41+
42+
override fun shutdown(): CompletableResultCode = delegate.shutdown()
43+
}

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ class InstrumentationManager(
6666
private const val LOGS_PATH = "/v1/logs"
6767
private const val TRACES_PATH = "/v1/traces"
6868
private const val INSTRUMENTATION_SCOPE_NAME = "com.launchdarkly.observability"
69-
70-
// Batch processor configuration constants
69+
const val ERROR_SPAN_NAME = "highlight.error"
7170
private const val BATCH_MAX_QUEUE_SIZE = 100
7271
private const val BATCH_SCHEDULE_DELAY_MS = 1000L
7372
private const val BATCH_EXPORTER_TIMEOUT_MS = 5000L
@@ -100,14 +99,14 @@ class InstrumentationManager(
10099

101100
otelRUM = OpenTelemetryRum.builder(application, otelRumConfig)
102101
.addLoggerProviderCustomizer { sdkLoggerProviderBuilder, _ ->
103-
return@addLoggerProviderCustomizer if (options.disableLogs) {
102+
return@addLoggerProviderCustomizer if (options.disableLogs && options.disableErrorTracking) {
104103
sdkLoggerProviderBuilder
105104
} else {
106105
configureLoggerProvider(sdkLoggerProviderBuilder)
107106
}
108107
}
109108
.addTracerProviderCustomizer { sdkTracerProviderBuilder, _ ->
110-
return@addTracerProviderCustomizer if (options.disableTraces) {
109+
return@addTracerProviderCustomizer if (options.disableTraces && options.disableErrorTracking) {
111110
sdkTracerProviderBuilder
112111
} else {
113112
configureTracerProvider(sdkTracerProviderBuilder)
@@ -199,26 +198,48 @@ class InstrumentationManager(
199198
}
200199

201200
private fun createLogExporter(primaryExporter: LogRecordExporter): LogRecordExporter {
201+
val conditionalExporter = ConditionalLogRecordExporter(
202+
delegate = primaryExporter,
203+
allowNormalLogs = !options.disableLogs,
204+
allowCrashes = !options.disableErrorTracking
205+
)
206+
202207
return if (options.debug) {
203-
val exporters = mutableListOf(primaryExporter, DebugLogExporter(logger))
208+
val debugExporter = ConditionalLogRecordExporter(
209+
delegate = DebugLogExporter(logger),
210+
allowNormalLogs = !options.disableLogs,
211+
allowCrashes = !options.disableErrorTracking
212+
)
213+
val exporters = mutableListOf<LogRecordExporter>(conditionalExporter, debugExporter)
204214
inMemoryLogExporter = InMemoryLogRecordExporter.create().also { exporters.add(it) }
205215

206216
val compositeExporter = CompositeLogExporter(exporters)
207217
SamplingLogExporter(compositeExporter, customSampler)
208218
} else {
209-
SamplingLogExporter(primaryExporter, customSampler)
219+
SamplingLogExporter(conditionalExporter, customSampler)
210220
}
211221
}
212222

213223
private fun createSpanExporter(primaryExporter: SpanExporter): SpanExporter {
224+
val conditionalExporter = ConditionalSpanExporter(
225+
delegate = primaryExporter,
226+
allowNormalSpans = !options.disableTraces,
227+
allowErrorSpans = !options.disableErrorTracking
228+
)
229+
214230
return if (options.debug) {
215-
val exporters = mutableListOf(primaryExporter, DebugSpanExporter(logger))
231+
val debugExporter = ConditionalSpanExporter(
232+
delegate = DebugSpanExporter(logger),
233+
allowNormalSpans = !options.disableTraces,
234+
allowErrorSpans = !options.disableErrorTracking
235+
)
236+
val exporters = mutableListOf<SpanExporter>(conditionalExporter, debugExporter)
216237
inMemorySpanExporter = InMemorySpanExporter.create().also { exporters.add(it) }
217238

218239
val compositeExporter = CompositeSpanExporter(exporters)
219240
SamplingTraceExporter(compositeExporter, customSampler)
220241
} else {
221-
SamplingTraceExporter(primaryExporter, customSampler)
242+
SamplingTraceExporter(conditionalExporter, customSampler)
222243
}
223244
}
224245

@@ -313,7 +334,7 @@ class InstrumentationManager(
313334
fun recordError(error: Error, attributes: Attributes) {
314335
if(!options.disableErrorTracking){
315336
val span = otelTracer
316-
.spanBuilder("highlight.error")
337+
.spanBuilder(ERROR_SPAN_NAME)
317338
.setParent(Context.current().with(Span.current()))
318339
.startSpan()
319340

0 commit comments

Comments
 (0)