Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
Logging refactor (#74)
Browse files Browse the repository at this point in the history
* Adds the `ktor-client-logging` and `mu-logging-jvm` dependencies.

* Adds the `LoggingConfiguration` in the `LoggingConfiguration`

- Create the LoggingConfiguration data class.
- Create the HttpClientLogLevel enum.
- Implements the LoggingConfiguration into the ClientConfiguration.
- Rewrite the docs.

* Implement in the `Client` the new logger

- Add the KLogging()
- Set the client logger level
- Remove the logger from the EventClient
- Replace the `log.` with `logger.` in the EventClient
- Add the `.level` property in the KLogger

* Implement in the `RequestHandler` the `KLogging()`

- Add the KLogging()
- Remove the logger from the EventClient
  • Loading branch information
MaicolAntali committed Dec 12, 2023
1 parent c5e4a0f commit a93198d
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 43 deletions.
2 changes: 2 additions & 0 deletions clashJ/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ dependencies {

implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.apache5)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.gson)

implementation(libs.mu.logging.jvm)
api(libs.logback.classic)

testImplementation(libs.ktor.client.mock)
Expand Down
8 changes: 8 additions & 0 deletions clashJ/src/main/kotlin/io/github/maicolantali/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ import io.github.maicolantali.types.internal.configuration.ClientConfiguration
import io.github.maicolantali.util.API_BASE_URL
import io.github.maicolantali.util.encodeTag
import io.github.maicolantali.util.getConfiguredRequestHandler
import io.github.maicolantali.util.level
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import mu.KLogging

/**
* Client class is used to interact with the Clash of Clans API.
Expand All @@ -53,8 +55,14 @@ open class Client(
internal open val password: String,
clientConfiguration: ClientConfiguration.() -> Unit = {},
) {
internal companion object : KLogging()

internal val config = ClientConfiguration().apply(clientConfiguration)

init {
logger("io.github.maicolantali").level = config.logging.clientLogLevel
}

private val requestHandler by lazy { getConfiguredRequestHandler() }

/**
Expand Down
30 changes: 12 additions & 18 deletions clashJ/src/main/kotlin/io/github/maicolantali/EventClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import io.github.maicolantali.event.PlayerEvents
import io.github.maicolantali.event.WarEvents
import io.github.maicolantali.event.cache.CacheManager
import io.github.maicolantali.exception.MaintenanceException
import io.github.maicolantali.http.RequestHandler
import io.github.maicolantali.types.api.model.clans.clan.Clan
import io.github.maicolantali.types.api.model.clans.clanMember.ClanMember
import io.github.maicolantali.types.api.model.clans.clanwar.ClanWar
Expand All @@ -25,7 +24,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import java.time.LocalDateTime
import java.util.concurrent.Executors

Expand Down Expand Up @@ -62,10 +60,6 @@ class EventClient(
private var isApiInMaintenance = false
private var maintenanceStartTime: LocalDateTime? = null

private companion object {
private val log = LoggerFactory.getLogger(RequestHandler::class.java)
}

/**
* Starts polling for events and updating player data.
*
Expand Down Expand Up @@ -93,7 +87,7 @@ class EventClient(
* @param playerTag The player tag that will be monitored and updated.
*/
fun addPlayerToUpdateQueue(playerTag: String) {
log.info("Adding the player tag: $playerTag to the update queue for monitoring.")
logger.info("Adding the player tag: $playerTag to the update queue for monitoring.")
players.add(adjustTag(playerTag))
}

