Skip to content

Commit a62b5e3

Browse files
feat(client): improve logging
Logging is now: 1. Streaming 4. Configurable in-memory 5. Generally more robust 6. Usable with any underlying http client
1 parent 60effd3 commit a62b5e3

10 files changed

Lines changed: 1727 additions & 17 deletions

File tree

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,6 @@ The SDK throws custom unchecked exception types:
615615

616616
## Logging
617617

618-
The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
619-
620618
Enable logging by setting the `IMAGE_KIT_LOG` environment variable to `info`:
621619

622620
```sh
@@ -629,6 +627,19 @@ Or to `debug` for more verbose logging:
629627
export IMAGE_KIT_LOG=debug
630628
```
631629

630+
Or configure the client manually using the `logLevel` method:
631+
632+
```java
633+
import io.imagekit.client.ImageKitClient;
634+
import io.imagekit.client.okhttp.ImageKitOkHttpClient;
635+
import io.imagekit.core.LogLevel;
636+
637+
ImageKitClient client = ImageKitOkHttpClient.builder()
638+
.fromEnv()
639+
.logLevel(LogLevel.INFO)
640+
.build();
641+
```
642+
632643
## ProGuard and R8
633644

634645
Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `image-kit-java-core` is published with a [configuration file](image-kit-java-core/src/main/resources/META-INF/proguard/image-kit-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).

