Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions .github/workflows/dependency-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Generate SBOM
uses: launchdarkly/gh-actions/actions/dependency-scan/generate-sbom@main
uses: launchdarkly/gh-actions/actions/dependency-scan/generate-sbom@8e082b23efd8be075c180606c8dc80b81e0c3544 # main
with:
types: 'nodejs'

- name: Evaluate SBOM Policy
uses: launchdarkly/gh-actions/actions/dependency-scan/evaluate-policy@main
uses: launchdarkly/gh-actions/actions/dependency-scan/evaluate-policy@8e082b23efd8be075c180606c8dc80b81e0c3544 # main
with:
bom-file: bom.nodejs.json
# The action's default, `latest`, resolves through an unauthenticated
# GitHub API call; when rate-limited it downloads an HTML 404 page as
# the `opa` binary and caches it. The policy needs OPA 1.x syntax.
opa-version: v1.9.0
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ class DisablingConfigOptionsE2ETest {
application.tearDownTest()
}

@Test
fun `Nothing should be exported when observability is disabled`() {
application.observabilityOptions = getOptionsAllEnabled().copy(enabled = false)
application.initForTest()
val otlpPrefix = "http://localhost:${application.mockWebServer?.port}/v1/"

triggerTestLog(severity = Severity.WARN)
triggerTestSpan()
triggerError()
triggerTestMetric()
LDObserve.flush()
waitForTelemetryData(telemetryInspector = application.telemetryInspector, telemetryType = TelemetryType.SPANS)

val inspector = application.telemetryInspector
assertTrue(inspector?.logExporter?.finishedLogRecordItems.orEmpty().isEmpty())
assertTrue(inspector?.spanExporter?.finishedSpanItems.orEmpty().isEmpty())
assertTrue(inspector?.metricExporter?.finishedMetricItems.orEmpty().isEmpty())
assertFalse(requestsStartWith(otlpPrefix))
}

@Test
fun `Logs should NOT be exported when logsApiLevel is NONE`() {
application.observabilityOptions = getOptionsAllEnabled().copy(logsApiLevel = ObservabilityOptions.LogLevel.NONE)
Expand Down Expand Up @@ -248,6 +268,13 @@ class DisablingConfigOptionsE2ETest {
}
}

private fun requestsStartWith(prefix: String): Boolean {
while (true) {
val request = application.mockWebServer?.takeRequest(100, TimeUnit.MILLISECONDS) ?: return false
if (request.requestUrl.toString().startsWith(prefix)) return true
}
}

private fun triggerTestLog(severity: Severity = Severity.INFO) {
LDObserve.recordLog(
message = TEST_LOG_MESSAGE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const val DEFAULT_BACKEND_URL = "https://pub.observability.app.launchdarkly.com"
/**
* Configuration options for the Observability plugin.
*
* @property enabled Whether the plugin exports telemetry. When `false` the plugin is installed and
* Session Replay keeps working, but no logs, traces, or metrics are exported and crash reporting
* and launch-time instrumentation are off. Defaults to `true`.
* @property serviceName The service name for the application. Defaults to [DEFAULT_SERVICE_NAME].
* @property serviceVersion The version of the service. Defaults to the SDK version.
* @property otlpEndpoint The OTLP exporter endpoint. Defaults to LaunchDarkly endpoint [DEFAULT_OTLP_ENDPOINT].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,14 @@ class ObservabilityService(
init {
requireMainThread { "ObservabilityService must be initialized on the main thread" }

registerOtlpExporters()
// With `enabled = false` the service is still built, so Session Replay keeps its session,
// screen, click and lifecycle signals, but nothing is exported: no exporters, span/log
// processors or metric reader are registered, the batch worker never starts, and the crash,
// launch-time and sampling-config network work is skipped. Mirrors iOS, which leaves the
// service unstarted when `isEnabled` is false.
if (observabilityOptions.enabled) {
registerOtlpExporters()
}
val otelRumConfig = createOtelRumConfig()

val rumBuilder = OpenTelemetryRum.builder(application, otelRumConfig)
Expand All @@ -251,7 +258,7 @@ class ObservabilityService(
// id and keep a single source of session identity across spans, logs, metrics, and replay.
LDRumSessionManagerAccessor.setSessionManager(rumBuilder, ldSessionManager)

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

Expand All @@ -278,7 +285,9 @@ class ObservabilityService(

override fun onSessionEnded(session: Session) {}
})
loadSamplingConfigAsync()
if (observabilityOptions.enabled) {
loadSamplingConfigAsync()
}

otelMeter = otelRUM.openTelemetry.meterProvider.get(INSTRUMENTATION_SCOPE_NAME)
otelLogger = otelRUM.openTelemetry.logsBridge.get(INSTRUMENTATION_SCOPE_NAME)
Expand Down Expand Up @@ -346,7 +355,9 @@ class ObservabilityService(
// always available; the `app_launch` span is gated by analytics.appLaunch inside the handler.
appLaunchTracker.start()

batchWorker.start()
if (observabilityOptions.enabled) {
batchWorker.start()
}
}

/**
Expand Down Expand Up @@ -450,7 +461,7 @@ class ObservabilityService(
// [LDRumSessionManagerAccessor]), so no SessionConfig is applied here.
val config = OtelRumConfig()

if (!observabilityOptions.instrumentations.crashReporting) {
if (!observabilityOptions.enabled || !observabilityOptions.instrumentations.crashReporting) {
// Disables [io.opentelemetry.android.instrumentation.crash.CrashReporterInstrumentation.java]
config.suppressInstrumentation("crash")
}
Expand Down Expand Up @@ -482,6 +493,7 @@ class ObservabilityService(

private fun configureLoggerProvider(sdkLoggerProviderBuilder: SdkLoggerProviderBuilder): SdkLoggerProviderBuilder {
sdkLoggerProviderBuilder.setResource(resources)
if (!observabilityOptions.enabled) return sdkLoggerProviderBuilder

val delegates = buildList<LogRecordProcessor> {
add(EventLogRecordProcessor(eventQueue = eventQueue, batchWorker = batchWorker))
Expand All @@ -504,6 +516,7 @@ class ObservabilityService(

private fun configureTracerProvider(sdkTracerProviderBuilder: SdkTracerProviderBuilder): SdkTracerProviderBuilder {
sdkTracerProviderBuilder.setResource(resources)
if (!observabilityOptions.enabled) return sdkTracerProviderBuilder

val debugExporters = buildList<io.opentelemetry.sdk.trace.export.SpanExporter> {
if (observabilityOptions.debug) {
Expand Down Expand Up @@ -531,6 +544,7 @@ class ObservabilityService(
}

private fun configureMeterProvider(sdkMeterProviderBuilder: SdkMeterProviderBuilder): SdkMeterProviderBuilder {
if (!observabilityOptions.enabled) return sdkMeterProviderBuilder.setResource(resources)
val eventExporter = EventMetricExporter(
eventQueue = eventQueue,
temporalitySelector = AggregationTemporalitySelector.deltaPreferred(),
Expand Down
Loading