Skip to content

Commit e2cfcc0

Browse files
authored
Merge pull request #174 from znzryb/fix-ktor-engine-classloader-clash
Fix analytics ServiceConfigurationError on IDEs that bundle their own ktor (2025.2+/2026.1)
2 parents 74d9554 + 810159f commit e2cfcc0

1 file changed

Lines changed: 40 additions & 27 deletions

File tree

src/main/kotlin/com/github/pushpavel/autocp/common/analytics/GoogleAnalytics.kt

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.openapi.application.ApplicationInfo
99
import com.intellij.openapi.components.Service
1010
import com.intellij.openapi.util.SystemInfo
1111
import io.ktor.client.*
12+
import io.ktor.client.engine.java.*
1213
import io.ktor.client.plugins.observer.*
1314
import io.ktor.client.request.*
1415
import io.ktor.client.statement.*
@@ -21,40 +22,52 @@ class GoogleAnalytics {
2122

2223
private val scope = ioScope()
2324

24-
private val httpClient = HttpClient {
25-
expectSuccess = false
26-
ResponseObserver { response ->
27-
println("HTTP status: ${response.status.value}\n${response.bodyAsText()}")
25+
// The engine must be named explicitly: engine auto-discovery goes through
26+
// java.util.ServiceLoader, and on IDEs whose platform bundles its own ktor
27+
// (2025.2+ ships ktor-client-cio in lib/) the lookup finds the platform's
28+
// CIOEngineContainer, which implements the platform copy of the interface,
29+
// not ours — ServiceConfigurationError: "... not a subtype".
30+
private val httpClient by lazy {
31+
HttpClient(Java) {
32+
expectSuccess = false
33+
ResponseObserver { response ->
34+
println("HTTP status: ${response.status.value}\n${response.bodyAsText()}")
35+
}
2836
}
2937
}
3038

3139
fun sendEvent(event: Event) {
3240
scope.launch {
33-
httpClient.post("${R.keys.analyticsEndPoint}?measurement_id=${R.keys.analyticsMeasurementId}&api_secret=${R.keys.analyticsApiSecret}") {
34-
contentType(ContentType.Application.Json)
35-
setBody(buildJsonObject {
36-
put("client_id", PropertiesComponent.getInstance().analyticsClientId)
37-
putJsonObject("user_properties") {
38-
putJsonObject("os") {
39-
put("value", SystemInfo.getOsNameAndVersion())
40-
}
41-
putJsonObject("productCode") {
42-
put("value", ApplicationInfo.getInstance().build.productCode)
43-
}
44-
putJsonObject("productVersion") {
45-
put("value", ApplicationInfo.getInstance().build.asStringWithoutProductCodeAndSnapshot())
46-
}
47-
putJsonObject("version") {
48-
// avoid PluginId.getId: it compiles to a PluginId.Companion reference that older IDEs (< 2025.2) don't have
49-
put("value", PluginManagerCore.plugins.find { it.pluginId.idString == R.keys.pluginId }?.version)
50-
}
41+
// analytics must never surface an IDE error, whatever goes wrong
42+
runCatching { postEvent(event) }
43+
}
44+
}
45+
46+
private suspend fun postEvent(event: Event) {
47+
httpClient.post("${R.keys.analyticsEndPoint}?measurement_id=${R.keys.analyticsMeasurementId}&api_secret=${R.keys.analyticsApiSecret}") {
48+
contentType(ContentType.Application.Json)
49+
setBody(buildJsonObject {
50+
put("client_id", PropertiesComponent.getInstance().analyticsClientId)
51+
putJsonObject("user_properties") {
52+
putJsonObject("os") {
53+
put("value", SystemInfo.getOsNameAndVersion())
5154
}
52-
put("non_personalized_ads", true)
53-
putJsonArray("events") {
54-
addJsonObject { event.buildJsonObj(this) }
55+
putJsonObject("productCode") {
56+
put("value", ApplicationInfo.getInstance().build.productCode)
5557
}
56-
}.toString().also { println(it) }.encodeToByteArray())
57-
}
58+
putJsonObject("productVersion") {
59+
put("value", ApplicationInfo.getInstance().build.asStringWithoutProductCodeAndSnapshot())
60+
}
61+
putJsonObject("version") {
62+
// avoid PluginId.getId: it compiles to a PluginId.Companion reference that older IDEs (< 2025.2) don't have
63+
put("value", PluginManagerCore.plugins.find { it.pluginId.idString == R.keys.pluginId }?.version)
64+
}
65+
}
66+
put("non_personalized_ads", true)
67+
putJsonArray("events") {
68+
addJsonObject { event.buildJsonObj(this) }
69+
}
70+
}.toString().also { println(it) }.encodeToByteArray())
5871
}
5972
}
6073

0 commit comments

Comments
 (0)