关于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');
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容