Skip to content

Commit c6720ef

Browse files
committed
Add launchdarkly integrations
1 parent 1d8442a commit c6720ef

File tree

10 files changed

+352
-0
lines changed

10 files changed

+352
-0
lines changed

buildSrc/src/main/java/Config.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ object Config {
7878
val SENTRY_QUARTZ_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.quartz"
7979
val SENTRY_JDBC_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jdbc"
8080
val SENTRY_OPENFEATURE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.openfeature"
81+
val SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.launchdarkly-server"
82+
val SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME = "$SENTRY_ANDROID_SDK_NAME.launchdarkly"
8183
val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet"
8284
val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta"
8385
val SENTRY_COMPOSE_HELPER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.compose.helper"

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core",
117117
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
118118
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClient" }
119119
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktorClient" }
120+
launchdarkly-android = { module = "com.launchdarkly:launchdarkly-android-client-sdk", version = "5.9.2" }
121+
launchdarkly-server = { module = "com.launchdarkly:launchdarkly-java-server-sdk", version = "7.10.2" }
120122
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j2" }
121123
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" }
122124
leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version = "2.14" }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public final class io/sentry/launchdarkly/android/BuildConfig {
2+
public static final field BUILD_TYPE Ljava/lang/String;
3+
public static final field DEBUG Z
4+
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5+
public static final field SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME Ljava/lang/String;
6+
public static final field VERSION_NAME Ljava/lang/String;
7+
public fun <init> ()V
8+
}
9+
10+
public final class io/sentry/launchdarkly/android/SentryLaunchDarklyAndroidHook : com/launchdarkly/sdk/android/integrations/Hook {
11+
public fun <init> ()V
12+
public fun <init> (Lio/sentry/IScopes;)V
13+
public fun afterEvaluation (Lcom/launchdarkly/sdk/android/integrations/EvaluationSeriesContext;Ljava/util/Map;Lcom/launchdarkly/sdk/EvaluationDetail;)Ljava/util/Map;
14+
}
15+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import io.gitlab.arturbosch.detekt.Detekt
2+
3+
plugins {
4+
id("com.android.library")
5+
alias(libs.plugins.kotlin.android)
6+
jacoco
7+
alias(libs.plugins.jacoco.android)
8+
alias(libs.plugins.gradle.versions)
9+
alias(libs.plugins.detekt)
10+
}
11+
12+
android {
13+
compileSdk = libs.versions.compileSdk.get().toInt()
14+
namespace = "io.sentry.launchdarkly.android"
15+
16+
defaultConfig {
17+
minSdk = libs.versions.minSdk.get().toInt()
18+
19+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20+
21+
// for AGP 4.1
22+
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
23+
buildConfigField(
24+
"String",
25+
"SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME",
26+
"\"${Config.Sentry.SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME}\"",
27+
)
28+
}
29+
30+
buildTypes {
31+
getByName("debug") { consumerProguardFiles("proguard-rules.pro") }
32+
getByName("release") { consumerProguardFiles("proguard-rules.pro") }
33+
}
34+
35+
kotlin {
36+
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
37+
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
38+
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
39+
}
40+
41+
testOptions {
42+
animationsDisabled = true
43+
unitTests.apply {
44+
isReturnDefaultValues = true
45+
isIncludeAndroidResources = true
46+
}
47+
}
48+
49+
lint {
50+
warningsAsErrors = true
51+
checkDependencies = true
52+
53+
// We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks.
54+
checkReleaseBuilds = false
55+
}
56+
57+
buildFeatures { buildConfig = true }
58+
59+
androidComponents.beforeVariants {
60+
it.enable = !Config.Android.shouldSkipDebugVariant(it.buildType)
61+
}
62+
}
63+
64+
kotlin { explicitApi() }
65+
66+
dependencies {
67+
api(projects.sentry)
68+
69+
// LaunchDarkly Android Client SDK
70+
// Note: This is for Android client-side applications
71+
compileOnly(libs.launchdarkly.android)
72+
73+
implementation(kotlin(Config.kotlinStdLib, Config.kotlinStdLibVersionAndroid))
74+
75+
// tests
76+
testImplementation(projects.sentry)
77+
testImplementation(projects.sentryTestSupport)
78+
testImplementation(kotlin(Config.kotlinStdLib))
79+
testImplementation(libs.kotlin.test.junit)
80+
testImplementation(libs.androidx.test.ext.junit)
81+
testImplementation(libs.mockito.kotlin)
82+
testImplementation(libs.mockito.inline)
83+
// LaunchDarkly Android Client SDK for tests
84+
testImplementation(libs.launchdarkly.android)
85+
}
86+
87+
tasks.withType<Detekt>().configureEach {
88+
// Target version of the generated JVM bytecode. It is used for type resolution.
89+
jvmTarget = JavaVersion.VERSION_1_8.toString()
90+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
##---------------Begin: proguard configuration for LaunchDarkly Android ----------
2+
3+
# To ensure that stack traces is unambiguous
4+
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
5+
-keepattributes LineNumberTable,SourceFile
6+
7+
##---------------End: proguard configuration for LaunchDarkly Android ----------
8+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.sentry.launchdarkly.android
2+
3+
import com.launchdarkly.sdk.EvaluationDetail
4+
import com.launchdarkly.sdk.LDValue
5+
import com.launchdarkly.sdk.LDValueType
6+
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext
7+
import com.launchdarkly.sdk.android.integrations.Hook
8+
import io.sentry.IScopes
9+
import io.sentry.ScopesAdapter
10+
import io.sentry.SentryIntegrationPackageStorage
11+
import io.sentry.SentryLevel
12+
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
13+
14+
public class SentryLaunchDarklyAndroidHook : Hook {
15+
private val scopes: IScopes
16+
17+
private companion object {
18+
init {
19+
SentryIntegrationPackageStorage.getInstance()
20+
.addPackage("maven:io.sentry:sentry-launchdarkly-android", BuildConfig.VERSION_NAME)
21+
}
22+
}
23+
24+
public constructor() : this(ScopesAdapter.getInstance())
25+
26+
public constructor(scopes: IScopes) : super("SentryLaunchDarklyAndroidHook") {
27+
this.scopes = scopes
28+
addPackageAndIntegrationInfo()
29+
}
30+
31+
private fun addPackageAndIntegrationInfo() {
32+
addIntegrationToSdkVersion("LaunchDarkly-Android")
33+
}
34+
35+
@Suppress("TooGenericExceptionCaught")
36+
override fun afterEvaluation(
37+
seriesContext: EvaluationSeriesContext?,
38+
seriesData: Map<String, Any>?,
39+
evaluationDetail: EvaluationDetail<LDValue>?,
40+
): Map<String, Any>? {
41+
try {
42+
val flagKey: String? = seriesContext?.flagKey
43+
val value: LDValue? = evaluationDetail?.value
44+
45+
if (flagKey != null && value != null && LDValueType.BOOLEAN == value.type) {
46+
val flagValue: Boolean = value.booleanValue()
47+
scopes.addFeatureFlag(flagKey, flagValue)
48+
}
49+
} catch (e: Throwable) {
50+
scopes.options.logger.log(SentryLevel.ERROR, "Failed to capture feature flag evaluation", e)
51+
}
52+
53+
return seriesData
54+
}
55+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public final class io/sentry/launchdarkly/server/BuildConfig {
2+
public static final field SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME Ljava/lang/String;
3+
public static final field VERSION_NAME Ljava/lang/String;
4+
}
5+
6+
public final class io/sentry/launchdarkly/server/SentryLaunchDarklyServerHook : com/launchdarkly/sdk/server/integrations/Hook {
7+
public fun <init> ()V
8+
public fun afterEvaluation (Lcom/launchdarkly/sdk/server/integrations/EvaluationSeriesContext;Ljava/util/Map;Lcom/launchdarkly/sdk/EvaluationDetail;)Ljava/util/Map;
9+
}
10+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import net.ltgt.gradle.errorprone.errorprone
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
`java-library`
6+
id("io.sentry.javadoc")
7+
alias(libs.plugins.kotlin.jvm)
8+
jacoco
9+
alias(libs.plugins.errorprone)
10+
alias(libs.plugins.gradle.versions)
11+
alias(libs.plugins.buildconfig)
12+
}
13+
14+
tasks.withType<KotlinCompile>().configureEach {
15+
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
16+
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
17+
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
18+
}
19+
20+
dependencies {
21+
api(projects.sentry)
22+
23+
// LaunchDarkly Java Server SDK (for JVM/server-side applications)
24+
// Note: For Android applications, use sentry-launchdarkly-android module with
25+
// com.launchdarkly:launchdarkly-android-client-sdk instead
26+
compileOnly(libs.launchdarkly.server)
27+
28+
compileOnly(libs.jetbrains.annotations)
29+
compileOnly(libs.nopen.annotations)
30+
errorprone(libs.errorprone.core)
31+
errorprone(libs.nopen.checker)
32+
errorprone(libs.nullaway)
33+
34+
// tests
35+
testImplementation(projects.sentry)
36+
testImplementation(projects.sentryTestSupport)
37+
testImplementation(kotlin(Config.kotlinStdLib))
38+
testImplementation(libs.kotlin.test.junit)
39+
testImplementation(libs.mockito.kotlin)
40+
testImplementation(libs.mockito.inline)
41+
// LaunchDarkly Java Server SDK for tests
42+
testImplementation(libs.launchdarkly.server)
43+
}
44+
45+
configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }
46+
47+
jacoco { toolVersion = libs.versions.jacoco.get() }
48+
49+
tasks.jacocoTestReport {
50+
reports {
51+
xml.required.set(true)
52+
html.required.set(false)
53+
}
54+
}
55+
56+
tasks {
57+
jacocoTestCoverageVerification {
58+
violationRules { rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } }
59+
}
60+
check {
61+
dependsOn(jacocoTestCoverageVerification)
62+
dependsOn(jacocoTestReport)
63+
}
64+
}
65+
66+
tasks.withType<JavaCompile>().configureEach {
67+
options.errorprone {
68+
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
69+
option("NullAway:AnnotatedPackages", "io.sentry")
70+
}
71+
}
72+
73+
buildConfig {
74+
useJavaOutput()
75+
packageName("io.sentry.launchdarkly.server")
76+
buildConfigField(
77+
"String",
78+
"SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME",
79+
"\"${Config.Sentry.SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME}\"",
80+
)
81+
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
82+
}
83+
84+
tasks.jar {
85+
manifest {
86+
attributes(
87+
"Sentry-Version-Name" to project.version,
88+
"Sentry-SDK-Name" to Config.Sentry.SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME,
89+
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-launchdarkly-server",
90+
"Implementation-Vendor" to "Sentry",
91+
"Implementation-Title" to project.name,
92+
"Implementation-Version" to project.version,
93+
)
94+
}
95+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.sentry.launchdarkly.server;
2+
3+
import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;
4+
5+
import com.launchdarkly.sdk.EvaluationDetail;
6+
import com.launchdarkly.sdk.LDValue;
7+
import com.launchdarkly.sdk.LDValueType;
8+
import com.launchdarkly.sdk.server.integrations.EvaluationSeriesContext;
9+
import com.launchdarkly.sdk.server.integrations.Hook;
10+
import io.sentry.IScopes;
11+
import io.sentry.ScopesAdapter;
12+
import io.sentry.SentryIntegrationPackageStorage;
13+
import io.sentry.SentryLevel;
14+
import java.util.Map;
15+
import java.util.Objects;
16+
import org.jetbrains.annotations.NotNull;
17+
import org.jetbrains.annotations.Nullable;
18+
import org.jetbrains.annotations.VisibleForTesting;
19+
20+
public final class SentryLaunchDarklyServerHook extends Hook {
21+
private final IScopes scopes;
22+
23+
static {
24+
SentryIntegrationPackageStorage.getInstance()
25+
.addPackage("maven:io.sentry:sentry-launchdarkly-server", BuildConfig.VERSION_NAME);
26+
}
27+
28+
public SentryLaunchDarklyServerHook() {
29+
this(ScopesAdapter.getInstance());
30+
}
31+
32+
@VisibleForTesting
33+
SentryLaunchDarklyServerHook(@NotNull IScopes scopes) {
34+
super("SentryLaunchDarklyServerHook");
35+
this.scopes = Objects.requireNonNull(scopes, "Scopes are required");
36+
addPackageAndIntegrationInfo();
37+
}
38+
39+
private void addPackageAndIntegrationInfo() {
40+
addIntegrationToSdkVersion("LaunchDarkly-Server");
41+
}
42+
43+
@Override
44+
public Map<String, Object> afterEvaluation(
45+
EvaluationSeriesContext seriesContext,
46+
Map<String, Object> seriesData,
47+
EvaluationDetail<LDValue> evaluationDetail) {
48+
if (evaluationDetail == null || seriesContext == null) {
49+
return seriesData;
50+
}
51+
52+
try {
53+
final @Nullable String flagKey = seriesContext.flagKey;
54+
final @Nullable LDValue value = evaluationDetail.getValue();
55+
56+
if (flagKey == null || value == null) {
57+
return seriesData;
58+
}
59+
60+
if (LDValueType.BOOLEAN.equals(value.getType())) {
61+
final boolean flagValue = value.booleanValue();
62+
scopes.addFeatureFlag(flagKey, flagValue);
63+
}
64+
} catch (Exception e) {
65+
scopes
66+
.getOptions()
67+
.getLogger()
68+
.log(SentryLevel.ERROR, "Failed to capture feature flag evaluation", e);
69+
}
70+
71+
return seriesData;
72+
}
73+
}

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ include(
6666
"sentry-quartz",
6767
"sentry-okhttp",
6868
"sentry-openfeature",
69+
"sentry-launchdarkly-server",
70+
"sentry-launchdarkly-android",
6971
"sentry-reactor",
7072
"sentry-async-profiler",
7173
"sentry-ktor-client",

0 commit comments

Comments
 (0)