Skip to content

Commit 45718e3

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 164fca2 commit 45718e3

10 files changed

Lines changed: 1728 additions & 17 deletions

File tree

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,6 @@ while (true) {
352352

353353
## Logging
354354

355-
The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
356-
357355
Enable logging by setting the `FINCH_LOG` environment variable to `info`:
358356

359357
```sh
@@ -366,6 +364,20 @@ Or to `debug` for more verbose logging:
366364
export FINCH_LOG=debug
367365
```
368366

367+
Or configure the client manually using the `logLevel` method:
368+
369+
```java
370+
import com.tryfinch.api.client.FinchClient;
371+
import com.tryfinch.api.client.okhttp.FinchOkHttpClient;
372+
import com.tryfinch.api.core.LogLevel;
373+
374+
FinchClient client = FinchOkHttpClient.builder()
375+
.fromEnv()
376+
.logLevel(LogLevel.INFO)
377+
.accessToken("My Access Token")
378+
.build();
379+
```
380+
369381
## Webhook Verification
370382

371383
We provide helper methods for verifying that a webhook request came from Finch, and not a malicious third party.

finch-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(":finch-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")

finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClient.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 com.tryfinch.api.client.FinchClient
77
import com.tryfinch.api.client.FinchClientImpl
88
import com.tryfinch.api.core.ClientOptions
9+
import com.tryfinch.api.core.LogLevel
910
import com.tryfinch.api.core.Sleeper
1011
import com.tryfinch.api.core.Timeout
1112
import com.tryfinch.api.core.http.AsyncStreamResponse
@@ -290,6 +291,15 @@ class FinchOkHttpClient private constructor() {
290291
*/
291292
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
292293

294+
/**
295+
* The level at which to log request and response information.
296+
*
297+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
298+
*
299+
* Defaults to [LogLevel.fromEnv].
300+
*/
301+
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
302+
293303
fun accessToken(accessToken: String?) = apply { clientOptions.accessToken(accessToken) }
294304

295305
/** Alias for calling [Builder.accessToken] with `accessToken.orElse(null)`. */

finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/client/okhttp/FinchOkHttpClientAsync.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 com.tryfinch.api.client.FinchClientAsync
77
import com.tryfinch.api.client.FinchClientAsyncImpl
88
import com.tryfinch.api.core.ClientOptions
9+
import com.tryfinch.api.core.LogLevel
910
import com.tryfinch.api.core.Sleeper
1011
import com.tryfinch.api.core.Timeout
1112
import com.tryfinch.api.core.http.AsyncStreamResponse
@@ -290,6 +291,15 @@ class FinchOkHttpClientAsync private constructor() {
290291
*/
291292
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
292293

294+
/**
295+
* The level at which to log request and response information.
296+
*
297+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
298+
*
299+
* Defaults to [LogLevel.fromEnv].
300+
*/
301+
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
302+
293303
fun accessToken(accessToken: String?) = apply { clientOptions.accessToken(accessToken) }
294304

295305
/** Alias for calling [Builder.accessToken] with `accessToken.orElse(null)`. */

finch-java-client-okhttp/src/main/kotlin/com/tryfinch/api/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("FINCH_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())

finch-java-core/src/main/kotlin/com/tryfinch/api/core/ClientOptions.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
66
import com.tryfinch.api.core.http.AsyncStreamResponse
77
import com.tryfinch.api.core.http.Headers
88
import com.tryfinch.api.core.http.HttpClient
9+
import com.tryfinch.api.core.http.LoggingHttpClient
910
import com.tryfinch.api.core.http.PhantomReachableClosingHttpClient
1011
import com.tryfinch.api.core.http.QueryParams
1112
import com.tryfinch.api.core.http.RetryingHttpClient
@@ -111,6 +112,14 @@ private constructor(
111112
* Defaults to 2.
112113
*/
113114
@get:JvmName("maxRetries") val maxRetries: Int,
115+
/**
116+
* The level at which to log request and response information.
117+
*
118+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
119+
*
120+
* Defaults to [LogLevel.fromEnv].
121+
*/
122+
@get:JvmName("logLevel") val logLevel: LogLevel,
114123
private val accessToken: String?,
115124
private val clientId: String?,
116125
private val clientSecret: String?,
@@ -177,6 +186,7 @@ private constructor(
177186
private var responseValidation: Boolean = false
178187
private var timeout: Timeout = Timeout.default()
179188
private var maxRetries: Int = 2
189+
private var logLevel: LogLevel = LogLevel.fromEnv()
180190
private var accessToken: String? = null
181191
private var clientId: String? = null
182192
private var clientSecret: String? = null
@@ -196,6 +206,7 @@ private constructor(
196206
responseValidation = clientOptions.responseValidation
197207
timeout = clientOptions.timeout
198208
maxRetries = clientOptions.maxRetries
209+
logLevel = clientOptions.logLevel
199210
accessToken = clientOptions.accessToken
200211
clientId = clientOptions.clientId
201212
clientSecret = clientOptions.clientSecret
@@ -323,6 +334,15 @@ private constructor(
323334
*/
324335
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
325336

337+
/**
338+
* The level at which to log request and response information.
339+
*
340+
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
341+
*
342+
* Defaults to [LogLevel.fromEnv].
343+
*/
344+
fun logLevel(logLevel: LogLevel) = apply { this.logLevel = logLevel }
345+
326346
fun accessToken(accessToken: String?) = apply { this.accessToken = accessToken }
327347

328348
/** Alias for calling [Builder.accessToken] with `accessToken.orElse(null)`. */
@@ -441,6 +461,7 @@ private constructor(
441461
* System properties take precedence over environment variables.
442462
*/
443463
fun fromEnv() = apply {
464+
logLevel(LogLevel.fromEnv())
444465
(System.getProperty("finch.baseUrl") ?: System.getenv("FINCH_BASE_URL"))?.let {
445466
baseUrl(it)
446467
}
@@ -527,7 +548,13 @@ private constructor(
527548
return ClientOptions(
528549
httpClient,
529550
RetryingHttpClient.builder()
530-
.httpClient(httpClient)
551+
.httpClient(
552+
LoggingHttpClient.builder()
553+
.httpClient(httpClient)
554+
.clock(clock)
555+
.level(logLevel)
556+
.build()
557+
)
531558
.sleeper(sleeper)
532559
.clock(clock)
533560
.maxRetries(maxRetries)
@@ -543,6 +570,7 @@ private constructor(
543570
responseValidation,
544571
timeout,
545572
maxRetries,
573+
logLevel,
546574
accessToken,
547575
clientId,
548576
clientSecret,
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 com.tryfinch.api.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 `FINCH_LOG` environment variable. */
25+
fun fromEnv() =
26+
when (System.getenv("FINCH_LOG")?.lowercase()) {
27+
"info" -> INFO
28+
"error" -> ERROR
29+
"debug" -> DEBUG
30+
else -> OFF
31+
}
32+
}
33+
}

finch-java-core/src/main/kotlin/com/tryfinch/api/core/Utils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.tryfinch.api.core.http.Headers
66
import com.tryfinch.api.errors.FinchInvalidDataException
77
import java.util.Collections
88
import java.util.SortedMap
9+
import java.util.SortedSet
910
import java.util.concurrent.CompletableFuture
1011
import java.util.concurrent.locks.Lock
1112

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

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

0 commit comments

Comments
 (0)