关于OkHttp HttpLoggingInterceptor日志错乱问题

用过OkHttp的人都知道,使用HttpLoggingInterceptor可以定位接口请求日志,来排查问题,但是多个接口并发请求(或者说一个页面多个接口并发请求)时,打印的日志有时候是错乱,原因是 HttpLoggingInterceptor.Logger中的log(String message)是分段回调的。日志打印错乱导致定位问题极为不方便
修复这个问题也很简单,复制HttpLoggingInterceptor一份类修改。

修改版本:

implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'

修改如下:

public final class HttpLoggingInterceptor2 implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");

public enum Level {
    /** No logs. */
    NONE,
    /**
     * Logs request and response lines.
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1 (3-byte body)
     *
     * <-- 200 OK (22ms, 6-byte body)
     * }</pre>
     */
    BASIC,
    /**
     * Logs request and response lines and their respective headers.
     *
     * <p>Example:
     * <pre>{@code
     * --> 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
     * }</pre>
     */
    HEADERS,
    /**
     * Logs request and response lines and their respective headers and bodies (if present).
     *
     * <p>Example:
     * <pre>{@code
     * --> 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
     * }</pre>
     */
    BODY
}

public interface Logger {
    /**
     * 日志回调
     * @param message 返回一个完整的日志信息
     */
    void log(String message);
}
private static final String NEW_LINE="\n";

private  void callbackLog(StringBuilder sb) {
    logger.log(sb.toString());
    sb.setLength(0);
}

public HttpLoggingInterceptor2(Logger logger) {
    this.logger = logger;
}

private final Logger logger;

private volatile Level level = Level.NONE;

/** Change the level at which this interceptor logs. */
public HttpLoggingInterceptor2 setLevel(Level level) {
    if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
    this.level = level;
    return this;
}

public Level getLevel() {
    return level;
}

@Override public Response intercept(Chain chain) throws IOException {
    Level level = this.level;

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

    boolean logBody = level == Level.BODY;
    boolean logHeaders = logBody || level == Level.HEADERS;

    RequestBody requestBody = request.body();
    boolean hasRequestBody = requestBody != null;

    Connection connection = chain.connection();
    String requestStartMessage = "--> "
            + request.method()
            + ' ' + request.url()
            + (connection != null ? " " + connection.protocol() : "");
    if (!logHeaders && hasRequestBody) {
        requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
    }
    StringBuilder sb = new StringBuilder();
    sb.append(requestStartMessage).append(NEW_LINE);

    if (logHeaders) {
        if (hasRequestBody) {
            // Request body headers are only present when installed as a network interceptor. Force
            // them to be included (when available) so there values are known.
            if (requestBody.contentType() != null) {
                sb.append("Content-Type: " + requestBody.contentType()).append(NEW_LINE);
            }
            if (requestBody.contentLength() != -1) {
                sb.append("Content-Length: " + requestBody.contentLength()).append(NEW_LINE);
            }
        }

        Headers headers = request.headers();
        for (int i = 0, count = headers.size(); i < count; i++) {
            String name = headers.name(i);
            // Skip headers from the request body as they are explicitly logged above.
            if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
                sb.append(name + ": " + headers.value(i)).append(NEW_LINE);
            }
        }

        if (!logBody || !hasRequestBody) {
            sb.append("--> END " + request.method()).append(NEW_LINE);
        } else if (bodyHasUnknownEncoding(request.headers())) {
            sb.append("--> END " + request.method() + " (encoded body omitted)").append(NEW_LINE);
        } else {
            Buffer buffer = new Buffer();
            requestBody.writeTo(buffer);

            Charset charset = UTF8;
            MediaType contentType = requestBody.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF8);
            }

            sb.append("");
            if (isPlaintext(buffer)) {
                String s = JsonUtils.formatJson(buffer.readString(charset));
                sb.append(s).append(NEW_LINE);
                sb.append("--> END " + request.method()
                        + " (" + requestBody.contentLength() + "-byte body)").append(NEW_LINE);
            } else {
                sb.append("--> END " + request.method() + " (binary "
                        + requestBody.contentLength() + "-byte body omitted)").append(NEW_LINE);
            }
        }
    }

    long startNs = System.nanoTime();
    Response response;
    try {
        response = chain.proceed(request);
    } catch (Exception e) {
        sb.append("<-- HTTP FAILED: " + e).append(NEW_LINE);
        throw e;
    }
    long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

    ResponseBody responseBody = response.body();
    long contentLength = responseBody.contentLength();
    String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
    sb.append("<-- "
            + response.code()
            + (response.message().isEmpty() ? "" : ' ' + response.message())
            + ' ' + response.request().url()
            + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')').append(NEW_LINE);

    if (logHeaders) {
        Headers headers = response.headers();
        for (int i = 0, count = headers.size(); i < count; i++) {
            sb.append(headers.name(i) + ": " + headers.value(i)).append(NEW_LINE);
        }

        if (!logBody || !HttpHeaders.hasBody(response)) {
            sb.append("<-- END HTTP").append(NEW_LINE);;
            callbackLog(sb);
        } else if (bodyHasUnknownEncoding(response.headers())) {
            sb.append("<-- END HTTP (encoded body omitted)").append(NEW_LINE);
            callbackLog(sb);
        } else {
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();

            Long gzippedLength = null;
            if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
                gzippedLength = buffer.size();
                GzipSource gzippedResponseBody = null;
                try {
                    gzippedResponseBody = new GzipSource(buffer.clone());
                    buffer = new Buffer();
                    buffer.writeAll(gzippedResponseBody);
                } finally {
                    if (gzippedResponseBody != null) {
                        gzippedResponseBody.close();
                    }
                }
            }

            Charset charset = UTF8;
            MediaType contentType = responseBody.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF8);
            }

            if (!isPlaintext(buffer)) {
                sb.append("").append(NEW_LINE);
                sb.append("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)").append(NEW_LINE);
                callbackLog(sb);
                return response;
            }

            if (contentLength != 0) {
                sb.append("").append(NEW_LINE);
                String s = buffer.clone().readString(charset);
                sb.append(JsonUtils.formatJson(s)).append(NEW_LINE);
            }

            if (gzippedLength != null) {
                sb.append("<-- END HTTP (" + buffer.size() + "-byte, "
                        + gzippedLength + "-gzipped-byte body)").append(NEW_LINE);
            } else {
                sb.append("<-- END HTTP (" + buffer.size() + "-byte body)").append(NEW_LINE);
            }
            callbackLog(sb);
        }
    }

    return response;
}