image-kit-java-client-okhttp/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ dependencies {
77
api(project(":image-kit-java-core"))
88

99
implementation("com.squareup.okhttp3:okhttp:4.12.0")
10-
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
1110

1211
testImplementation(kotlin("test"))
1312
testImplementation("org.assertj:assertj-core:3.27.7")

image-kit-java-client-okhttp/src/main/kotlin/io/imagekit/client/okhttp/ImageKitOkHttpClient.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
66
import io.imagekit.client.ImageKitClient
77
import io.imagekit.client.ImageKitClientImpl
88
import io.imagekit.core.ClientOptions
9+
import io.imagekit.core.LogLevel
910
import io.imagekit.core.Sleeper
1011
import io.imagekit.core.Timeout
1112
import io.imagekit.core.http.Headers
@@ -277,6 +278,15 @@ class ImageKitOkHttpClient private constructor() {
277278
*/
278279
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
279280

281+
/**
282+
* The level at which to log request and response information.
283+
*
284+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
285+
*
286+
* Defaults to [LogLevel.fromEnv].
287+
*/
288+
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
289+
280290
/**
281291
* Your ImageKit private API key (starts with `private_`). You can find this in the
282292
* [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).

image-kit-java-client-okhttp/src/main/kotlin/io/imagekit/client/okhttp/ImageKitOkHttpClientAsync.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
66
import io.imagekit.client.ImageKitClientAsync
77
import io.imagekit.client.ImageKitClientAsyncImpl
88
import io.imagekit.core.ClientOptions
9+
import io.imagekit.core.LogLevel
910
import io.imagekit.core.Sleeper
1011
import io.imagekit.core.Timeout
1112
import io.imagekit.core.http.Headers
@@ -277,6 +278,15 @@ class ImageKitOkHttpClientAsync private constructor() {
277278
*/
278279
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
279280

281+
/**
282+
* The level at which to log request and response information.
283+
*
284+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
285+
*
286+
* Defaults to [LogLevel.fromEnv].
287+
*/
288+
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
289+
280290
/**
281291
* Your ImageKit private API key (starts with `private_`). You can find this in the
282292
* [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).

image-kit-java-client-okhttp/src/main/kotlin/io/imagekit/client/okhttp/OkHttpClient.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import okhttp3.Request
3535
import okhttp3.RequestBody
3636
import okhttp3.RequestBody.Companion.toRequestBody
3737
import okhttp3.Response
38-
import okhttp3.logging.HttpLoggingInterceptor
3938
import okio.BufferedSink
4039
import okio.buffer
4140
import okio.sink
@@ -93,18 +92,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
9392
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
9493
val clientBuilder = okHttpClient.newBuilder()
9594

96-
val logLevel =
97-
when (System.getenv("IMAGE_KIT_LOG")?.lowercase()) {
98-
"info" -> HttpLoggingInterceptor.Level.BASIC
99-
"debug" -> HttpLoggingInterceptor.Level.BODY
100-
else -> null
101-
}
102-
if (logLevel != null) {
103-
clientBuilder.addNetworkInterceptor(
104-
HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
105-
)
106-
}
107-
10895
requestOptions.timeout?.let {
10996
clientBuilder
11097
.connectTimeout(it.connect())

image-kit-java-core/src/main/kotlin/io/imagekit/core/ClientOptions.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package io.imagekit.core
55
import com.fasterxml.jackson.databind.json.JsonMapper
66
import io.imagekit.core.http.Headers
77
import io.imagekit.core.http.HttpClient
8+
import io.imagekit.core.http.LoggingHttpClient
89
import io.imagekit.core.http.PhantomReachableClosingHttpClient
910
import io.imagekit.core.http.QueryParams
1011
import io.imagekit.core.http.RetryingHttpClient
@@ -97,6 +98,14 @@ private constructor(
9798
* Defaults to 2.
9899
*/
99100
@get:JvmName("maxRetries") val maxRetries: Int,
101+
/**
102+
* The level at which to log request and response information.
103+
*
104+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
105+
*
106+
* Defaults to [LogLevel.fromEnv].
107+
*/
108+
@get:JvmName("logLevel") val logLevel: LogLevel,
100109
/**
101110
* Your ImageKit private API key (starts with `private_`). You can find this in the
102111
* [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).
@@ -175,6 +184,7 @@ private constructor(
175184
private var responseValidation: Boolean = false
176185
private var timeout: Timeout = Timeout.default()
177186
private var maxRetries: Int = 2
187+
private var logLevel: LogLevel = LogLevel.fromEnv()
178188
private var privateKey: String? = null
179189
private var password: String? = "do_not_set"
180190
private var webhookSecret: String? = null
@@ -192,6 +202,7 @@ private constructor(
192202
responseValidation = clientOptions.responseValidation
193203
timeout = clientOptions.timeout
194204
maxRetries = clientOptions.maxRetries
205+
logLevel = clientOptions.logLevel
195206
privateKey = clientOptions.privateKey
196207
password = clientOptions.password
197208
webhookSecret = clientOptions.webhookSecret
@@ -304,6 +315,15 @@ private constructor(
304315
*/
305316
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
306317

318+
/**
319+
* The level at which to log request and response information.
320+
*
321+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
322+
*
323+
* Defaults to [LogLevel.fromEnv].
324+
*/
325+
fun logLevel(logLevel: LogLevel) = apply { this.logLevel = logLevel }
326+
307327
/**
308328
* Your ImageKit private API key (starts with `private_`). You can find this in the
309329
* [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).
@@ -430,6 +450,7 @@ private constructor(
430450
* System properties take precedence over environment variables.
431451
*/
432452
fun fromEnv() = apply {
453+
logLevel(LogLevel.fromEnv())
433454
(System.getProperty("imagekit.baseUrl") ?: System.getenv("IMAGE_KIT_BASE_URL"))?.let {
434455
baseUrl(it)
435456
}
@@ -497,7 +518,13 @@ private constructor(
497518
return ClientOptions(
498519
httpClient,
499520
RetryingHttpClient.builder()
500-
.httpClient(httpClient)
521+
.httpClient(
522+
LoggingHttpClient.builder()
523+
.httpClient(httpClient)
524+
.clock(clock)
525+
.level(logLevel)
526+
.build()
527+
)
501528
.sleeper(sleeper)
502529
.clock(clock)
503530
.maxRetries(maxRetries)
@@ -512,6 +539,7 @@ private constructor(
512539
responseValidation,
513540
timeout,
514541
maxRetries,
542+
logLevel,
515543
privateKey,
516544
password,
517545
webhookSecret,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// File generated from our OpenAPI spec by Stainless.
2+
3+
package io.imagekit.core
4+
5+
/** The level at which to log request and response information. */
6+
enum class LogLevel {
7+
/** No logging. */
8+
OFF,
9+
/** Minimal request and response summary logs. No headers or bodies are logged. */
10+
INFO,
11+
/** [INFO] logs plus details about request failures. */
12+
ERROR,
13+
/**
14+
* Full request and response logs. Sensitive headers are redacted, but sensitive data in request
15+
* and response bodies may still be visible.
16+
*/
17+
DEBUG;
18+
19+
/** Returns whether this level is at or higher than the given [level]. */
20+
fun shouldLog(level: LogLevel): Boolean = ordinal >= level.ordinal
21+
22+
companion object {
23+
24+
/** Returns a [LogLevel] based on the `IMAGE_KIT_LOG` environment variable. */
25+
fun fromEnv() =
26+
when (System.getenv("IMAGE_KIT_LOG")?.lowercase()) {
27+
"info" -> INFO
28+
"error" -> ERROR
29+
"debug" -> DEBUG
30+
else -> OFF
31+
}
32+
}
33+
}

image-kit-java-core/src/main/kotlin/io/imagekit/core/Utils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package io.imagekit.core
55
import io.imagekit.errors.ImageKitInvalidDataException
66
import java.util.Collections
77
import java.util.SortedMap
8+
import java.util.SortedSet
89
import java.util.concurrent.CompletableFuture
910
import java.util.concurrent.locks.Lock
1011

@@ -16,6 +17,11 @@ internal fun <T : Any> T?.getOrThrow(name: String): T =
1617
internal fun <T> List<T>.toImmutable(): List<T> =
1718
if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList())
1819

20+
@JvmSynthetic
21+
internal fun <V : Comparable<V>> SortedSet<V>.toImmutable(): SortedSet<V> =
22+
if (isEmpty()) Collections.emptySortedSet()
23+
else Collections.unmodifiableSortedSet(toSortedSet(comparator() ?: Comparator.naturalOrder()))
24+
1925
@JvmSynthetic
2026
internal fun <K, V> Map<K, V>.toImmutable(): Map<K, V> =
2127
if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap())

0 commit comments

Comments
 (0)