Expand All @@ -107,7 +101,7 @@ class EventClient(
* @param clanTag The clan tag that will be monitored and updated.
*/
fun addClanToUpdateQueue(clanTag: String) {
log.info("Adding the clan tag: $clanTag to the update queue for monitoring.")
logger.info("Adding the clan tag: $clanTag to the update queue for monitoring.")
clans.add(adjustTag(clanTag))
}

Expand All @@ -121,7 +115,7 @@ class EventClient(
* @param clanTag The clan tag for which war events will be monitored and updated.
*/
fun addWarToUpdateQueue(clanTag: String) {
log.info("Adding the clan tag: $clanTag to the update queue for monitoring war events.")
logger.info("Adding the clan tag: $clanTag to the update queue for monitoring war events.")
wars.add(adjustTag(clanTag))
}

Expand All @@ -135,7 +129,7 @@ class EventClient(
* @param playerTag The player tag that will no longer be monitored.
*/
fun removePlayerToUpdateQueue(playerTag: String) {
log.info("Removing the player tag: $playerTag from the update queue.")
logger.info("Removing the player tag: $playerTag from the update queue.")
players.remove(adjustTag(playerTag))
}

Expand All @@ -149,7 +143,7 @@ class EventClient(
* @param clanTag The clan tag that will no longer be monitored.
*/
fun removeClanToUpdateQueue(clanTag: String) {
log.info("Removing the clan tag: $clanTag from the update queue.")
logger.info("Removing the clan tag: $clanTag from the update queue.")
clans.remove(adjustTag(clanTag))
}

Expand All @@ -163,7 +157,7 @@ class EventClient(
* @param clanTag The clan tag for which war events will no longer be monitored.
*/
fun removeWarToUpdateQueue(clanTag: String) {
log.info("Removing the clan tag: $clanTag from the update queue for monitoring war events.")
logger.info("Removing the clan tag: $clanTag from the update queue for monitoring war events.")
wars.remove(adjustTag(clanTag))
}

Expand Down Expand Up @@ -278,7 +272,7 @@ class EventClient(
event: Event<*, *, *, *>,
callback: Callback<*, *, *>,
) {
log.info("Adding a new callback for the $event event.")
logger.info("Adding a new callback for the $event event.")

eventCallbacks
.computeIfAbsent(event::class.java.superclass) { HashMap() }
Expand All @@ -294,22 +288,22 @@ class EventClient(

if (isApiInMaintenance) {
isApiInMaintenance = false
log.info("API is back online. Resuming updates.")
logger.info("API is back online. Resuming updates.")

triggerEvent(maintenanceStartTime, LocalDateTime.now(), MaintenanceEvents::class.java)
maintenanceStartTime = null
}
} catch (e: MaintenanceException) {
if (!isApiInMaintenance) {
log.info("API is in maintenance. Stopping updates.")
logger.info("API is in maintenance. Stopping updates.")
isApiInMaintenance = true // API is in maintenance
}
if (maintenanceStartTime == null) {
maintenanceStartTime = LocalDateTime.now()
triggerEvent(maintenanceStartTime, eventType = MaintenanceEvents::class.java)
}
} catch (e: Exception) {
log.error("Error checking API maintenance status: ${e.message}")
logger.error("Error checking API maintenance status: ${e.message}")
}

delay(config.event.maintenanceCheckInterval)
Expand Down Expand Up @@ -340,10 +334,10 @@ class EventClient(

delayMillis = 100L // Reset delay on successful request
} catch (e: MaintenanceException) {
log.info("API is in maintenance. Stopping updates (${type.simpleName}: $tag).")
logger.info("API is in maintenance. Stopping updates (${type.simpleName}: $tag).")
isApiInMaintenance = true
} catch (e: Exception) {
log.error("Error: ${e.message}")
logger.error("Error: ${e.message}")
delay(delayMillis) // Exponential backoff
delayMillis *= 2
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import mu.KLogging
import java.time.LocalDateTime
import java.util.Base64
import java.util.concurrent.atomic.AtomicBoolean
Expand All @@ -61,9 +61,8 @@ class RequestHandler(
private val engine: HttpClientEngine,
private val throttler: BaseThrottler,
) {
private companion object {
private companion object : KLogging() {
private const val MAX_KEY_COUNT = 10
private val log = LoggerFactory.getLogger(RequestHandler::class.java)
}

private val keysSet = SafeSet<String>()
Expand Down Expand Up @@ -93,7 +92,7 @@ class RequestHandler(
*/
suspend fun login() =
coroutineScope {
log.info("Logging into the developer website.")
logger.info("Logging into the developer website.")

keysSet.clear()

Expand All @@ -110,29 +109,29 @@ class RequestHandler(
break // Success, exit the loop
} catch (e: HttpRequestTimeoutException) {
if (attempt == 3) {
log.error("Login operation failed. Error ${e.message}")
logger.error("Login operation failed. Error ${e.message}")
throw e
}

log.warn("Login operation failed. Retry in $delayMillis ms")
logger.warn("Login operation failed. Retry in $delayMillis ms")
delay(delayMillis)
delayMillis *= 2 // Exponential backoff
}
}

if (loginResponse.status == HttpStatusCode.Forbidden) {
log.error("The provided credential are incorrect or invalid.")
logger.error("The provided credential are incorrect or invalid.")
closeHttpClient()
throw InvalidCredentialException("The provided credential are incorrect or invalid.")
}
log.info("Successfully logged in.")
logger.info("Successfully logged in.")

val ip: String =
async {
getIp(loginResponse.body<JsonObject>().getAsJsonPrimitive("temporaryAPIToken").asString)
}.await()

log.info("The current IP address has been successfully retrieved. IP address: $ip")
logger.info("The current IP address has been successfully retrieved. IP address: $ip")

retriveKeys(ip, loginResponse.headers["set-cookie"]!!)
}
Expand Down Expand Up @@ -217,7 +216,7 @@ class RequestHandler(

continue
} else {
log.error("Forbidden, resp: $responseJson, reason: ${responseJson.get("reason")}")
logger.error("Forbidden, resp: $responseJson, reason: ${responseJson.get("reason")}")
}

throw HttpException("Forbidden, resp: $responseJson, reason: ${responseJson.get("reason")}")
Expand All @@ -228,7 +227,7 @@ class RequestHandler(
}

HttpStatusCode.TooManyRequests -> {
log.error("Reached the maximum rate-limits by the API. Consider a new number of request allowed per second.")
logger.error("Reached the maximum rate-limits by the API. Consider a new number of request allowed per second.")
throw HttpException(
"Reached the maximum rate-limits by the API. Consider a new number of request allowed per second.",
)
Expand All @@ -251,13 +250,13 @@ class RequestHandler(
throw BadGatewayException("The API timed out waiting for the request.")
}

log.debug("HttpRequestTimeoutException caught. Retry to execute the request: $url.")
logger.debug("HttpRequestTimeoutException caught. Retry to execute the request: $url.")
delay(tries * 3 + 1L)
continue
}
}

log.error("Reached the maxRetryAttempts (${requestOptions.maxRetryAttempts}) without a valid responses.")
logger.error("Reached the maxRetryAttempts (${requestOptions.maxRetryAttempts}) without a valid responses.")
throw ClashJException("Reached the maxRetryAttempts (${requestOptions.maxRetryAttempts}) without a valid responses.")
}

Expand All @@ -283,7 +282,7 @@ class RequestHandler(

// Filter key for the specified key name,
val keys = keyListResponse.keys.toMutableList()
log.info("${keys.size} valid keys retrieved from the developer site.")
logger.info("${keys.size} valid keys retrieved from the developer site.")

// Revoke keys that have not the current ip
keys.filter { it.name == keyOptions.keyName }
Expand Down Expand Up @@ -315,14 +314,14 @@ class RequestHandler(
}

if (keysSet.size() < keyOptions.keyCount && keys.size == 10) {
log.warn(
logger.warn(
"Required ${keyOptions.keyCount} keys, but maximum ${keysSet.size()} found/made (limit: 10/acc). " +
"Please delete keys or decrease `keyCount`.",
)
}

if (keysSet.size() == 0) {
log.error(
logger.error(
"${keys.size} existing API keys, none match keyName: ${keyOptions.keyName}. " +
"Specify another keyName or delete unused keys at 'https://developer.clashofclans.com'.",
)
Expand Down Expand Up @@ -356,7 +355,7 @@ class RequestHandler(
LocalDateTime.now()
}
}"}"""
log.info("Generating a new key based on the data: $keyJson")
logger.info("Generating a new key based on the data: $keyJson")

val response: CreateKeyResponse =
httpClient.post("$DEV_SITE_BASE_URL/apikey/create") {
Expand All @@ -365,7 +364,7 @@ class RequestHandler(
}.body()

if (response.status.message != "ok") {
log.error("Failed to create the new API Key. Details: ${response.status.detail}")
logger.error("Failed to create the new API Key. Details: ${response.status.detail}")
closeHttpClient()
throw HttpException("Failed to create the new API Key. Details: ${response.status.detail}")
}
Expand All @@ -386,7 +385,7 @@ class RequestHandler(
key: Key,
cookie: String,
): Boolean {
log.info("Removing the key named: ${key.name} and IP: ${key.cidrRanges} (as it does not match our current IP address).")
logger.info("Removing the key named: ${key.name} and IP: ${key.cidrRanges} (as it does not match our current IP address).")

val response =
httpClient.post("${DEV_SITE_BASE_URL}/apikey/revoke") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.github.maicolantali.types.internal

import io.ktor.client.plugins.logging.LogLevel

/**
* Enumeration representing different log levels for an HTTP client.
*
* This enum class defines log levels that can be associated with an HTTP client,
* mapping them to the corresponding log levels in the Ktor HTTP client library.
*/
enum class HttpClientLogLevel {
/**
* No logging.
*/
NONE,

/**
* Log only informational messages for HTTP requests and responses.
*/
INFO,

/**
* Log HTTP request and response bodies in addition to informational messages.
*/
BODY,

/**
* Log HTTP headers in addition to informational messages.
*/
HEADERS,

/**
* Log all details, including HTTP headers and request/response bodies.
*/
ALL,

;

/**
* Converts the enum value to the corresponding Ktor log level.
*
* @return The Ktor log level associated with the enum value.
*/
internal fun toKtorLogLevel() =
when (this) {
NONE -> LogLevel.NONE
INFO -> LogLevel.INFO
BODY -> LogLevel.BODY
HEADERS -> LogLevel.HEADERS
ALL -> LogLevel.ALL
}
}
Loading

0 comments on commit a93198d

Please sign in to comment.