@@ -5,139 +5,116 @@ import com.coder.toolbox.settings.HttpLoggingVerbosity
55import okhttp3.Headers
66import okhttp3.Interceptor
77import okhttp3.MediaType
8+ import okhttp3.Request
89import okhttp3.RequestBody
910import okhttp3.Response
1011import okhttp3.ResponseBody
1112import okio.Buffer
1213import java.nio.charset.StandardCharsets
1314
15+ private val SENSITIVE_HEADERS = setOf (" Coder-Session-Token" , " Proxy-Authorization" )
16+
1417class 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