/**
 * Returns true if the body in question probably contains human readable text. Uses a small sample
 * of code points to detect unicode control characters commonly used in binary file signatures.
 */
static boolean isPlaintext(Buffer buffer) {
    try {
        Buffer prefix = new Buffer();
        long byteCount = buffer.size() < 64 ? buffer.size() : 64;
        buffer.copyTo(prefix, 0, byteCount);
        for (int i = 0; i < 16; i++) {
            if (prefix.exhausted()) {
                break;
            }
            int codePoint = prefix.readUtf8CodePoint();
            if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                return false;
            }
        }
        return true;
    } catch (EOFException e) {
        return false; // Truncated UTF-8 sequence.
    }
}

private boolean bodyHasUnknownEncoding(Headers headers) {
    String contentEncoding = headers.get("Content-Encoding");
    return contentEncoding != null
            && !contentEncoding.equalsIgnoreCase("identity")
            && !contentEncoding.equalsIgnoreCase("gzip");
}

}

使用:

private static final class HttpLogger implements HttpLoggingInterceptor2.Logger {
    @Override
    public void log(String message) {
        com.orhanobut.logger.Logger.i(message);//   https://github.com/orhanobut/logger
    }
}


OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (isDebug){
HttpLoggingInterceptor2 logInterceptor = new HttpLoggingInterceptor2(new HttpLogger());
logInterceptor.setLevel(HttpLoggingInterceptor2.Level.BODY);
    builder.addNetworkInterceptor(logInterceptor); //添加网络日志拦截器
}

最后会发现一家人(日志)整整齐齐的打印出来,是不是很完美。

涉及的类:

public class JsonUtils {

    /**
     * 格式化json字符串
     *
     * @param jsonStr 需要格式化的json串
     * @return 格式化后的json串
     */
    public static String formatJson(String jsonStr) {
        if (null == jsonStr || "".equals(jsonStr)) return "";
        StringBuilder sb = new StringBuilder();
        char last = '\0';
        char current = '\0';
        int indent = 0;
        for (int i = 0; i < jsonStr.length(); i++) {
            last = current;
            current = jsonStr.charAt(i);
            //遇到{ [换行,且下一行缩进
            switch (current) {
                case '{':
                case '[':
                    sb.append(current);
                    sb.append('\n');
                    indent++;
                    addIndentBlank(sb, indent);
                    break;
                //遇到} ]换行,当前行缩进
                case '}':
                case ']':
                    sb.append('\n');
                    indent--;
                    addIndentBlank(sb, indent);
                    sb.append(current);
                    break;
                //遇到,换行
                case ',':
                    sb.append(current);
                    if (last != '\\') {
                        sb.append('\n');
                        addIndentBlank(sb, indent);
                    }
                    break;
                default:
                    sb.append(current);
            }
        }
        return sb.toString();
    }

    /**
     * 添加space
     *
     * @param sb
     * @param indent
     */
    private static void addIndentBlank(StringBuilder sb, int indent) {
        for (int i = 0; i < indent; i++) {
            sb.append('\t');
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容