Skip to content

Commit 2decf9b

Browse files
committed
chore: refactor interceptor around the kotlin buildString DSL
1 parent 8a3a698 commit 2decf9b

File tree

1 file changed

+76
-99
lines changed

1 file changed

+76
-99
lines changed

src/main/kotlin/com/coder/toolbox/sdk/interceptors/LoggingInterceptor.kt

Lines changed: 76 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -5,139 +5,116 @@ import com.coder.toolbox.settings.HttpLoggingVerbosity
55
import okhttp3.Headers
66
import okhttp3.Interceptor
77
import okhttp3.MediaType
8+
import okhttp3.Request
89
import okhttp3.RequestBody
910
import okhttp3.Response
1011
import okhttp3.ResponseBody
1112
import okio.Buffer
1213
import java.nio.charset.StandardCharsets
1314

15+
private val SENSITIVE_HEADERS = setOf("Coder-Session-Token", "Proxy-Authorization")
16+
1417
class LoggingInterceptor(private val context: CoderToolboxContext) : Interceptor {
18+
1519
override fun intercept(chain: Interceptor.Chain): Response {
1620
val logLevel = context.settingsStore.httpClientLogLevel
1721
if (logLevel == HttpLoggingVerbosity.NONE) {
1822
return chain.proceed(chain.request())
1923
}
24+
2025
val request = chain.request()
21-
val requestLog = StringBuilder()
22-
requestLog.append("request --> ${request.method} ${request.url}\n")
23-
if (logLevel == HttpLoggingVerbosity.HEADERS) {
24-
requestLog.append(request.headers.toSanitizedString())
25-
}
26-
if (logLevel == HttpLoggingVerbosity.BODY) {
27-
request.body.toPrintableString()?.let {
28-
requestLog.append(it)
29-
}
30-
}
31-
context.logger.info(requestLog.toString())
26+
logRequest(request, logLevel)
3227

3328
val response = chain.proceed(request)
34-
val responseLog = StringBuilder()
35-
responseLog.append("response <-- ${response.code} ${response.message} ${request.url}\n")
36-
if (logLevel == HttpLoggingVerbosity.HEADERS) {
37-
responseLog.append(response.headers.toSanitizedString())
38-
}
39-
if (logLevel == HttpLoggingVerbosity.BODY) {
40-
response.body.toPrintableString()?.let {
41-
responseLog.append(it)
42-
}
43-
}
29+
logResponse(response, request, logLevel)
4430

45-
context.logger.info(responseLog.toString())
4631
return response
4732
}
4833

49-
private fun Headers.toSanitizedString(): String {
50-
val result = StringBuilder()
51-
this.forEach {
52-
if (it.first == "Coder-Session-Token" || it.first == "Proxy-Authorization") {
53-
result.append("${it.first}: <redacted>\n")
54-
} else {
55-
result.append("${it.first}: ${it.second}\n")
56-
}
57-
}
58-
return result.toString()
59-
}
34+
private fun logRequest(request: Request, logLevel: HttpLoggingVerbosity) {
35+
val log = buildString {
36+
append("request --> ${request.method} ${request.url}")
6037

61-
/**
62-
* Converts a RequestBody to a printable string representation.
63-
* Handles different content types appropriately.
64-
*
65-
* @return String representation of the body, or metadata if not readable
66-
*/
67-
fun RequestBody?.toPrintableString(): String? {
68-
if (this == null) {
69-
return null
70-
}
38+
if (logLevel >= HttpLoggingVerbosity.HEADERS) {
39+
append("\n${request.headers.sanitized()}")
40+
}
7141

72-
if (!contentType().isPrintable()) {
73-
return "[Binary request body: ${contentLength().formatBytes()}, Content-Type: ${contentType()}]\n"
42+
if (logLevel == HttpLoggingVerbosity.BODY) {
43+
request.body?.let { body ->
44+
append("\n${body.toPrintableString()}")
45+
}
46+
}
7447
}
7548

76-
return try {
77-
val buffer = Buffer()
78-
writeTo(buffer)
79-
80-
val charset = contentType()?.charset() ?: StandardCharsets.UTF_8
81-
buffer.readString(charset)
82-
} catch (e: Exception) {
83-
"[Error reading request body: ${e.message}]\n"
84-
}
49+
context.logger.info(log)
8550
}
8651

87-
/**
88-
* Converts a ResponseBody to a printable string representation.
89-
* Handles different content types appropriately.
90-
*
91-
* @return String representation of the body, or metadata if not readable
92-
*/
93-
fun ResponseBody?.toPrintableString(): String? {
94-
if (this == null) {
95-
return null
96-
}
52+
private fun logResponse(response: Response, request: Request, logLevel: HttpLoggingVerbosity) {
53+
val log = buildString {
54+
append("response <-- ${response.code} ${response.message} ${request.url}")
9755

98-
if (!contentType().isPrintable()) {
99-
return "[Binary response body: ${contentLength().formatBytes()}, Content-Type: ${contentType()}]\n"
100-
}
56+
if (logLevel >= HttpLoggingVerbosity.HEADERS) {
57+
append("\n${response.headers.sanitized()}")
58+
}
10159

102-
return try {
103-
val source = source()
104-
source.request(Long.MAX_VALUE)
105-
val charset = contentType()?.charset() ?: StandardCharsets.UTF_8
106-
source.buffer.clone().readString(charset)
107-
} catch (e: Exception) {
108-
"[Error reading response body: ${e.message}]\n"
60+
if (logLevel == HttpLoggingVerbosity.BODY) {
61+
response.body?.let { body ->
62+
append("\n${body.toPrintableString()}")
63+
}
64+
}
10965
}
66+
67+
context.logger.info(log)
11068
}
69+
}
11170

112-
/**
113-
* Checks if a MediaType represents printable/readable content
114-
*/
115-
private fun MediaType?.isPrintable(): Boolean {
116-
if (this == null) return false
71+
// Extension functions for cleaner code
72+
private fun Headers.sanitized(): String = buildString {
73+
this@sanitized.forEach { (name, value) ->
74+
val displayValue = if (name in SENSITIVE_HEADERS) "<redacted>" else value
75+
append("$name: $displayValue\n")
76+
}
77+
}
11778

118-
return when {
119-
// Text types
120-
type == "text" -> true
79+
private fun RequestBody.toPrintableString(): String {
80+
if (!contentType().isPrintable()) {
81+
return "[Binary body: ${contentLength().formatBytes()}, ${contentType()}]"
82+
}
12183

122-
// JSON variants
123-
subtype == "json" -> true
124-
subtype.endsWith("+json") -> true
84+
return try {
85+
val buffer = Buffer()
86+
writeTo(buffer)
87+
buffer.readString(contentType()?.charset() ?: StandardCharsets.UTF_8)
88+
} catch (e: Exception) {
89+
"[Error reading body: ${e.message}]"
90+
}
91+
}
12592

126-
// Default to non-printable for safety
127-
else -> false
128-
}
93+
private fun ResponseBody.toPrintableString(): String {
94+
if (!contentType().isPrintable()) {
95+
return "[Binary body: ${contentLength().formatBytes()}, ${contentType()}]"
12996
}
13097

131-
/**
132-
* Formats byte count in human-readable format
133-
*/
134-
private fun Long.formatBytes(): String {
135-
return when {
136-
this < 0 -> "unknown size"
137-
this < 1024 -> "${this}B"
138-
this < 1024 * 1024 -> "${this / 1024}KB"
139-
this < 1024 * 1024 * 1024 -> "${this / (1024 * 1024)}MB"
140-
else -> "${this / (1024 * 1024 * 1024)}GB"
141-
}
98+
return try {
99+
val source = source()
100+
source.request(Long.MAX_VALUE)
101+
source.buffer.clone().readString(contentType()?.charset() ?: StandardCharsets.UTF_8)
102+
} catch (e: Exception) {
103+
"[Error reading body: ${e.message}]"
142104
}
105+
}
106+
107+
private fun MediaType?.isPrintable(): Boolean = when {
108+
this == null -> false
109+
type == "text" -> true
110+
subtype == "json" || subtype.endsWith("+json") -> true
111+
else -> false
112+
}
113+
114+
private fun Long.formatBytes(): String = when {
115+
this < 0 -> "unknown"
116+
this < 1024 -> "${this}B"
117+
this < 1024 * 1024 -> "${this / 1024}KB"
118+
this < 1024 * 1024 * 1024 -> "${this / (1024 * 1024)}MB"
119+
else -> "${this / (1024 * 1024 * 1024)}GB"
143120
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy