@@ -5,139 +5,116 @@ import com.coder.toolbox.settings.HttpLoggingVerbosity
5
5
import okhttp3.Headers
6
6
import okhttp3.Interceptor
7
7
import okhttp3.MediaType
8
+ import okhttp3.Request
8
9
import okhttp3.RequestBody
9
10
import okhttp3.Response
10
11
import okhttp3.ResponseBody
11
12
import okio.Buffer
12
13
import java.nio.charset.StandardCharsets
13
14
15
+ private val SENSITIVE_HEADERS = setOf (" Coder-Session-Token" , " Proxy-Authorization" )
16
+
14
17
class LoggingInterceptor (private val context : CoderToolboxContext ) : Interceptor {
18
+
15
19
override fun intercept (chain : Interceptor .Chain ): Response {
16
20
val logLevel = context.settingsStore.httpClientLogLevel
17
21
if (logLevel == HttpLoggingVerbosity .NONE ) {
18
22
return chain.proceed(chain.request())
19
23
}
24
+
20
25
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)
32
27
33
28
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)
44
30
45
- context.logger.info(responseLog.toString())
46
31
return response
47
32
}
48
33
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} " )
60
37
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
+ }
71
41
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
+ }
74
47
}
75
48
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)
85
50
}
86
51
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} " )
97
55
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
+ }
101
59
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
+ }
109
65
}
66
+
67
+ context.logger.info(log)
110
68
}
69
+ }
111
70
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
+ }
117
78
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
+ }
121
83
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
+ }
125
92
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()} ] "
129
96
}
130
97
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} ]"
142
104
}
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"
143
120
}
0 commit comments