diff --git a/.gitignore b/.gitignore index 2162583..11f5c03 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ out/ bin/ gen/ target/ -/gradle.properties /secret.gpg /secring.gpg /.claude/ diff --git a/build.gradle.kts b/build.gradle.kts index 3ae5ed6..52820aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,165 +1,103 @@ import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.SonatypeHost +import helpers.configureMavenCentralMetadata +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi -plugins { - kotlin("jvm") version "1.9.21" - kotlin("plugin.serialization") version "1.9.21" - id("org.jetbrains.dokka") version "1.9.10" - id("com.vanniktech.maven.publish") version "0.29.0" -} - -group = "io.github.logtide-dev" -version = "0.4.0" - -repositories { - mavenCentral() -} - -dependencies { - // Kotlin - implementation(kotlin("stdlib")) - - // HTTP Client - implementation("com.squareup.okhttp3:okhttp:4.12.0") - implementation("com.squareup.okhttp3:okhttp-sse:4.12.0") - - // JSON Serialization - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - - // Coroutines - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") - - // Logging - compileOnly("org.slf4j:slf4j-api:2.0.9") - - // Framework integrations - compileOnly("org.springframework.boot:spring-boot-starter-web:3.2.0") - compileOnly("io.ktor:ktor-server-core:2.3.7") - compileOnly("jakarta.servlet:jakarta.servlet-api:6.0.0") - - // Testing - testImplementation(kotlin("test")) - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") - testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") - testImplementation("io.mockk:mockk:1.13.8") +val kotlinJvmTarget: String by project +val projectGroup: String by project +val projectVersion: String by project - // Logging for tests (required since slf4j-api is compileOnly) - testImplementation("org.slf4j:slf4j-api:2.0.9") - testImplementation("org.slf4j:slf4j-simple:2.0.9") - - // Framework testing dependencies - testImplementation("io.ktor:ktor-server-test-host:2.3.7") - testImplementation("io.ktor:ktor-server-content-negotiation:2.3.7") - testImplementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7") - testImplementation("org.springframework:spring-test:6.1.1") - testImplementation("org.springframework:spring-webmvc:6.1.1") - testImplementation("org.springframework.boot:spring-boot-test:3.2.0") - testImplementation("jakarta.servlet:jakarta.servlet-api:6.0.0") +plugins { + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.maven.publish) apply false } -tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = "17" +subprojects { + if (projectGroup.isBlank() || projectVersion.isBlank()) { + throw GradleException("Project group and version must be defined in gradle.properties") } -} -tasks.test { - useJUnitPlatform() -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -mavenPublishing { - publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) - signIfKeyPresent(project) - - coordinates("io.github.logtide-dev", "logtide-sdk-kotlin", version.toString()) - - pom { - name.set("LogTide Kotlin SDK") - description.set("Official Kotlin SDK for LogTide - Self-hosted log management with batching, retry logic, circuit breaker, and query API") - url.set("https://github.com/logtide-dev/logtide-sdk-kotlin") - - licenses { - license { - name.set("MIT License") - url.set("https://opensource.org/licenses/MIT") + group = projectGroup + version = projectVersion + + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "com.vanniktech.maven.publish") + apply(plugin = "signing") + + pluginManager.withPlugin("com.vanniktech.maven.publish") { + pluginManager.withPlugin("signing") { + extensions.configure { + publishToMavenCentral( + SonatypeHost.CENTRAL_PORTAL, + automaticRelease = true + ) + signIfKeyPresent(this@subprojects) + + pom { + configureMavenCentralMetadata(this@subprojects) + } } } + } - developers { - developer { - id.set("polliog") - name.set("Polliog") - email.set("giuseppe@solture.it") - } - developer { - id.set("emanueleiannuzzi") - name.set("Emanuele Iannuzzi") - email.set("hello@emanueleiannuzzi.me") + plugins.withType().configureEach { + extensions.configure { + toolchain { + languageVersion.set(JavaLanguageVersion.of(kotlinJvmTarget)) + vendor.set(JvmVendorSpec.AMAZON) + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } } + } - scm { - connection.set("scm:git:git://github.com/logtide-dev/logtide-sdk-kotlin.git") - developerConnection.set("scm:git:ssh://github.com/logtide-dev/logtide-sdk-kotlin.git") - url.set("https://github.com/logtide-dev/logtide-sdk-kotlin") + plugins.withId("org.jetbrains.kotlin.jvm") { + extensions.configure { + jvmToolchain(kotlinJvmTarget.toInt()) } } -} -tasks.register("printVersion") { - doLast { - println(project.version.toString()) + tasks.withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = kotlinJvmTarget + } } -} - -tasks.register("checkVersionTag") { - doLast { - val tag = System.getenv("GITHUB_REF_NAME") - ?.removePrefix("v") - ?: return@doLast - - val versionString = project.version.toString() - if (versionString != tag) { - throw GradleException( - "Version mismatch: project.version=$versionString, tag=$tag" - ) - } + tasks.withType { + useJUnitPlatform() } } -tasks["publishAndReleaseToMavenCentral"].dependsOn("checkVersionTag") - @OptIn(ExperimentalEncodingApi::class) -fun MavenPublishBaseExtension.signIfKeyPresent(project: Project) { - val keyId = System.getenv("KEY_ID").also { - if (it == null) { - println("KEY_ID environment variable not set, assuming binary .gpg key.") - } - } - val keyBytes = System.getenv("SECRING")?.let { Base64.decode(it.toByteArray()).decodeToString() } +private fun MavenPublishBaseExtension.signIfKeyPresent(project: Project) { + val keyId = System.getenv("KEY_ID") + val keyBytes = runCatching { + Base64.decode(System.getenv("SECRING").toByteArray()).decodeToString() + }.getOrNull() val keyPassword = System.getenv("PASSWORD") - if (keyBytes == null || keyPassword == null) { - println("Signing environment variables not set, skipping signing.") - return + if (keyBytes != null && keyPassword != null) { + println("Signing artifacts with in-memory PGP key (.gpg)") + project.extensions.configure("signing") { + // For binary .gpg keys + if (keyId == null) { + useInMemoryPgpKeys(keyBytes, keyPassword) + } else { + useInMemoryPgpKeys(keyId, keyBytes, keyPassword) + } + signAllPublications() + } + } else { + println("Skipping signing of artifacts: PGP key or password not found in environment variables") } +} - project.extensions.configure("signing") { - // For binary .gpg keys - if (keyId == null) { - useInMemoryPgpKeys(keyBytes, keyPassword) - } else { - useInMemoryPgpKeys(keyId, keyBytes, keyPassword) - } - signAllPublications() +tasks.register("printVersion") { + doLast { + println(project.version.toString()) } } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..d328fd6 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenLocal() + mavenCentral() + maven { url = uri("https://packages.confluent.io/maven/") } +} + +dependencies { + gradleApi() +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt new file mode 100644 index 0000000..70fc32f --- /dev/null +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -0,0 +1,13 @@ +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.findByType + +@PublishedApi +internal inline val Project.libsVersionCatalog + get() = extensions.findByType()?.named("libs") + ?: error("Version catalog 'libs' not found") + +@PublishedApi +internal inline val Project.frameworksVersionCatalog + get() = extensions.findByType()?.named("frameworks") + ?: error("Version catalog 'frameworks' not found") diff --git a/buildSrc/src/main/kotlin/helpers/Publishing.kt b/buildSrc/src/main/kotlin/helpers/Publishing.kt new file mode 100644 index 0000000..d131c0a --- /dev/null +++ b/buildSrc/src/main/kotlin/helpers/Publishing.kt @@ -0,0 +1,40 @@ +package helpers + +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.gradle.api.publish.maven.MavenPom + +private infix fun Property.by(value: T) = set(value) + +@Suppress("Unused") +fun MavenPom.configureMavenCentralMetadata(project: Project) { + name by project.name + description by "Official Kotlin SDK for LogTide - Self-hosted log management with batching, retry logic, circuit breaker, and query API" + url by "https://github.com/logtide-dev/logtide-sdk-kotlin" + + licenses { + license { + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") + } + } + + developers { + developer { + id by "polliog" + name by "Polliog" + email by "giuseppe@solture.it" + } + developer { + id by "emanueleiannuzzi" + name by "Emanuele Iannuzzi" + email by "hello@emanueleiannuzzi.me" + } + } + + scm { + connection by "scm:git:git://github.com/logtide-dev/logtide-sdk-kotlin.git" + developerConnection by "scm:git:ssh://github.com/logtide-dev/logtide-sdk-kotlin.git" + url by "https://github.com/logtide-dev/logtide-sdk-kotlin" + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugins/LogtideConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/LogtideConventionPlugin.kt new file mode 100644 index 0000000..c1fb9bc --- /dev/null +++ b/buildSrc/src/main/kotlin/plugins/LogtideConventionPlugin.kt @@ -0,0 +1,23 @@ +package plugins + +import libsVersionCatalog +import org.gradle.api.Plugin +import org.gradle.api.Project + +class LogtideConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + val libs = libsVersionCatalog + pluginManager.apply(libs.findPlugin("kotlin-jvm").get().get().pluginId) + + dependencies.apply { + add("implementation", libs.findBundle("kotlin").get()) + add("compileOnly", libs.findBundle("slf4j").get()) + + add("testImplementation", libs.findBundle("test-slf4j").get()) + add("testImplementation", libs.findBundle("test-kotlin").get()) + add("testImplementation", libs.findBundle("test-okhttp").get()) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/logtide.convention.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/logtide.convention.properties new file mode 100644 index 0000000..376309b --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/logtide.convention.properties @@ -0,0 +1 @@ +implementation-class=plugins.LogtideConventionPlugin \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..d585e4a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,10 @@ +# Gradle Properties +org.gradle.parallel=false +org.gradle.jvmargs=-Dfile.encoding=UTF-8 +org.gradle.configuration.cache=true +org.gradle.caching=true +# Project Properties +projectGroup=io.github.logtide-dev +projectVersion=0.5.0 +# Kotlin +kotlinJvmTarget=17 \ No newline at end of file diff --git a/gradle/frameworks.versions.toml b/gradle/frameworks.versions.toml new file mode 100644 index 0000000..0a52080 --- /dev/null +++ b/gradle/frameworks.versions.toml @@ -0,0 +1,25 @@ +[versions] +spring-boot = "3.2.0" +ktor = "2.3.7" +jakarta = "6.0.0" + +[plugins] + +[libraries] +spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" } +ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta" } + +# Test +ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } +ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } + +spring-test = { module = "org.springframework:spring-test", version = "6.1.1" } +spring-webmvc = { module = "org.springframework:spring-webmvc", version = "6.1.1" } +spring-boot-test = { module = "org.springframework.boot:spring-boot-test", version.ref = "spring-boot" } + +[bundles] +test-ktor = ["ktor-server-test-host", "ktor-server-content-negotiation", "ktor-serialization-kotlinx-json"] +test-spring = ["spring-test", "spring-webmvc", "spring-boot-test"] +test-jakarta = ["jakarta-servlet-api"] \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..37caf8c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,64 @@ +[versions] +kotlin = "1.9.21" +okhttp = "4.12.0" +slf4j = "2.0.9" + +[plugins] +# Kotlin +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } + +maven-publish = { id = "com.vanniktech.maven.publish", version = "0.29.0" } + +[libraries] +# Kotlin +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.3" } +kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.7.3" } +kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.0" } + +# HTTP Client +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-sse = { module = "com.squareup.okhttp3:okhttp-sse", version.ref = "okhttp" } +okhttp-mockws = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } + +# Mockk +mockk = { module = "io.mockk:mockk", version = "1.13.8" } + +# SLF4J +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } + +[bundles] +kotlin = [ + "kotlin-stdlib", + "kotlin-coroutines-core", +] + +kotlin-serialization = [ + "kotlin-serialization-json", +] + +slf4j = [ + "slf4j-api", +] + +okhttp = [ + "okhttp", + "okhttp-sse", +] + +test-slf4j = [ + "slf4j-simple", +] + +test-okhttp = [ + "okhttp-mockws", + "mockk", +] + +test-kotlin = [ + "kotlin-test", + "kotlin-coroutines-test", +] \ No newline at end of file diff --git a/logtide-core/build.gradle.kts b/logtide-core/build.gradle.kts new file mode 100644 index 0000000..7957228 --- /dev/null +++ b/logtide-core/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("logtide.convention") + alias(libs.plugins.kotlin.serialization) +} + +dependencies { + implementation(libs.bundles.kotlin.serialization) + implementation(libs.bundles.okhttp) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/logtide/sdk/CircuitBreaker.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/CircuitBreaker.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/CircuitBreaker.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/CircuitBreaker.kt diff --git a/src/main/kotlin/dev/logtide/sdk/LogTideClient.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/LogTideClient.kt similarity index 99% rename from src/main/kotlin/dev/logtide/sdk/LogTideClient.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/LogTideClient.kt index f967ec0..c2af63a 100644 --- a/src/main/kotlin/dev/logtide/sdk/LogTideClient.kt +++ b/logtide-core/src/main/kotlin/dev/logtide/sdk/LogTideClient.kt @@ -36,7 +36,7 @@ import kotlin.math.pow * retry logic, circuit breaker, and query capabilities. */ class LogTideClient(private val options: LogTideClientOptions) { - private val logger = LoggerFactory.getLogger(LogTideClient::class.java) + private val logger = LoggerFactory.getLogger(this::class.java) private val httpClient: OkHttpClient = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) diff --git a/src/main/kotlin/dev/logtide/sdk/TraceIdContext.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/TraceIdContext.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/TraceIdContext.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/TraceIdContext.kt diff --git a/src/main/kotlin/dev/logtide/sdk/enums/CircuitState.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/enums/CircuitState.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/enums/CircuitState.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/enums/CircuitState.kt diff --git a/src/main/kotlin/dev/logtide/sdk/enums/LogLevel.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/enums/LogLevel.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/enums/LogLevel.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/enums/LogLevel.kt diff --git a/src/main/kotlin/dev/logtide/sdk/exceptions/BufferFullException.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/BufferFullException.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/exceptions/BufferFullException.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/BufferFullException.kt diff --git a/src/main/kotlin/dev/logtide/sdk/exceptions/CircuitBreakerOpenException.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/CircuitBreakerOpenException.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/exceptions/CircuitBreakerOpenException.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/CircuitBreakerOpenException.kt diff --git a/src/main/kotlin/dev/logtide/sdk/exceptions/LogTideException.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/LogTideException.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/exceptions/LogTideException.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/exceptions/LogTideException.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsOptions.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsOptions.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsOptions.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsOptions.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsResponse.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsResponse.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsResponse.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/AggregatedStatsResponse.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/AnyValueSerializer.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/AnyValueSerializer.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/AnyValueSerializer.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/AnyValueSerializer.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/ClientMetrics.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/ClientMetrics.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/ClientMetrics.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/ClientMetrics.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/InstantSerializer.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/InstantSerializer.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/InstantSerializer.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/InstantSerializer.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/LogEntry.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogEntry.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/LogEntry.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogEntry.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/LogTideClientOptions.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogTideClientOptions.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/LogTideClientOptions.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogTideClientOptions.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/LogsResponse.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogsResponse.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/LogsResponse.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/LogsResponse.kt diff --git a/src/main/kotlin/dev/logtide/sdk/models/QueryOptions.kt b/logtide-core/src/main/kotlin/dev/logtide/sdk/models/QueryOptions.kt similarity index 100% rename from src/main/kotlin/dev/logtide/sdk/models/QueryOptions.kt rename to logtide-core/src/main/kotlin/dev/logtide/sdk/models/QueryOptions.kt diff --git a/src/test/kotlin/dev/logtide/sdk/CircuitBreakerTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/CircuitBreakerTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/CircuitBreakerTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/CircuitBreakerTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/LogTideClientHttpTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/LogTideClientHttpTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/LogTideClientHttpTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/LogTideClientHttpTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/LogTideClientTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/LogTideClientTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/LogTideClientTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/LogTideClientTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/TraceIdContextTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/TraceIdContextTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/TraceIdContextTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/TraceIdContextTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/exceptions/ExceptionTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/exceptions/ExceptionTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/exceptions/ExceptionTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/exceptions/ExceptionTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/models/LogTideClientOptionsTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/models/LogTideClientOptionsTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/models/LogTideClientOptionsTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/models/LogTideClientOptionsTest.kt diff --git a/src/test/kotlin/dev/logtide/sdk/models/ModelsSerializationTest.kt b/logtide-core/src/test/kotlin/dev/logtide/sdk/models/ModelsSerializationTest.kt similarity index 100% rename from src/test/kotlin/dev/logtide/sdk/models/ModelsSerializationTest.kt rename to logtide-core/src/test/kotlin/dev/logtide/sdk/models/ModelsSerializationTest.kt diff --git a/logtide-jakarta/build.gradle.kts b/logtide-jakarta/build.gradle.kts new file mode 100644 index 0000000..711fc80 --- /dev/null +++ b/logtide-jakarta/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("logtide.convention") +} + +dependencies { + implementation(project(":logtide-core")) + compileOnly(frameworks.jakarta.servlet.api) + + testImplementation(frameworks.bundles.test.jakarta) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/logtide/sdk/middleware/LogTideFilter.kt b/logtide-jakarta/src/main/kotlin/LogTideFilter.kt similarity index 83% rename from src/main/kotlin/dev/logtide/sdk/middleware/LogTideFilter.kt rename to logtide-jakarta/src/main/kotlin/LogTideFilter.kt index 4ff6110..4f1080a 100644 --- a/src/main/kotlin/dev/logtide/sdk/middleware/LogTideFilter.kt +++ b/logtide-jakarta/src/main/kotlin/LogTideFilter.kt @@ -1,9 +1,12 @@ -package dev.logtide.sdk.middleware +@file:Suppress("Unused") + +package dev.logtide.sdk.jakarta import dev.logtide.sdk.LogTideClient import jakarta.servlet.* import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.slf4j.LoggerFactory /** * Jakarta Servlet Filter for automatic HTTP request/response logging @@ -32,21 +35,19 @@ class LogTideFilter( private val skipHealthCheck: Boolean = true, private val skipPaths: Set = emptySet() ) : Filter { + private val logger = LoggerFactory.getLogger(this::class.java) init { - println("╭────────────────────────────────────────────╮") - println("│ LogTide Filter Initialized │") - println("╰────────────────────────────────────────────╯") - println(" Service Name: $serviceName") - println(" Log Requests: $logRequests") - println(" Log Responses: $logResponses") - println(" Log Errors: $logErrors") - println(" Skip Health Check: $skipHealthCheck") + logger.info("LogTide Filter Initialized") + logger.info(" Service Name: $serviceName") + logger.info(" Log Requests: $logRequests") + logger.info(" Log Responses: $logResponses") + logger.info(" Log Errors: $logErrors") + logger.info(" Skip Health Check: $skipHealthCheck") if (skipPaths.isNotEmpty()) { - println(" Skip Paths: ${skipPaths.joinToString(", ")}") + logger.info(" Skip Paths: ${skipPaths.joinToString(", ")}") } - println("✓ Jakarta Servlet filter ready for HTTP logging") - println() + logger.info(" Jakarta Servlet filter ready for HTTP logging") } companion object { @@ -155,4 +156,4 @@ class LogTideFilter( } return path in skipPaths } -} +} \ No newline at end of file diff --git a/src/test/kotlin/dev/logtide/sdk/middleware/LogTideFilterTest.kt b/logtide-jakarta/src/test/kotlin/LogTideFilterTest.kt similarity index 98% rename from src/test/kotlin/dev/logtide/sdk/middleware/LogTideFilterTest.kt rename to logtide-jakarta/src/test/kotlin/LogTideFilterTest.kt index f69e78c..e007bcb 100644 --- a/src/test/kotlin/dev/logtide/sdk/middleware/LogTideFilterTest.kt +++ b/logtide-jakarta/src/test/kotlin/LogTideFilterTest.kt @@ -1,8 +1,9 @@ -package dev.logtide.sdk.middleware - import dev.logtide.sdk.LogTideClient +import dev.logtide.sdk.jakarta.LogTideFilter import dev.logtide.sdk.models.LogTideClientOptions -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import jakarta.servlet.FilterChain import jakarta.servlet.FilterConfig import jakarta.servlet.ServletRequest @@ -14,7 +15,10 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.time.Duration.Companion.seconds /** @@ -523,4 +527,4 @@ class LogTideFilterTest { assertEquals(traceId2, secondRequestTraceId) assertNull(client.getTraceId()) } -} +} \ No newline at end of file diff --git a/logtide-ktor/build.gradle.kts b/logtide-ktor/build.gradle.kts new file mode 100644 index 0000000..3f00f4d --- /dev/null +++ b/logtide-ktor/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("logtide.convention") + alias(libs.plugins.kotlin.serialization) +} + +dependencies { + implementation(project(":logtide-core")) + compileOnly(frameworks.ktor.server.core) + + testImplementation(frameworks.bundles.test.ktor) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/logtide/sdk/middleware/LogTidePlugin.kt b/logtide-ktor/src/main/kotlin/LogTidePlugin.kt similarity index 93% rename from src/main/kotlin/dev/logtide/sdk/middleware/LogTidePlugin.kt rename to logtide-ktor/src/main/kotlin/LogTidePlugin.kt index 6a60555..5d5ca65 100644 --- a/src/main/kotlin/dev/logtide/sdk/middleware/LogTidePlugin.kt +++ b/logtide-ktor/src/main/kotlin/LogTidePlugin.kt @@ -1,6 +1,7 @@ @file:OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class) +@file:Suppress("Unused") -package dev.logtide.sdk.middleware +package dev.logtide.sdk.ktor import dev.logtide.sdk.LogTideClient import dev.logtide.sdk.TraceIdElement @@ -153,9 +154,7 @@ val LogTidePlugin = createApplicationPlugin( val config = pluginConfig // Log plugin installation - application.log.info("╭────────────────────────────────────────────╮") - application.log.info("│ LogTide Plugin Initialized │") - application.log.info("╰────────────────────────────────────────────╯") + application.log.info("LogTide Plugin Initialized") application.log.info(" Service Name: ${config.serviceName}") application.log.info(" API URL: ${config.apiUrl}") application.log.info(" Batch Size: ${config.batchSize}") @@ -169,8 +168,8 @@ val LogTidePlugin = createApplicationPlugin( } val client = LogTideClient(config.toClientOptions()) - application.log.info("✓ LogTide client created and ready") - application.log.info("✓ Access client manually via: call.application.attributes[LogTideClientKey]") + application.log.info(" LogTide client created and ready") + application.log.info(" Access client manually via: call.application.attributes[LogTideClientKey]") // Store client in application attributes for manual access application.attributes.put(LogTideClientKey, client) diff --git a/src/test/kotlin/dev/logtide/sdk/middleware/LogTidePluginTest.kt b/logtide-ktor/src/test/kotlin/LogTidePluginTest.kt similarity index 87% rename from src/test/kotlin/dev/logtide/sdk/middleware/LogTidePluginTest.kt rename to logtide-ktor/src/test/kotlin/LogTidePluginTest.kt index f2c1df5..feff3e6 100644 --- a/src/test/kotlin/dev/logtide/sdk/middleware/LogTidePluginTest.kt +++ b/logtide-ktor/src/test/kotlin/LogTidePluginTest.kt @@ -1,46 +1,50 @@ -@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class, kotlinx.coroutines.DelicateCoroutinesApi::class) - -package dev.logtide.sdk.middleware - import dev.logtide.sdk.LogTideClient -import dev.logtide.sdk.TraceIdElement import dev.logtide.sdk.currentTraceId -import dev.logtide.sdk.threadLocalTraceId +import dev.logtide.sdk.ktor.LogTideClientKey +import dev.logtide.sdk.ktor.LogTidePlugin +import dev.logtide.sdk.ktor.LogTidePluginConfig import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.testing.* -import io.mockk.* -import kotlinx.coroutines.runBlocking import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds /** * Integration tests for LogTidePlugin (Ktor middleware) */ class LogTidePluginTest { - private lateinit var mockServer: MockWebServer + internal fun Application.installLogtideDefault(mockServer: MockWebServer) { + install(LogTidePlugin) { + apiUrl = mockServer.url("/").toString() + apiKey = "test_key" + serviceName = "test-service" + logRequests = false + logResponses = false + } + } + @BeforeEach fun setup() { mockServer = MockWebServer() mockServer.start() - threadLocalTraceId.set(null) } @AfterEach fun cleanup() { mockServer.shutdown() - threadLocalTraceId.set(null) } // ==================== Plugin Installation Tests ==================== @@ -48,13 +52,7 @@ class LogTidePluginTest { @Test fun `plugin should install successfully`() = testApplication { application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -72,13 +70,7 @@ class LogTidePluginTest { var clientFromAttributes: LogTideClient? = null application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -125,16 +117,8 @@ class LogTidePluginTest { @Test fun `plugin should not log request when disabled`() = testApplication { - var logsCount = 0 - application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -156,13 +140,7 @@ class LogTidePluginTest { var extractedTraceId: String? = null application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -174,7 +152,9 @@ class LogTidePluginTest { val traceId = "550e8400-e29b-41d4-a716-446655440000" client.get("/test") { - header("X-Trace-ID", traceId) + headers.apply { + append("X-Trace-ID", traceId) + } } assertEquals(traceId, extractedTraceId) @@ -185,13 +165,7 @@ class LogTidePluginTest { var generatedTraceId: String? = null application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -232,7 +206,9 @@ class LogTidePluginTest { } client.get("/test") { - header("X-Trace-ID", headerTraceId) + headers.apply { + append("X-Trace-ID", headerTraceId) + } } assertEquals(headerTraceId, contextTraceId) @@ -264,7 +240,9 @@ class LogTidePluginTest { val traceId = "custom-trace-123" client.get("/test") { - header("Custom-Trace-Header", traceId) + headers.apply { + append("Custom-Trace-Header", traceId) + } } assertEquals(traceId, extractedTraceId) @@ -274,8 +252,6 @@ class LogTidePluginTest { @Test fun `plugin should skip health check path by default`() = testApplication { - var requestLogged = false - application { install(LogTidePlugin) { apiUrl = mockServer.url("/").toString() @@ -433,7 +409,9 @@ class LogTidePluginTest { } client.get("/test") { - header("X-Trace-ID", headerTraceId) + headers.apply { + append("X-Trace-ID", headerTraceId) + } } // Without interceptor, context won't have trace ID @@ -478,13 +456,7 @@ class LogTidePluginTest { val traceId = "550e8400-e29b-41d4-a716-446655440003" application { - install(LogTidePlugin) { - apiUrl = mockServer.url("/").toString() - apiKey = "test_key" - serviceName = "test-service" - logRequests = false - logResponses = false - } + installLogtideDefault(mockServer) routing { get("/test") { @@ -494,7 +466,9 @@ class LogTidePluginTest { } client.get("/test") { - header("X-Trace-ID", traceId) + headers.apply { + append("X-Trace-ID", traceId) + } } // After the request completes, ThreadLocal should be cleared @@ -556,4 +530,4 @@ class LogTidePluginTest { assertNotNull(config.extractMetadataFromOutgoingContent) assertNotNull(config.extractTraceIdFromCall) } -} +} \ No newline at end of file diff --git a/logtide-spring/build.gradle.kts b/logtide-spring/build.gradle.kts new file mode 100644 index 0000000..5988549 --- /dev/null +++ b/logtide-spring/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("logtide.convention") +} + +dependencies { + implementation(project(":logtide-core")) + compileOnly(frameworks.spring.boot.starter.web) + + testImplementation(frameworks.bundles.test.spring) + testImplementation(frameworks.bundles.test.jakarta) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/logtide/sdk/middleware/LogTideInterceptor.kt b/logtide-spring/src/main/kotlin/LogTideInterceptor.kt similarity index 82% rename from src/main/kotlin/dev/logtide/sdk/middleware/LogTideInterceptor.kt rename to logtide-spring/src/main/kotlin/LogTideInterceptor.kt index 2c165d2..10e4d31 100644 --- a/src/main/kotlin/dev/logtide/sdk/middleware/LogTideInterceptor.kt +++ b/logtide-spring/src/main/kotlin/LogTideInterceptor.kt @@ -1,8 +1,11 @@ -package dev.logtide.sdk.middleware +@file:Suppress("Unused") + +package dev.logtide.sdk.spring import dev.logtide.sdk.LogTideClient import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.slf4j.LoggerFactory import org.springframework.web.servlet.HandlerInterceptor /** @@ -30,21 +33,19 @@ class LogTideInterceptor( private val skipHealthCheck: Boolean = true, private val skipPaths: Set = emptySet() ) : HandlerInterceptor { + private val logger = LoggerFactory.getLogger(this::class.java) init { - println("╭────────────────────────────────────────────╮") - println("│ LogTide Interceptor Initialized │") - println("╰────────────────────────────────────────────╯") - println(" Service Name: $serviceName") - println(" Log Requests: $logRequests") - println(" Log Responses: $logResponses") - println(" Log Errors: $logErrors") - println(" Skip Health Check: $skipHealthCheck") + logger.info("LogTide Interceptor Initialized") + logger.info(" Service Name: $serviceName") + logger.info(" Log Requests: $logRequests") + logger.info(" Log Responses: $logResponses") + logger.info(" Log Errors: $logErrors") + logger.info(" Skip Health Check: $skipHealthCheck") if (skipPaths.isNotEmpty()) { - println(" Skip Paths: ${skipPaths.joinToString(", ")}") + logger.info(" Skip Paths: ${skipPaths.joinToString(", ")}") } - println("✓ Spring Boot interceptor ready for HTTP logging") - println() + logger.info(" Spring Boot interceptor ready for HTTP logging") } companion object { @@ -145,4 +146,4 @@ class LogTideInterceptor( } return path in skipPaths } -} +} \ No newline at end of file diff --git a/src/test/kotlin/dev/logtide/sdk/middleware/LogTideInterceptorTest.kt b/logtide-spring/src/test/kotlin/LogTideInterceptorTest.kt similarity index 98% rename from src/test/kotlin/dev/logtide/sdk/middleware/LogTideInterceptorTest.kt rename to logtide-spring/src/test/kotlin/LogTideInterceptorTest.kt index 387469e..8935a83 100644 --- a/src/test/kotlin/dev/logtide/sdk/middleware/LogTideInterceptorTest.kt +++ b/logtide-spring/src/test/kotlin/LogTideInterceptorTest.kt @@ -1,8 +1,9 @@ -package dev.logtide.sdk.middleware - import dev.logtide.sdk.LogTideClient import dev.logtide.sdk.models.LogTideClientOptions -import io.mockk.* +import dev.logtide.sdk.spring.LogTideInterceptor +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import okhttp3.mockwebserver.MockResponse @@ -10,7 +11,10 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds /** @@ -382,4 +386,4 @@ class LogTideInterceptorTest { interceptor.afterCompletion(mockRequest, mockResponse, Any(), null) assertNull(client.getTraceId()) } -} +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3a16aa3..028e2a8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,19 @@ +rootProject.name = "logtide-kotlin-java" + +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + versionCatalogs { + create("frameworks") { + from(files("gradle/frameworks.versions.toml")) + } + } + repositories { + mavenLocal() + mavenCentral() + maven { url = uri("https://packages.confluent.io/maven/") } + } +} + pluginManagement { repositories { gradlePluginPortal() @@ -12,4 +28,7 @@ pluginManagement { } } -rootProject.name = "logtide-sdk-kotlin" \ No newline at end of file +include("logtide-core") +include("logtide-spring") +include("logtide-ktor") +include("logtide-jakarta") \ No newline at end of file