-
-
Save kozaxinan/99317b78c1e9dba27e39290386cba658 to your computer and use it in GitHub Desktop.
ConnectionDebugListener for OkHttp to log everything in event listener
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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