Skip to content

Instantly share code, notes, and snippets.

@kozaxinan
Created September 5, 2025 11:36
Show Gist options
  • Save kozaxinan/99317b78c1e9dba27e39290386cba658 to your computer and use it in GitHub Desktop.
Save kozaxinan/99317b78c1e9dba27e39290386cba658 to your computer and use it in GitHub Desktop.
ConnectionDebugListener for OkHttp to log everything in event listener
import okhttp3.Call
import okhttp3.Connection
import okhttp3.ConnectionPool
import okhttp3.EventListener
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.connection.RealConnectionPool
import timber.log.Timber
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
open class ConnectionDebugListener(
private val connectionPool: ConnectionPool,
) : EventListener() {
private val connectionTracker = mutableMapOf<String, Long>()
private fun getConnectionKey(connection: Connection): String {
return "${connection.socket().remoteSocketAddress}@${connection.socket().hashCode()}"
}
private fun getPoolInfo(connection: Connection): String {
val route = connection.route()
return "pool[${route.address.url.host}:${route.address.url.port}]"
}
private fun getPoolStats(): String {
return try {
"pool[idle:${connectionPool.idleConnectionCount()}, total:${connectionPool.connectionCount()}]"
} catch (_: Exception) {
"pool[stats unavailable]"
}
}
override fun callStart(call: Call) {
Timber.i("πŸ“ž Call started: ${call.url()}")
}
override fun dnsStart(call: Call, domainName: String) {
Timber.i("🌐 DNS lookup started for $domainName - ${call.url()}")
}
override fun dnsEnd(call: Call, domainName: String, inetAddressList: List<InetAddress>) {
Timber.i("🌐 DNS lookup finished for $domainName - ${call.url()}")
}
override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
val realPool = RealConnectionPool.get(connectionPool)
val poolStats = "pool[idle:${realPool.idleConnectionCount()}, total:${realPool.connectionCount()}]"
val route = call.request().url
Timber.i("πŸ”Œ Opening NEW connection to $inetSocketAddress via $proxy - ${call.url()}")
Timber.i("πŸ“Š Connection pool status: $poolStats - no reusable connection found for ${route.host}:${route.port}")
}
override fun secureConnectStart(call: Call) {
Timber.i("πŸ”’ TLS handshake started - ${call.url()}")
}
override fun secureConnectEnd(call: Call, handshake: okhttp3.Handshake?) {
Timber.i("πŸ”’ TLS handshake finished - ${call.url()}")
}
override fun connectEnd(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy,
protocol: Protocol?
) {
Timber.i(
"βœ… Connection established to $inetSocketAddress (protocol=${protocol ?: "unknown"}) " +
call.callSummary()
)
}
override fun connectFailed(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy,
protocol: Protocol?,
ioe: IOException
) {
Timber.i(
ioe,
"❌ Connection failed to $inetSocketAddress (protocol=${protocol ?: "unknown"}) - " +
call.url()
)
}
override fun connectionAcquired(call: Call, connection: Connection) {
val connectionKey = getConnectionKey(connection)
val lastUsed = connectionTracker[connectionKey]
val reuseInfo = if (lastUsed != null) {
val timeSinceLastUse = System.currentTimeMillis() - lastUsed
"REUSED (last used ${timeSinceLastUse}ms ago)"
} else {
"NEW"
}
connectionTracker[connectionKey] = System.currentTimeMillis()
val protocol = connection.protocol()
val supportsMultiplexing = protocol == Protocol.HTTP_2 || protocol == Protocol.H2_PRIOR_KNOWLEDGE
val multiplexInfo = if (supportsMultiplexing) "multiplexing=YES" else "multiplexing=NO"
Timber.i(
"♻️ Connection acquired [$reuseInfo] from ${getPoolInfo(connection)}: " +
"${connection.socket().remoteSocketAddress} protocol=$protocol $multiplexInfo ${getPoolStats()} - " +
call.url()
)
}
override fun requestHeadersStart(call: Call) {
Timber.i("⬆️ Request headers started - ${call.url()}")
}
override fun requestHeadersEnd(call: Call, request: Request) {
Timber.i("⬆️ Request headers finished - ${call.url()}")
}
override fun requestBodyStart(call: Call) {
Timber.i("⬆️ Request body started - ${call.url()}")
}
override fun requestBodyEnd(call: Call, byteCount: Long) {
Timber.i("⬆️ Request body finished: $byteCount bytes - ${call.url()}")
}
override fun responseHeadersStart(call: Call) {
Timber.i("⬇️ Response headers started - ${call.url()}")
}
override fun responseHeadersEnd(call: Call, response: Response) {
Timber.i("⬇️ Response headers finished - ${call.url()}")
}
override fun responseBodyStart(call: Call) {
Timber.i("⬇️ Response body started - ${call.url()}")
}
override fun responseBodyEnd(call: Call, byteCount: Long) {
Timber.i("⬇️ Response body finished: $byteCount bytes - ${call.url()}")
}
override fun connectionReleased(call: Call, connection: Connection) {
Timber.i(
"πŸ”„ Connection released back to pool: ${connection.socket().remoteSocketAddress} " +
call.callSummary()
)
}
private fun Call.callSummary(): String = "${[email protected]()} - ${request().url}"
override fun callEnd(call: Call) {
Timber.i("🏁 Call finished: ${call.url()}")
}
override fun callFailed(call: Call, ioe: IOException) {
Timber.i(ioe, "❌ Call failed: ${call.url()}")
}
private fun Call.url(): String = request().url.toString()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment