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)