diff --git a/.github/workflows/publish_snapshots.yml b/.github/workflows/publish_snapshots.yml
index 81137529..a0d08297 100644
--- a/.github/workflows/publish_snapshots.yml
+++ b/.github/workflows/publish_snapshots.yml
@@ -36,7 +36,7 @@ jobs:
env:
OSSRH_USER: ${{ secrets.OSSRH_USER }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- run: ./gradlew -Psigning.gnupg.keyName=CBEF2CF0 -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} publish
+ run: ./gradlew -Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_NAME }} -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} publish
- name: Plugin has SNAPSHOT version
run: ./gradlew :radar-commons-gradle:properties | grep '^version:.*-SNAPSHOT$'
@@ -45,4 +45,4 @@ jobs:
env:
OSSRH_USER: ${{ secrets.OSSRH_USER }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- run: ./gradlew -Psigning.gnupg.keyName=CBEF2CF0 -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} :radar-commons-gradle:publish
+ run: ./gradlew -Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_NAME }} -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} :radar-commons-gradle:publish
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
index aa29b78a..f864aefa 100644
--- a/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -1,6 +1,6 @@
@Suppress("ConstPropertyName", "MemberVisibilityCanBePrivate")
object Versions {
- const val project = "1.1.2"
+ const val project = "1.1.3"
object Plugins {
const val licenseReport = "2.5"
@@ -17,7 +17,7 @@ object Versions {
const val slf4j = "2.0.13"
const val confluent = "7.6.0"
const val kafka = "${confluent}-ce"
- const val avro = "1.11.3"
+ const val avro = "1.12.0"
const val jackson = "2.15.3"
const val okhttp = "4.12.0"
const val junit = "5.10.0"
diff --git a/radar-commons-gradle/README.md b/radar-commons-gradle/README.md
index a860f24b..c0f674c3 100644
--- a/radar-commons-gradle/README.md
+++ b/radar-commons-gradle/README.md
@@ -2,6 +2,17 @@
A Gradle plugin to do some common RADAR-base tasks.
+
+* [radar-commons-gradle](#radar-commons-gradle)
+ * [Usage](#usage)
+ * [radar-commons-gradle plugin](#radar-commons-gradle-plugin)
+ * [radarKotlin extension](#radarkotlin-extension)
+ * [Enable logging with log4j2](#enable-logging-with-log4j2)
+ * [Enable monitoring with Sentry](#enable-monitoring-with-sentry)
+ * [Enable Sentry source context](#enable-sentry-source-context)
+ * [Customizing Sentry configuration](#customizing-sentry-configuration)
+
+
## Usage
Add the following block to `settings.gradle.kts` to get access to the RADAR-base plugins.
@@ -51,6 +62,13 @@ subprojects {
kotlinVersion.set(Versions.Plugins.kotlin) // already has a default value
junitVersion.set(Versions.junit) // already has a default value
ktlintVersion.set(Versions.ktlint) // already has a default value
+ slf4jVersion.set(Versions.slf4j) // already has a default value
+ // log4j2Version.set(Versions.log4j2) // setting this will enable log4j2
+ // sentryVersion.set(Versions.sentry) // setting this will enable Sentry monitoring
+ // sentryEnabled.set(false) // setting this to true will enable Sentry monitoring
+ // sentrySourceContextToken.set("") // setting this will upload the source code context to Sentry
+ // sentryOrganization.set("radar-base") // already has a default value, only needed when setting 'sentrySourceContextToken'
+ // sentryProject.set("") // already has a default value, only needed when setting 'sentrySourceContextToken'
}
// Both values are required to be set to use radar-publishing.
@@ -68,3 +86,103 @@ subprojects {
}
}
```
+
+## radar-commons-gradle plugin
+
+This plugin provides the basics for RADAR-base projects.
+
+### radarKotlin extension
+
+#### Enable logging with log4j2
+
+By default, no logging implementation is added to projects (only the slf4j interface is included). To enable log4j2, add
+the following to your root project configurations:
+
+```gradle
+...
+subprojects {
+ ...
+ radarKotlin {
+ log4j2Version.set(Versions.log4j2)
+ }
+ ...
+}
+```
+
+#### Enable monitoring with Sentry
+
+1. Activate log4j2 logging (see [above](#enable-logging-with-log4j2))
+2. Activate Sentry in the _radarKotlin_ extension:
+
+```gradle
+...
+subprojects {
+ ...
+ radarKotlin {
+ sentryEnabled.set(true)
+ }
+ ...
+}
+```
+
+3. Add a Sentry log Appender to the `log4j2.xml` configuration file to `src/main/resources`. For example:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+4. Set the `SENTRY_DSN` environment variable at runtime to the DSN of your Sentry project.
+
+#### Enable Sentry source context
+
+Sentry can be configured to show the source code context of the error. For this to work, source code can be uploaded
+to the target sentry organization and project during the build phase. To enable this, set the following values:
+
+```gradle
+...
+subprojects {
+ ...
+ radarKotlin {
+ sentrySourceContextToken.set("my-token")
+ sentryOrganization.set("my-organization")
+ sentryProject.set("my-project")
+ }
+ ...
+}
+```
+
+NOTE: The organization and project must correspond to the values for the monitoring environment in Sentry. The
+organization and project are encooded in Sentry OAuth token.
+
+#### Customizing Sentry configuration
+
+In a project that uses radar-commons-gradle, the Sentry configuration can be customized by using the _sentry_ extension
+like for instance:
+
+build.gradle.kts
+
+```gradle
+sentry {
+ debug = true
+ ...
+}
+...
+
+Additional information on config options: https://docs.sentry.io/platforms/java/guides/log4j2/gradle/
diff --git a/radar-commons-gradle/build.gradle.kts b/radar-commons-gradle/build.gradle.kts
index d0a488a6..b919b348 100644
--- a/radar-commons-gradle/build.gradle.kts
+++ b/radar-commons-gradle/build.gradle.kts
@@ -29,7 +29,10 @@ dependencies {
implementation("com.github.ben-manes:gradle-versions-plugin:${Versions.gradleVersionsPlugin}")
implementation("io.github.gradle-nexus:publish-plugin:${Versions.Plugins.publishPlugin}")
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlint}")
- implementation("com.github.jk1.dependency-license-report:com.github.jk1.dependency-license-report.gradle.plugin:${Versions.Plugins.licenseReport}")
+ implementation(
+ "com.github.jk1.dependency-license-report:com.github.jk1.dependency-license-report.gradle.plugin:${Versions.Plugins.licenseReport}",
+ )
+ implementation("io.sentry.jvm.gradle:io.sentry.jvm.gradle.gradle.plugin:${Versions.sentry}")
}
gradlePlugin {
@@ -69,7 +72,7 @@ tasks.withType {
manifest {
attributes(
"Implementation-Title" to project.name,
- "Implementation-Version" to project.version
+ "Implementation-Version" to project.version,
)
}
}
@@ -135,13 +138,15 @@ publishing {
}
}
-fun Project.propertyOrEnv(propertyName: String, envName: String): String? {
- return if (hasProperty(propertyName)) {
+fun Project.propertyOrEnv(
+ propertyName: String,
+ envName: String,
+): String? =
+ if (hasProperty(propertyName)) {
property(propertyName)?.toString()
} else {
System.getenv(envName)
}
-}
nexusPublishing {
this.repositories {
@@ -176,7 +181,7 @@ tasks.withType {
// They should be copied from the Versions.kt file directly to maintain consistency.
@Suppress("ConstPropertyName", "MemberVisibilityCanBePrivate")
object Versions {
- const val project = "1.1.2"
+ const val project = "1.1.3"
object Plugins {
const val licenseReport = "2.5"
@@ -192,8 +197,8 @@ object Versions {
const val java = 17
const val slf4j = "2.0.13"
const val confluent = "7.6.0"
- const val kafka = "${confluent}-ce"
- const val avro = "1.11.3"
+ const val kafka = "$confluent-ce"
+ const val avro = "1.12.0"
const val jackson = "2.15.3"
const val okhttp = "4.12.0"
const val junit = "5.10.0"
@@ -209,4 +214,5 @@ object Versions {
const val guava = "32.1.1-jre"
const val gradleVersionsPlugin = "0.50.0"
const val ktlint = "12.0.3"
+ const val sentry = "4.10.0"
}
diff --git a/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/RadarKotlinPlugin.kt b/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/RadarKotlinPlugin.kt
index b3c4f059..e6fc4b5d 100644
--- a/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/RadarKotlinPlugin.kt
+++ b/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/RadarKotlinPlugin.kt
@@ -1,6 +1,8 @@
package org.radarbase.gradle.plugin
import com.github.jk1.license.LicenseReportPlugin
+import io.sentry.android.gradle.extensions.SentryPluginExtension
+import io.sentry.jvm.gradle.SentryJvmPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ApplicationPlugin
@@ -42,6 +44,10 @@ interface RadarKotlinExtension {
val log4j2Version: Property
val slf4jVersion: Property
val ktlintVersion: Property
+ val sentryEnabled: Property
+ val sentryOrganization: Property
+ val sentryProject: Property
+ val sentrySourceContextToken: Property
}
class RadarKotlinPlugin : Plugin {
@@ -52,12 +58,19 @@ class RadarKotlinPlugin : Plugin {
kotlinApiVersion.convention("")
junitVersion.convention(Versions.junit)
ktlintVersion.convention(Versions.ktlint)
- slf4jVersion.convention(Versions.ktlint)
+ slf4jVersion.convention(Versions.slf4j)
+ sentryEnabled.convention(false)
+ sentryOrganization.convention("radar-base")
+ sentryProject.convention(project.name)
+ sentrySourceContextToken.convention("")
}
apply(plugin = "kotlin")
apply()
+ // SentryJvmPlugin will be removed in afterEvaluate when sentryEnabled == false.
+ apply()
+
repositories {
mavenCentral {
mavenContent {
@@ -162,6 +175,20 @@ class RadarKotlinPlugin : Plugin {
implementation("org.slf4j:slf4j-api:${extension.slf4jVersion.get()}")
}
}
+ if (extension.sentryEnabled.get()) {
+ val sentry = extensions.get("sentry") as SentryPluginExtension
+ sentry.org.set(extension.sentryOrganization)
+ sentry.projectName.set(extension.sentryProject)
+ if (extension.sentrySourceContextToken.isPresent &&
+ extension.sentrySourceContextToken.get().isNotEmpty()
+ ) {
+ // Passing the source context token will activate upload of our source code to Sentry.
+ sentry.includeSourceContext.set(true)
+ sentry.authToken.set(extension.sentrySourceContextToken)
+ }
+ } else {
+ plugins.removeIf({ it is SentryJvmPlugin })
+ }
if (extension.log4j2Version.isPresent) {
dependencies {
val log4j2Version = extension.log4j2Version.get()
@@ -171,6 +198,10 @@ class RadarKotlinPlugin : Plugin {
runtimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:$log4j2Version")
runtimeOnly("org.apache.logging.log4j:log4j-core:$log4j2Version")
runtimeOnly("org.apache.logging.log4j:log4j-jul:$log4j2Version")
+ if (extension.sentryEnabled.get()) {
+ val annotationProcessor by configurations
+ annotationProcessor("org.apache.logging.log4j:log4j-core:$log4j2Version")
+ }
} else {
val testRuntimeOnly by configurations
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:$log4j2Version")
diff --git a/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/Versions.kt b/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/Versions.kt
index fb7e4bb2..40ae0207 100644
--- a/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/Versions.kt
+++ b/radar-commons-gradle/src/main/kotlin/org/radarbase/gradle/plugin/Versions.kt
@@ -6,4 +6,5 @@ object Versions {
const val ktlint = "0.50.0"
const val java = 11
const val junit = "5.10.0"
+ const val slf4j = "2.0.16"
}
diff --git a/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/ClientCredentialsConfig.kt b/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/ClientCredentialsConfig.kt
index b1ba8a72..17797d92 100644
--- a/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/ClientCredentialsConfig.kt
+++ b/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/ClientCredentialsConfig.kt
@@ -6,6 +6,8 @@ data class ClientCredentialsConfig(
val tokenUrl: String,
val clientId: String? = null,
val clientSecret: String? = null,
+ val scope: String? = null,
+ val audience: String? = null,
) {
/**
* Fill in the client ID and client secret from environment variables. The variables are
diff --git a/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/OAuthClientProvider.kt b/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/OAuthClientProvider.kt
index 9a2fd00b..18a8e8ad 100644
--- a/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/OAuthClientProvider.kt
+++ b/radar-commons-kotlin/src/main/kotlin/org/radarbase/ktor/auth/OAuthClientProvider.kt
@@ -56,6 +56,8 @@ fun Auth.clientCredentials(
append("grant_type", "client_credentials")
append("client_id", authConfig.clientId)
append("client_secret", authConfig.clientSecret)
+ authConfig.scope?.let { append("scope", it) }
+ authConfig.audience?.let { append("audience", it) }
},
) {
accept(ContentType.Application.Json)
diff --git a/radar-commons/src/test/java/org/radarbase/data/AvroDatumEncoderTest.java b/radar-commons/src/test/java/org/radarbase/data/AvroDatumEncoderTest.java
index 410ea225..8a110cc0 100644
--- a/radar-commons/src/test/java/org/radarbase/data/AvroDatumEncoderTest.java
+++ b/radar-commons/src/test/java/org/radarbase/data/AvroDatumEncoderTest.java
@@ -16,10 +16,6 @@
package org.radarbase.data;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.UUID;
-import java.util.concurrent.ThreadLocalRandom;
import junit.framework.TestCase;
import org.apache.avro.specific.SpecificData;
import org.radarbase.topic.AvroTopic;
@@ -29,9 +25,22 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertArrayEquals;
+
public class AvroDatumEncoderTest extends TestCase {
private static final Logger logger = LoggerFactory.getLogger(AvroDatumEncoderTest.class);
+ public static String byteArrayToHex(byte[] a) {
+ StringBuilder sb = new StringBuilder(a.length * 2);
+ for (byte b : a)
+ sb.append(String.format("%02x", b & 0xff));
+ return sb.toString();
+ }
+
public void testJson() throws IOException {
AvroDatumEncoder encoder = new AvroDatumEncoder(SpecificData.get(), false);
AvroTopic topic = new AvroTopic<>("keeeeys", ObservationKey.getClassSchema(), EmpaticaE4BloodVolumePulse.getClassSchema(), ObservationKey.class, EmpaticaE4BloodVolumePulse.class);
@@ -58,20 +67,13 @@ public void testBinary() throws IOException {
byte[] expectedKey = {2, 8, 116, 101, 115, 116, 2, 97, 2, 98};
System.out.println("key: 0x" + byteArrayToHex(key));
System.out.println("expected: 0x" + byteArrayToHex(expectedKey));
- assertTrue(Arrays.equals(expectedKey, key));
+ assertArrayEquals(expectedKey, key);
byte[] value = valueEncoder.encode(new EmpaticaE4BloodVolumePulse(0d, 0d, 0f));
// 8 bytes, 8 bytes, 4 bytes, all zero
byte[] expectedValue = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
System.out.println("value: 0x" + byteArrayToHex(value));
System.out.println("expected: 0x" + byteArrayToHex(expectedValue));
- assertTrue(Arrays.equals(expectedValue, value));
- }
-
- public static String byteArrayToHex(byte[] a) {
- StringBuilder sb = new StringBuilder(a.length * 2);
- for(byte b: a)
- sb.append(String.format("%02x", b & 0xff));
- return sb.toString();
+ assertArrayEquals(expectedValue, value);
}
public void testSize() throws IOException {