Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
dcbe7dd
rearrange files
abelonogov-ld Feb 26, 2026
ae11179
Empty session replay plugin
abelonogov-ld Feb 26, 2026
a357362
renaming
abelonogov-ld Feb 26, 2026
fd062ea
empty hooks
abelonogov-ld Feb 26, 2026
605fe0f
EvalHook
abelonogov-ld Feb 26, 2026
c73c0bc
add comment
abelonogov-ld Feb 26, 2026
b5587e7
Android hooks started working
abelonogov-ld Feb 27, 2026
957795b
match to iOS
abelonogov-ld Feb 27, 2026
2e15d53
Merge branch 'main' into andrey/hooks
abelonogov-ld Feb 28, 2026
52e3a3c
remove dependencies
abelonogov-ld Feb 28, 2026
4af8770
0.27.0
abelonogov-ld Mar 1, 2026
858041f
Merge branch 'main' into andrey/hooks
abelonogov-ld Mar 1, 2026
0c55a45
separate start
abelonogov-ld Mar 2, 2026
c4280e2
make SDK35 compilable
abelonogov-ld Mar 2, 2026
8bca813
forgotten dependency
abelonogov-ld Mar 2, 2026
fbf5c1a
Merge branch 'main' into andrey/hooks
abelonogov-ld Mar 2, 2026
08ffef7
can launch
abelonogov-ld Mar 3, 2026
4d48233
working
abelonogov-ld Mar 3, 2026
057dc85
fat working
abelonogov-ld Mar 3, 2026
2fd116b
comment identify stuff
abelonogov-ld Mar 3, 2026
68f9dc7
hook proxy
abelonogov-ld Mar 3, 2026
8f20444
Merge branch 'andrey/hooks' into andrey/android-hooks
abelonogov-ld Mar 3, 2026
5fa9867
demo plugin
abelonogov-ld Mar 3, 2026
1bb1d87
last suggestion
abelonogov-ld Mar 3, 2026
f664bd9
matching versions
abelonogov-ld Mar 3, 2026
0682e76
small fixes
abelonogov-ld Mar 3, 2026
3b9b3e0
3.11
abelonogov-ld Mar 3, 2026
3b7d577
3.13
abelonogov-ld Mar 4, 2026
7e07e89
update
abelonogov-ld Mar 4, 2026
b9ef80d
add xcuserdata to .gitignore for mobile-dotnet SDK
abelonogov-ld Mar 4, 2026
70d6f86
feedback
abelonogov-ld Mar 4, 2026
bf256bb
Merge branch 'main' into andrey/android-hooks
abelonogov-ld Mar 4, 2026
6135e20
format json
abelonogov-ld Mar 4, 2026
7d45595
add tasks.json to .prettierignore
abelonogov-ld Mar 4, 2026
7801dec
launching from local dotnet core
abelonogov-ld Mar 9, 2026
dcff8eb
attirbutes working
abelonogov-ld Mar 11, 2026
76cf0f6
support init attributes
abelonogov-ld Mar 11, 2026
1a08b6c
reverse Android change
abelonogov-ld Mar 11, 2026
1042182
Merge branch 'main' into andrey/init-attributes
abelonogov-ld Mar 11, 2026
e9a3d7e
forgotten files
abelonogov-ld Mar 11, 2026
f17ae86
Remove build pattern for options
abelonogov-ld Mar 11, 2026
194cfba
move file
abelonogov-ld Mar 11, 2026
4372403
Merge branch 'main' into andrey/recordlog
abelonogov-ld Mar 11, 2026
9f888c8
Refactor to be close to iOS and MAUI integration hook
abelonogov-ld Mar 19, 2026
abac43e
remove printing in system
abelonogov-ld Mar 19, 2026
27d2002
Android MAUI update
abelonogov-ld Mar 19, 2026
b79b772
Merge branch 'main' into andrey/recordlog-android
abelonogov-ld Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
Text(
text = "Masking",
style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
SessionReplayHeader()
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))

MaskingButtons()
Expand All @@ -131,8 +127,6 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
)
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))

SessionReplayToggle()

IdentifyButtons(viewModel = viewModel)

InstrumentationButtons(viewModel = viewModel)
Expand All @@ -146,19 +140,19 @@ private fun MainScreen(viewModel: ViewModel, innerPadding: PaddingValues) {
}

