Skip to content

Instantly share code, notes, and snippets.

@erickok
Last active November 20, 2025 13:00
Show Gist options
  • Select an option

  • Save erickok/e371a9e0b9e702ed441d to your computer and use it in GitHub Desktop.

Select an option

Save erickok/e371a9e0b9e702ed441d to your computer and use it in GitHub Desktop.
Simple logging interceptor for OkHttp that logs full request headers and response headers + body (useful for use with Retrofit 2 where logging was removed)
if (BuildConfig.DEBUG) {
httpclient.interceptors().add(new LoggingInterceptor());
}
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d("OkHttp", String.format("--> Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
Buffer requestBuffer = new Buffer();
request.body().writeTo(requestBuffer);
Log.d("OkHttp", requestBuffer.readUtf8());
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.d("OkHttp", String.format("<-- Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
MediaType contentType = response.body().contentType();
BufferedSource buffer = Okio.buffer(new GzipSource(response.body().source()));
String content = buffer.readUtf8();
Log.d("OkHttp", content);
ResponseBody wrappedBody = ResponseBody.create(contentType, content);
return response.newBuilder().removeHeader("Content-Encoding").body(wrappedBody).build();
}
}
if (BuildConfig.DEBUG) {
httpclient.interceptors().add(new LoggingInterceptor());
}
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d("OkHttp", String.format("--> Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
Buffer requestBuffer = new Buffer();
request.body().writeTo(requestBuffer);
Log.d("OkHttp", requestBuffer.readUtf8());
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.d("OkHttp", String.format("<-- Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
MediaType contentType = response.body().contentType();
String content = response.body().string();
Log.d("OkHttp", content);
ResponseBody wrappedBody = ResponseBody.create(contentType, content);
return response.newBuilder().body(wrappedBody).build();
}
}
@bradroid
Copy link

bradroid commented Dec 7, 2015

Hi, one question: why do you create new response at the end of the block with

ResponseBody wrappedBody = ResponseBody.create(contentType, content);
        return response.newBuilder().body(wrappedBody).build();

can't you just return the response object you got in line 17?

return response;

@Nxele
Copy link

Nxele commented Jan 31, 2019

Hi erickok

i need help I'm new to interceptors how can i execute this interceptor so it addHeader

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    if (BuildConfig.DEBUG) {
        httpClient.interceptors().add(new Interceptor() {

            @Override
            public Response intercept(Interceptor.Chain chain) throws IOException {
                Request original = chain.request();
                Toast.makeText(getApplicationContext(),"Please work now", Toast.LENGTH_LONG).show();
                Request request = original.newBuilder()
                        //.header("User-Agent", "AirbusMapBox")
                        .addHeader("Authorization",Airbus_key)
                        .method(original.method(), original.body())
                        .build();
                return chain.proceed(request);
            }
        });

        OkHttpClient client = httpClient.build();

        Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://view.geoapi-airbusds.com")
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();
    }

@behelit
Copy link

behelit commented Dec 9, 2019

This doesn't seem to log timeout's or exceptions

@rfermontero
Copy link

Hi, one question: why do you create new response at the end of the block with

ResponseBody wrappedBody = ResponseBody.create(contentType, content);
        return response.newBuilder().body(wrappedBody).build();

can't you just return the response object you got in line 17?

return response;

I think it's because the stream is closed when prints body

@flocsy
Copy link

flocsy commented Jul 12, 2021

Here's a merged version that can log gzip and non-gzip responses as well:

public class ResponseLoggingInterceptor implements Interceptor {
    
        public static final String CONTENT_ENCODING = "content-encoding";
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
    
            String content = null;
            ResponseBody body = response.body();
            if (body != null) {
                MediaType contentType = body.contentType();
                String contentEncoding = response.header(CONTENT_ENCODING);
                if ("gzip".equals(contentEncoding)) {
                    BufferedSource buffer = Okio.buffer(new GzipSource(body.source()));
                    content = buffer.readUtf8();
                    ResponseBody wrappedBody = ResponseBody.create(contentType, content);
                    response = response.newBuilder().removeHeader(CONTENT_ENCODING).body(wrappedBody).build();
                } else {
                    content = body.string();
                    ResponseBody wrappedBody = ResponseBody.create(contentType, content);
                    response = response.newBuilder().body(wrappedBody).build();
                }
            }
            String protocol = response.protocol().name().replaceFirst("_", "/");
            protocol = protocol.replace('_', '.');
            String httpLine = "" + protocol + ' ' + response.code();
            Log.v("OkHttp", String.format("%s\n%s\n\n%s", httpLine, response.headers(), content));
    
            return response;
        }
    }

@lukeduncan-scot
Copy link

Spot on for me @flocsy thank you.

@dimaslanjaka
Copy link

dimaslanjaka commented Jan 13, 2024

I modify this code with okhttp v4 latest 2023. Please remove non project code, i was pasted without removing my util code.

implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:okhttp-urlconnection")
implementation("com.squareup.okhttp3:logging-interceptor")
import im3.FileManager;
import okhttp3.*;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.GzipSource;
import okio.Okio;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


// source https://gist.github.com/erickok/e371a9e0b9e702ed441d
public static class OkHttpGzippedLoggingInterceptor implements Interceptor {
    /**
     * get log file curl result
     *
     * @param url the url
     */
    public static String getLogFile(URI url) {
        return FileManager.getDir(new File("intercepts", "/" + url.getHost() + "/" + url.getPath()).toString()) + "/http.log";
    }

    public static String getLogFile(String urlstr) throws URISyntaxException {
        URI url = new URI(urlstr);
        return FileManager.getDir(new File("intercepts", "/" + url.getHost() + "/" + url.getPath()).toString()) + "/http.log";
    }

    @Override
    public @NotNull Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        String reqMsg = String.format("--> Sending request %s %s on %s%n%s", request.method(), request.url(), chain.connection(), request.headers());
        System.out.println(reqMsg);

        BufferedSink requestBuffer = Okio.buffer(Okio.sink(new File(".cache", "okhttp-gzipped.log")));
        //            Buffer requestBuffer = new Buffer();
        RequestBody requestBody = request.body();
        if (requestBody != null) requestBody.writeTo(requestBuffer);
        String reqBody = requestBuffer.getBuffer().readUtf8();
        System.out.println(reqBody + "\n");

        Response response = chain.proceed(request);

        String filePath;
        try {
            URI url = new URI(request.url().toString());
            filePath = getLogFile(url);
        } catch (URISyntaxException e) {
            filePath = "";
        }

        long t2 = System.nanoTime();
        String resMsg = String.format("<-- Received response for %s %s in %.1fms%n%s", response.request().url(), response.protocol().toString().toUpperCase(Locale.ROOT), (t2 - t1) / 1e6 d, response.headers());
        System.out.println(resMsg);

        ResponseBody responseBody = response.body();
        MediaType contentType = null;
        BufferedSource buffer;
        String content = "NO CONTENT IN RESPONSE";
        if (responseBody != null) {
            contentType = responseBody.contentType();
            buffer = Okio.buffer(new GzipSource(responseBody.source()));
            content = buffer.readUtf8();
            System.out.println(content);
        }

        if (!filePath.isEmpty()) {
            FileManager.save(filePath, String.format("%s%n%s%n%s%n%s%n", reqMsg, reqBody, resMsg, content));
        }

        ResponseBody wrappedBody = ResponseBody.create(contentType, content);
        return response.newBuilder().removeHeader("Content-Encoding").body(wrappedBody).build();
    }
}

// source https://gist.github.com/erickok/e371a9e0b9e702ed441d
public static class OkhttpLoggingInterceptor implements Interceptor {
    @Override
    public @NotNull Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        String reqMsg = String.format("--> Sending request %s %s on %s%n%s", request.method(), request.url(), chain.connection(), request.headers());
        System.out.println(reqMsg);

        BufferedSink requestBuffer = Okio.buffer(Okio.sink(new File(".cache", "okhttp-gzipped.log")));
        //            Buffer requestBuffer = new Buffer();
        RequestBody requestBody = request.body();
        if (requestBody != null) requestBody.writeTo(requestBuffer);
        String reqBody = requestBuffer.getBuffer().readUtf8();
        System.out.println(reqBody + "\n");

        Response response = chain.proceed(request);

        String filePath;
        try {
            URI url = new URI(request.url().toString());
            filePath = OkHttpGzippedLoggingInterceptor.getLogFile(url);
        } catch (URISyntaxException e) {
            filePath = "";
        }

        long t2 = System.nanoTime();
        String resMsg = String.format("<-- Received response for %s %s in %.1fms%n%s", response.request().url(), response.protocol().toString().toUpperCase(Locale.ROOT), (t2 - t1) / 1e6 d, response.headers());
        System.out.println(resMsg);

        ResponseBody responseBody = response.body();
        MediaType contentType = null;
        String content = "NO CONTENT IN RESPONSE";
        if (responseBody != null) {
            contentType = responseBody.contentType();
            content = responseBody.string();
            System.out.println(content);
        }

        if (!filePath.isEmpty()) {
            FileManager.save(filePath, String.format("%s%n%s%n%s%n%s%n", reqMsg, reqBody, resMsg, content));
        }

        ResponseBody wrappedBody = ResponseBody.create(contentType, content);
        return response.newBuilder().removeHeader("Content-Encoding").body(wrappedBody).build();
    }
}

the log result in file and console
image

@Zhuinden
Copy link

Nah, time to just use hte one that comes with okhttp3

import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.internal.http.promisesBody
import okhttp3.internal.platform.Platform
import okio.Buffer
import okio.GzipSource
import java.io.EOFException
import java.io.IOException
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets.UTF_8
import java.util.TreeSet
import java.util.concurrent.TimeUnit
import kotlin.collections.plusAssign
import kotlin.text.equals
import kotlin.text.plus

class CustomHttpLoggingInterceptor @JvmOverloads constructor(
    private val logger: Logger = Logger.DEFAULT
) : Interceptor {

    @Volatile private var headersToRedact = emptySet<String>()

    @set:JvmName("level")
    @Volatile var level = Level.NONE

    enum class Level {
        /** No logs. */
        NONE,

        /**
         * Logs request and response lines.
         *
         * Example:
         * ```
         * --> POST /greeting http/1.1 (3-byte body)
         *
         * <-- 200 OK (22ms, 6-byte body)
         * ```
         */
        BASIC,

        /**
         * Logs request and response lines and their respective headers.
         *
         * Example:
         * ```
         * --> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         * --> END POST
         *
         * <-- 200 OK (22ms)
         * Content-Type: plain/text
         * Content-Length: 6
         * <-- END HTTP
         * ```
         */
        HEADERS,

        /**
         * Logs request and response lines and their respective headers and bodies (if present).
         *
         * Example:
         * ```
         * --> POST /greeting http/1.1
         * Host: example.com
         * Content-Type: plain/text
         * Content-Length: 3
         *
         * Hi?
         * --> END POST
         *
         * <-- 200 OK (22ms)
         * Content-Type: plain/text
         * Content-Length: 6
         *
         * Hello!
         * <-- END HTTP
         * ```
         */
        BODY
    }

    fun interface Logger {
        fun log(message: String)

        companion object {
            /** A [Logger] defaults output appropriate for the current platform. */
            @JvmField
            val DEFAULT: Logger = DefaultLogger()
            private class DefaultLogger : Logger {
                override fun log(message: String) {
                    val strings = message.chunked(250)

                    strings.forEach {
                        Platform.get().log(it)
                    }
                }
            }
        }
    }

    fun redactHeader(name: String) {
        val newHeadersToRedact = TreeSet(String.CASE_INSENSITIVE_ORDER)
        newHeadersToRedact += headersToRedact
        newHeadersToRedact += name
        headersToRedact = newHeadersToRedact
    }

    /**
     * Sets the level and returns this.
     *
     * This was deprecated in OkHttp 4.0 in favor of the [level] val. In OkHttp 4.3 it is
     * un-deprecated because Java callers can't chain when assigning Kotlin vals. (The getter remains
     * deprecated).
     */
    fun setLevel(level: Level) = apply {
        this.level = level
    }

    @JvmName("-deprecated_level")
    @Deprecated(
        message = "moved to var",
        replaceWith = ReplaceWith(expression = "level"),
        level = DeprecationLevel.ERROR
    )
    fun getLevel(): Level = level

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val level = this.level

        val request = chain.request()
        if (level == Level.NONE) {
            return chain.proceed(request)
        }

        val logBody = level == Level.BODY
        val logHeaders = logBody || level == Level.HEADERS

        val requestBody = request.body

        val connection = chain.connection()
        var requestStartMessage =
            ("--> ${request.method} ${request.url}${if (connection != null) " " + connection.protocol() else ""}")
        if (!logHeaders && requestBody != null) {
            requestStartMessage += " (${requestBody.contentLength()}-byte body)"
        }
        logger.log(requestStartMessage)

        if (logHeaders) {
            val headers = request.headers

            if (requestBody != null) {
                // Request body headers are only present when installed as a network interceptor. When not
                // already present, force them to be included (if available) so their values are known.
                requestBody.contentType()?.let {
                    if (headers["Content-Type"] == null) {
                        logger.log("Content-Type: $it")
                    }
                }
                if (requestBody.contentLength() != -1L) {
                    if (headers["Content-Length"] == null) {
                        logger.log("Content-Length: ${requestBody.contentLength()}")
                    }
                }
            }

            for (i in 0 until headers.size) {
                logHeader(headers, i)
            }

            if (!logBody || requestBody == null) {
                logger.log("--> END ${request.method}")
            } else if (bodyHasUnknownEncoding(request.headers)) {
                logger.log("--> END ${request.method} (encoded body omitted)")
            } else if (requestBody.isDuplex()) {
                logger.log("--> END ${request.method} (duplex request body omitted)")
            } else if (requestBody.isOneShot()) {
                logger.log("--> END ${request.method} (one-shot body omitted)")
            } else {
                val buffer = Buffer()
                requestBody.writeTo(buffer)

                val contentType = requestBody.contentType()
                val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8

                logger.log("")
                if (buffer.isProbablyUtf8()) {
                    logger.log(buffer.readString(charset))
                    logger.log("--> END ${request.method} (${requestBody.contentLength()}-byte body)")
                } else {
                    logger.log(
                        "--> END ${request.method} (binary ${requestBody.contentLength()}-byte body omitted)")
                }
            }
        }

        val startNs = System.nanoTime()
        val response: Response
        try {
            response = chain.proceed(request)
        } catch (e: Exception) {
            logger.log("<-- HTTP FAILED: $e")
            throw e
        }

        val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)

        val responseBody = response.body!!
        val contentLength = responseBody.contentLength()
        val bodySize = if (contentLength != -1L) "$contentLength-byte" else "unknown-length"
        logger.log(
            "<-- ${response.code}${if (response.message.isEmpty()) "" else ' ' + response.message} ${response.request.url} (${tookMs}ms${if (!logHeaders) ", $bodySize body" else ""})")

        if (logHeaders) {
            val headers = response.headers
            for (i in 0 until headers.size) {
                logHeader(headers, i)
            }

            if (!logBody || !response.promisesBody()) {
                logger.log("<-- END HTTP")
            } else if (bodyHasUnknownEncoding(response.headers)) {
                logger.log("<-- END HTTP (encoded body omitted)")
            } else {
                val source = responseBody.source()
                source.request(Long.MAX_VALUE) // Buffer the entire body.
                var buffer = source.buffer

                var gzippedLength: Long? = null
                if ("gzip".equals(headers["Content-Encoding"], ignoreCase = true)) {
                    gzippedLength = buffer.size
                    GzipSource(buffer.clone()).use { gzippedResponseBody ->
                        buffer = Buffer()
                        buffer.writeAll(gzippedResponseBody)
                    }
                }

                val contentType = responseBody.contentType()
                val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8

                if (!buffer.isProbablyUtf8()) {
                    logger.log("")
                    logger.log("<-- END HTTP (binary ${buffer.size}-byte body omitted)")
                    return response
                }

                if (contentLength != 0L) {
                    logger.log("")
                    logger.log(buffer.clone().readString(charset))
                }

                if (gzippedLength != null) {
                    logger.log("<-- END HTTP (${buffer.size}-byte, $gzippedLength-gzipped-byte body)")
                } else {
                    logger.log("<-- END HTTP (${buffer.size}-byte body)")
                }
            }
        }

        return response
    }

    private fun logHeader(headers: Headers, i: Int) {
        val value = if (headers.name(i) in headersToRedact) "██" else headers.value(i)
        logger.log(headers.name(i) + ": " + value)
    }

    private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
        val contentEncoding = headers["Content-Encoding"] ?: return false
        return !contentEncoding.equals("identity", ignoreCase = true) &&
            !contentEncoding.equals("gzip", ignoreCase = true)
    }
}

internal fun Buffer.isProbablyUtf8(): Boolean {
    try {
        val prefix = Buffer()
        val byteCount = size.coerceAtMost(64)
        copyTo(prefix, 0, byteCount)
        for (i in 0 until 16) {
            if (prefix.exhausted()) {
                break
            }
            val codePoint = prefix.readUtf8CodePoint()
            if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                return false
            }
        }
        return true
    } catch (_: EOFException) {
        return false // Truncated UTF-8 sequence.
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment