Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 2 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ object Config {
val SENTRY_QUARTZ_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.quartz"
val SENTRY_JDBC_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jdbc"
val SENTRY_OPENFEATURE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.openfeature"
val SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.launchdarkly-server"
val SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME = "$SENTRY_ANDROID_SDK_NAME.launchdarkly"
val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet"
val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta"
val SENTRY_COMPOSE_HELPER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.compose.helper"
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core",
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClient" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktorClient" }
launchdarkly-android = { module = "com.launchdarkly:launchdarkly-android-client-sdk", version = "5.9.2" }
launchdarkly-server = { module = "com.launchdarkly:launchdarkly-java-server-sdk", version = "7.10.2" }
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j2" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" }
leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version = "2.14" }
Expand Down
15 changes: 15 additions & 0 deletions sentry-launchdarkly-android/api/sentry-launchdarkly-android.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public final class io/sentry/launchdarkly/android/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public static final field SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
public fun <init> ()V
}

public final class io/sentry/launchdarkly/android/SentryLaunchDarklyAndroidHook : com/launchdarkly/sdk/android/integrations/Hook {
public fun <init> ()V
public fun <init> (Lio/sentry/IScopes;)V
public fun afterEvaluation (Lcom/launchdarkly/sdk/android/integrations/EvaluationSeriesContext;Ljava/util/Map;Lcom/launchdarkly/sdk/EvaluationDetail;)Ljava/util/Map;
}

90 changes: 90 additions & 0 deletions sentry-launchdarkly-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import io.gitlab.arturbosch.detekt.Detekt

plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
jacoco
alias(libs.plugins.jacoco.android)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.detekt)
}

android {
compileSdk = libs.versions.compileSdk.get().toInt()
namespace = "io.sentry.launchdarkly.android"

defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

// for AGP 4.1
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
buildConfigField(
"String",
"SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME",
"\"${Config.Sentry.SENTRY_LAUNCHDARKLY_ANDROID_SDK_NAME}\"",
)
}

buildTypes {
getByName("debug") { consumerProguardFiles("proguard-rules.pro") }
getByName("release") { consumerProguardFiles("proguard-rules.pro") }
}

kotlin {
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

testOptions {
animationsDisabled = true
unitTests.apply {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
}

lint {
warningsAsErrors = true
checkDependencies = true

// We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks.
checkReleaseBuilds = false
}

buildFeatures { buildConfig = true }

androidComponents.beforeVariants {
it.enable = !Config.Android.shouldSkipDebugVariant(it.buildType)
}
}

kotlin { explicitApi() }

dependencies {
api(projects.sentry)

// LaunchDarkly Android Client SDK
// Note: This is for Android client-side applications
compileOnly(libs.launchdarkly.android)

implementation(kotlin(Config.kotlinStdLib, Config.kotlinStdLibVersionAndroid))

// tests
testImplementation(projects.sentry)
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
// LaunchDarkly Android Client SDK for tests
testImplementation(libs.launchdarkly.android)
}

tasks.withType<Detekt>().configureEach {
// Target version of the generated JVM bytecode. It is used for type resolution.
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
8 changes: 8 additions & 0 deletions sentry-launchdarkly-android/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
##---------------Begin: proguard configuration for LaunchDarkly Android ----------

# To ensure that stack traces is unambiguous
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
-keepattributes LineNumberTable,SourceFile

##---------------End: proguard configuration for LaunchDarkly Android ----------

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.sentry.launchdarkly.android

import com.launchdarkly.sdk.EvaluationDetail
import com.launchdarkly.sdk.LDValue
import com.launchdarkly.sdk.LDValueType
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext
import com.launchdarkly.sdk.android.integrations.Hook
import io.sentry.IScopes
import io.sentry.ScopesAdapter
import io.sentry.SentryIntegrationPackageStorage
import io.sentry.SentryLevel
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion

public class SentryLaunchDarklyAndroidHook : Hook {
private val scopes: IScopes

private companion object {
init {
SentryIntegrationPackageStorage.getInstance()
.addPackage("maven:io.sentry:sentry-launchdarkly-android", BuildConfig.VERSION_NAME)
}
}

public constructor() : this(ScopesAdapter.getInstance())

public constructor(scopes: IScopes) : super("SentryLaunchDarklyAndroidHook") {
this.scopes = scopes
addPackageAndIntegrationInfo()
}

private fun addPackageAndIntegrationInfo() {
addIntegrationToSdkVersion("LaunchDarkly-Android")
}

@Suppress("TooGenericExceptionCaught")
override fun afterEvaluation(
seriesContext: EvaluationSeriesContext?,
seriesData: Map<String, Any>?,
evaluationDetail: EvaluationDetail<LDValue>?,
): Map<String, Any>? {
try {
val flagKey: String? = seriesContext?.flagKey
val value: LDValue? = evaluationDetail?.value

if (flagKey != null && value != null && LDValueType.BOOLEAN == value.type) {
val flagValue: Boolean = value.booleanValue()
scopes.addFeatureFlag(flagKey, flagValue)
}
} catch (e: Throwable) {
scopes.options.logger.log(SentryLevel.ERROR, "Failed to capture feature flag evaluation", e)
}

return seriesData
}
}
10 changes: 10 additions & 0 deletions sentry-launchdarkly-server/api/sentry-launchdarkly-server.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public final class io/sentry/launchdarkly/server/BuildConfig {
public static final field SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
}

public final class io/sentry/launchdarkly/server/SentryLaunchDarklyServerHook : com/launchdarkly/sdk/server/integrations/Hook {
public fun <init> ()V
public fun afterEvaluation (Lcom/launchdarkly/sdk/server/integrations/EvaluationSeriesContext;Ljava/util/Map;Lcom/launchdarkly/sdk/EvaluationDetail;)Ljava/util/Map;
}

95 changes: 95 additions & 0 deletions sentry-launchdarkly-server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
jacoco
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.buildconfig)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

dependencies {
api(projects.sentry)

// LaunchDarkly Java Server SDK (for JVM/server-side applications)
// Note: For Android applications, use sentry-launchdarkly-android module with
// com.launchdarkly:launchdarkly-android-client-sdk instead
compileOnly(libs.launchdarkly.server)

compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nopen.annotations)
errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
errorprone(libs.nullaway)

// tests
testImplementation(projects.sentry)
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
// LaunchDarkly Java Server SDK for tests
testImplementation(libs.launchdarkly.server)
}

configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }

jacoco { toolVersion = libs.versions.jacoco.get() }

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(false)
}
}

tasks {
jacocoTestCoverageVerification {
violationRules { rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } }
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
}
}

tasks.withType<JavaCompile>().configureEach {
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.launchdarkly.server")
buildConfigField(
"String",
"SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME",
"\"${Config.Sentry.SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME}\"",
)
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

tasks.jar {
manifest {
attributes(
"Sentry-Version-Name" to project.version,
"Sentry-SDK-Name" to Config.Sentry.SENTRY_LAUNCHDARKLY_SERVER_SDK_NAME,
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-launchdarkly-server",
"Implementation-Vendor" to "Sentry",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.sentry.launchdarkly.server;

import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;

import com.launchdarkly.sdk.EvaluationDetail;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.LDValueType;
import com.launchdarkly.sdk.server.integrations.EvaluationSeriesContext;
import com.launchdarkly.sdk.server.integrations.Hook;
import io.sentry.IScopes;
import io.sentry.ScopesAdapter;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryLevel;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

public final class SentryLaunchDarklyServerHook extends Hook {
private final IScopes scopes;

static {
SentryIntegrationPackageStorage.getInstance()
.addPackage("maven:io.sentry:sentry-launchdarkly-server", BuildConfig.VERSION_NAME);
}

public SentryLaunchDarklyServerHook() {
this(ScopesAdapter.getInstance());
}

@VisibleForTesting
SentryLaunchDarklyServerHook(@NotNull IScopes scopes) {
super("SentryLaunchDarklyServerHook");
this.scopes = Objects.requireNonNull(scopes, "Scopes are required");
addPackageAndIntegrationInfo();
}

private void addPackageAndIntegrationInfo() {
addIntegrationToSdkVersion("LaunchDarkly-Server");
}

@Override
public Map<String, Object> afterEvaluation(
EvaluationSeriesContext seriesContext,
Map<String, Object> seriesData,
EvaluationDetail<LDValue> evaluationDetail) {
if (evaluationDetail == null || seriesContext == null) {
return seriesData;
}

try {
final @Nullable String flagKey = seriesContext.flagKey;
final @Nullable LDValue value = evaluationDetail.getValue();

if (flagKey == null || value == null) {
return seriesData;
}

if (LDValueType.BOOLEAN.equals(value.getType())) {
final boolean flagValue = value.booleanValue();
scopes.addFeatureFlag(flagKey, flagValue);
}
} catch (Exception e) {
scopes
.getOptions()
.getLogger()
.log(SentryLevel.ERROR, "Failed to capture feature flag evaluation", e);
}

return seriesData;
}
}
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ include(
"sentry-quartz",
"sentry-okhttp",
"sentry-openfeature",
"sentry-launchdarkly-server",
"sentry-launchdarkly-android",
"sentry-reactor",
"sentry-async-profiler",
"sentry-ktor-client",
Expand Down
Loading