@Composable
private fun SessionReplayToggle() {
private fun SessionReplayHeader() {
var isSessionReplayEnabled by rememberSaveable { mutableStateOf(true) }

Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Session Replay",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)
style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold)
)
Switch(
checked = isSessionReplayEnabled,
Expand Down Expand Up @@ -295,7 +289,7 @@ private fun MaskingButtons() {
)

MaskingRow(
name = "Check",
name = "Dialogs",
ctx = context,
activity1 = XMLMaskingActivity::class.java,
activity2 = ComposeMaskingActivity::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class BenchmarkExecutor {
}

val exportDiffManager = ExportDiffManager(compression = method, scale = 1f)
val eventGenerator = RRWebEventGenerator(canvasDrawEntourage = 300)
val eventGenerator = RRWebEventGenerator(canvasDrawEntourage = 300, title = "benchmark")
val json = Json
var bytes = 0
var isFirst = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ open class BaseApplication : Application() {
.anonymous(true)
.build()

LDClient.init(this@BaseApplication, ldConfig, context)
LDClient.init(this@BaseApplication, ldConfig, context, 1)
val anonContext = LDContext.builder(ContextKind.DEFAULT, "anonymous-userkey")
.anonymous(true)
.build()

//LDClient.get().identify(anonContext)

telemetryInspector = observabilityPlugin.getTelemetryInspector()

if (testUrl == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.app.Application
import com.launchdarkly.logging.LDLogger
import com.launchdarkly.observability.api.ObservabilityOptions
import com.launchdarkly.observability.coroutines.DispatcherProviderHolder
import com.launchdarkly.observability.interfaces.LDExtendedInstrumentation
import com.launchdarkly.observability.interfaces.Metric
import com.launchdarkly.observability.network.GraphQLClient
import com.launchdarkly.observability.network.SamplingApiService
Expand All @@ -16,7 +15,10 @@ import com.launchdarkly.observability.sampling.SamplingTraceExporter
import io.opentelemetry.android.OpenTelemetryRum
import io.opentelemetry.android.OpenTelemetryRumBuilder
import io.opentelemetry.android.config.OtelRumConfig
import io.opentelemetry.android.instrumentation.AndroidInstrumentation
import io.opentelemetry.android.instrumentation.InstallationContext
import io.opentelemetry.android.session.SessionConfig
import io.opentelemetry.android.session.SessionManager
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.logs.Logger
import io.opentelemetry.api.logs.Severity
Expand Down Expand Up @@ -64,17 +66,17 @@ import java.util.concurrent.TimeUnit
* @param resources The OpenTelemetry resource describing this service.
* @param logger The logger for internal logging.
* @param observabilityOptions Additional configuration options for the SDK.
* @param instrumentations A list of custom instrumentations to be added.
*/
class InstrumentationManager(
private val application: Application,
private val sdkKey: String,
private val resources: Resource,
private val logger: LDLogger,
private val observabilityOptions: ObservabilityOptions,
private val instrumentations: List<LDExtendedInstrumentation>,
) {
private val otelRUM: OpenTelemetryRum
lateinit var sessionManager: SessionManager
private set
private var otelMeter: Meter
private var otelLogger: Logger
private var otelTracer: Tracer
Expand All @@ -100,6 +102,8 @@ class InstrumentationManager(
initializeTelemetryInspector()
val otelRumConfig = createOtelRumConfig()

var capturedSessionManager: SessionManager? = null

val rumBuilder = OpenTelemetryRum.builder(application, otelRumConfig)
.addLoggerProviderCustomizer { sdkLoggerProviderBuilder, _ ->
val processor = createLoggerProcessor(
Expand All @@ -110,7 +114,6 @@ class InstrumentationManager(
logger,
telemetryInspector,
observabilityOptions,
instrumentations
)
logProcessor = processor
return@addLoggerProviderCustomizer sdkLoggerProviderBuilder.addLogRecordProcessor(processor)
Expand All @@ -122,15 +125,19 @@ class InstrumentationManager(
return@addMeterProviderCustomizer configureMeterProvider(sdkMeterProviderBuilder)
}

for (instrumentation in instrumentations) {
rumBuilder.addInstrumentation(instrumentation)
}
rumBuilder.addInstrumentation(object : AndroidInstrumentation {
override val name = "ld-session-manager-bridge"
override fun install(ctx: InstallationContext) {
capturedSessionManager = ctx.sessionManager
}
})

if (observabilityOptions.instrumentations.launchTime) {
addLaunchTimeInstrumentation(rumBuilder)
}

otelRUM = rumBuilder.build()
sessionManager = capturedSessionManager!!
loadSamplingConfigAsync()

otelMeter = otelRUM.openTelemetry.meterProvider.get(INSTRUMENTATION_SCOPE_NAME)
Expand Down Expand Up @@ -422,7 +429,6 @@ class InstrumentationManager(
logger: LDLogger,
telemetryInspector: TelemetryInspector?,
observabilityOptions: ObservabilityOptions,
instrumentations: List<LDExtendedInstrumentation>,
): LogRecordProcessor {
val primaryLogExporter = createOtlpLogExporter(observabilityOptions)
sdkLoggerProviderBuilder.setResource(resource)
Expand All @@ -434,28 +440,10 @@ class InstrumentationManager(
observabilityOptions = observabilityOptions
)

val samplingProcessor = SamplingLogProcessor(
return SamplingLogProcessor(
delegate = createBatchLogRecordProcessor(finalExporter),
sampler = exportSampler
)

/*
Here we set up a routing log processor that will route logs with a matching scope name to the
respective instrumentation's log record processor. If the log's scope name does not match
an instrumentation's scope name, it will fall through to the base processor. This was
originally added to route replay instrumentation logs through a separate log processing
pipeline to provide instrumentation specific caching and export.
*/
val routingLogRecordProcessor = RoutingLogRecordProcessor(fallthroughProcessor = samplingProcessor)
instrumentations.forEach { instrumentation ->
instrumentation.getLogRecordProcessor(credential = sdkKey)?.let { processor ->
instrumentation.getLoggerScopeName().let { scopeName ->
routingLogRecordProcessor.addProcessor(scopeName, processor)
}
}
}

return routingLogRecordProcessor
}

private fun createOtlpLogExporter(observabilityOptions: ObservabilityOptions): LogRecordExporter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.launchdarkly.observability.client
import android.app.Application
import com.launchdarkly.logging.LDLogger
import com.launchdarkly.observability.api.ObservabilityOptions
import com.launchdarkly.observability.interfaces.LDExtendedInstrumentation
import com.launchdarkly.observability.interfaces.Metric
import com.launchdarkly.observability.interfaces.Observe
import io.opentelemetry.api.common.Attributes
Expand All @@ -30,21 +29,21 @@ class ObservabilityClient : Observe {
* @param resource The resource.
* @param logger The logger.
* @param options Additional options for the client.
* @param instrumentations A list of extended instrumentation providers.
*/
constructor(
application: Application,
sdkKey: String,
resource: Resource,
logger: LDLogger,
options: ObservabilityOptions,
instrumentations: List<LDExtendedInstrumentation>
) {
this.instrumentationManager = InstrumentationManager(
application, sdkKey, resource, logger, options, instrumentations
application, sdkKey, resource, logger, options,
)
}

val sessionManager get() = instrumentationManager.sessionManager

internal constructor(
instrumentationManager: InstrumentationManager
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.launchdarkly.observability.client
import android.app.Application
import com.launchdarkly.logging.LDLogger
import com.launchdarkly.observability.api.ObservabilityOptions
import io.opentelemetry.android.session.SessionManager

/**
* Shared information between plugins.
Expand All @@ -12,4 +13,5 @@ data class ObservabilityContext(
val options: ObservabilityOptions,
val application: Application,
val logger: LDLogger,
var sessionManager: SessionManager? = null,
)

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,12 @@ class Observability(
}
}

val instrumentations = InstrumentationContributorManager.get(lDClient).flatMap { it.provideInstrumentations() }
observabilityClient = ObservabilityClient(
application, sdkKey, resourceBuilder.build(), logger, options, instrumentations
val client = ObservabilityClient(
application, sdkKey, resourceBuilder.build(), logger, options,
)
observabilityClient?.let {
LDObserve.init(it)
}
observabilityClient = client
LDObserve.context?.sessionManager = client.sessionManager
LDObserve.init(client)
} else {
logger.warn("Observability could not be initialized for sdkKey: $sdkKey")
}
Expand Down
Loading
Loading