smithy-lang / smithy-kotlin

Smithy code generator for Kotlin (in development)
Apache License 2.0
77 stars 26 forks source link

fix(rt): slf4j 1.x compatability #963

Closed aajtodd closed 11 months ago

aajtodd commented 11 months ago

Issue \

fixes https://github.com/awslabs/aws-sdk-kotlin/issues/993

Description of changes

This PR provides automatic fallback to SLF4J 1.x compatible logging implementation when 2.x API can't be loaded. 2.x is binary compatible with 1.x but it does contain some new APIs that aren't available in 1.x (namely the fluent builder APIs). We could just map everything to a 1.x compatible API but it's not guaranteed to be the best mapping since underlying logging implementations may be more efficient than our compatibility layer can achieve.


Alternative

I was able to solve this differently by creating a separate 1.x implementation and then using Gradle component metadata rules to replace the underlying 2.x implementation we ship by default. I decided that fallback by default is a better customer experience though.

// file: build.gradle.kts of some consuming project

class LoggingCapability : ComponentMetadataRule {
    private val loggingModules = setOf("logging-slf4j2", "logging-slf4j")
    override fun execute(context: ComponentMetadataContext) = context.details.run {
        if (id.group == "aws.smithy.kotlin" && loggingModules.contains(id.name)){
            allVariants {
                withCapabilities {
                    addCapability("aws.smithy.kotlin", "slf4j-logger", id.version)
                }
            }
        }
    }
}

dependencies {
    // activate the "LoggingCapability" rule
    components.all(LoggingCapability::class.java)

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
    implementation("aws.sdk.kotlin:s3:$sdkVersion")
    implementation("aws.sdk.kotlin:dynamodb:$sdkVersion")

   // force 1.x on our classpath, this is just to reproduce the issue, in practice customers have been experiencing this
   // due to 1.x being what ends up on the classpath _somehow_ (e.g. shipped with gradle)
    implementation("org.slf4j:slf4j-api:1.7.36") {
        version {
            strictly("1.7.36")
        }
    }

    // hypothetical 1.x based implementation with same package name/types expected by default provider 
    implementation("aws.smithy.kotlin:logging-slf4j:$smithyKotlinVersion")

    // slf4j 1.x log4j
    implementation("org.apache.logging.log4j:log4j-slf4j-impl:2.20.0")
    implementation("org.apache.logging.log4j:log4j-core:2.20.0")
}

configurations.all {
    resolutionStrategy.capabilitiesResolution.withCapability("aws.smithy.kotlin:slf4j-logger") {
        val toBeSelected = candidates.firstOrNull { it.id.let { id -> id is ModuleComponentIdentifier && id.module == "logging-slf4j" } }
        toBeSelected?.let { select(it) }
        because("need slf4j 1.x bindings")
    }
}

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

NatanLifshitz commented 11 months ago

Awesome, this issue was blocking us from updating past 0.28.1.

sonarcloud[bot] commented 11 months ago

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 2 Code Smells

No Coverage information No Coverage information
9.8% 9.8% Duplication

idea Catch issues before they fail your Quality Gate with our IDE extension sonarlint SonarLint