From 7e2d7661cd01039ef49aa9c7693a37a49094d1bb Mon Sep 17 00:00:00 2001 From: Zweihui <616505546@qq.com> Date: Wed, 31 Jan 2018 17:00:45 +0800 Subject: [PATCH] improve http print log --- .../com/jess/arms/http/FormatPrinter.java | 236 ++++++++++++++++++ .../jess/arms/http/RequestInterceptor.java | 157 ++++++------ 2 files changed, 322 insertions(+), 71 deletions(-) create mode 100644 arms/src/main/java/com/jess/arms/http/FormatPrinter.java diff --git a/arms/src/main/java/com/jess/arms/http/FormatPrinter.java b/arms/src/main/java/com/jess/arms/http/FormatPrinter.java new file mode 100644 index 0000000..6c0c2fa --- /dev/null +++ b/arms/src/main/java/com/jess/arms/http/FormatPrinter.java @@ -0,0 +1,236 @@ +/* + * Copyright 2018 JessYan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jess.arms.http; + +import android.text.TextUtils; + +import java.util.List; + +import okhttp3.Request; +import timber.log.Timber; + +/** + * ================================================ + * 对 OkHttp 的请求和响应信息进行更规范和清晰的打印 + *

+ * Created by JessYan on 25/01/2018 14:51 + * Contact me + * Follow me + * ================================================ + */ + +public class FormatPrinter { + private static final String TAG = "ArmsHttpLog"; + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private static final String DOUBLE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR; + + private static final String[] OMITTED_RESPONSE = {LINE_SEPARATOR, "Omitted response body"}; + private static final String[] OMITTED_REQUEST = {LINE_SEPARATOR, "Omitted request body"}; + + private static final String N = "\n"; + private static final String T = "\t"; + private static final String REQUEST_UP_LINE = "┌────── Request ────────────────────────────────────────────────────────────────────────"; + private static final String END_LINE = "└───────────────────────────────────────────────────────────────────────────────────────"; + private static final String RESPONSE_UP_LINE = "┌────── Response ───────────────────────────────────────────────────────────────────────"; + private static final String BODY_TAG = "Body:"; + private static final String URL_TAG = "URL: "; + private static final String METHOD_TAG = "Method: @"; + private static final String HEADERS_TAG = "Headers:"; + private static final String STATUS_CODE_TAG = "Status Code: "; + private static final String RECEIVED_TAG = "Received in: "; + private static final String CORNER_UP = "┌ "; + private static final String CORNER_BOTTOM = "└ "; + private static final String CENTER_LINE = "├ "; + private static final String DEFAULT_LINE = "│ "; + + private FormatPrinter() { + throw new UnsupportedOperationException("you can't instantiate me!"); + } + + private static boolean isEmpty(String line) { + return TextUtils.isEmpty(line) || N.equals(line) || T.equals(line) || TextUtils.isEmpty(line.trim()); + } + + /** + * 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 可以解析的情况 + * + * @param request + * @param bodyString + */ + static void printJsonRequest(Request request, String bodyString) { + final String requestBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString; + final String tag = getTag(true); + + Timber.tag(tag).i(REQUEST_UP_LINE); + logLines(tag, new String[]{URL_TAG + request.url()}, false); + logLines(tag, getRequest(request), true); + logLines(tag, requestBody.split(LINE_SEPARATOR), true); + Timber.tag(tag).i(END_LINE); + } + + /** + * 打印网络请求信息, 当网络请求时 {{@link okhttp3.RequestBody}} 为 {@code null} 或不可解析的情况 + * + * @param request + */ + static void printFileRequest(Request request) { + final String tag = getTag(true); + + Timber.tag(tag).i(REQUEST_UP_LINE); + logLines(tag, new String[]{URL_TAG + request.url()}, false); + logLines(tag, getRequest(request), true); + logLines(tag, OMITTED_REQUEST, true); + Timber.tag(tag).i(END_LINE); + } + + /** + * 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 可以解析的情况 + * + * @param chainMs + * @param isSuccessful + * @param code + * @param headers + * @param bodyString + * @param segments + * @param message + * @param responseUrl + */ + static void printJsonResponse(long chainMs, boolean isSuccessful, + int code, String headers, String bodyString, List segments, String message, final String responseUrl) { + + final String responseBody = LINE_SEPARATOR + BODY_TAG + LINE_SEPARATOR + bodyString; + final String tag = getTag(false); + final String[] urlLine = {URL_TAG + responseUrl, N}; + + Timber.tag(tag).i(RESPONSE_UP_LINE); + logLines(tag, urlLine, true); + logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true); + logLines(tag, responseBody.split(LINE_SEPARATOR), true); + Timber.tag(tag).i(END_LINE); + } + + /** + * 打印网络响应信息, 当网络响应时 {{@link okhttp3.ResponseBody}} 为 {@code null} 或不可解析的情况 + * + * @param chainMs + * @param isSuccessful + * @param code + * @param headers + * @param segments + * @param message + * @param responseUrl + */ + static void printFileResponse(long chainMs, boolean isSuccessful, + int code, String headers, List segments, String message, final String responseUrl) { + final String tag = getTag(false); + final String[] urlLine = {URL_TAG + responseUrl, N}; + + Timber.tag(tag).i(RESPONSE_UP_LINE); + logLines(tag, urlLine, true); + logLines(tag, getResponse(headers, chainMs, code, isSuccessful, segments, message), true); + logLines(tag, OMITTED_RESPONSE, true); + Timber.tag(tag).i(END_LINE); + } + + + /** + * 对 {@code lines} 中的信息进行逐行打印 + * + * @param tag + * @param lines + * @param withLineSize 为 {@code true} 时, 每行的信息长度不会超过110, 超过则自动换行 + */ + private static void logLines(String tag, String[] lines, boolean withLineSize) { + for (String line : lines) { + int lineLength = line.length(); + int MAX_LONG_SIZE = withLineSize ? 110 : lineLength; + for (int i = 0; i <= lineLength / MAX_LONG_SIZE; i++) { + int start = i * MAX_LONG_SIZE; + int end = (i + 1) * MAX_LONG_SIZE; + end = end > line.length() ? line.length() : end; + Timber.tag(tag).i(DEFAULT_LINE + line.substring(start, end)); + } + } + } + + + private static String[] getRequest(Request request) { + String log; + String header = request.headers().toString(); + log = METHOD_TAG + request.method() + DOUBLE_SEPARATOR + + (isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR + dotHeaders(header)); + return log.split(LINE_SEPARATOR); + } + + private static String[] getResponse(String header, long tookMs, int code, boolean isSuccessful, + List segments, String message) { + String log; + String segmentString = slashSegments(segments); + log = ((!TextUtils.isEmpty(segmentString) ? segmentString + " - " : "") + "is success : " + + isSuccessful + " - " + RECEIVED_TAG + tookMs + "ms" + DOUBLE_SEPARATOR + STATUS_CODE_TAG + + code + " / " + message + DOUBLE_SEPARATOR + (isEmpty(header) ? "" : HEADERS_TAG + LINE_SEPARATOR + + dotHeaders(header))); + return log.split(LINE_SEPARATOR); + } + + private static String slashSegments(List segments) { + StringBuilder segmentString = new StringBuilder(); + for (String segment : segments) { + segmentString.append("/").append(segment); + } + return segmentString.toString(); + } + + /** + * 对 {@code header} 按规定的格式进行处理 + * + * @param header + * @return + */ + private static String dotHeaders(String header) { + String[] headers = header.split(LINE_SEPARATOR); + StringBuilder builder = new StringBuilder(); + String tag = "─ "; + if (headers.length > 1) { + for (int i = 0; i < headers.length; i++) { + if (i == 0) { + tag = CORNER_UP; + } else if (i == headers.length - 1) { + tag = CORNER_BOTTOM; + } else { + tag = CENTER_LINE; + } + builder.append(tag).append(headers[i]).append("\n"); + } + } else { + for (String item : headers) { + builder.append(tag).append(item).append("\n"); + } + } + return builder.toString(); + } + + + private static String getTag(boolean isRequest) { + if (isRequest) { + return TAG + "-Request"; + } else { + return TAG + "-Response"; + } + } + +} diff --git a/arms/src/main/java/com/jess/arms/http/RequestInterceptor.java b/arms/src/main/java/com/jess/arms/http/RequestInterceptor.java index eeb5625..0d55e78 100644 --- a/arms/src/main/java/com/jess/arms/http/RequestInterceptor.java +++ b/arms/src/main/java/com/jess/arms/http/RequestInterceptor.java @@ -25,6 +25,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -79,12 +80,12 @@ public Response intercept(Chain chain) throws IOException { boolean logRequest = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.REQUEST); if (logRequest) { - boolean hasRequestBody = request.body() != null; //打印请求信息 - Timber.tag(getTag(request, "Request_Info")).w("Params : 「 %s 」%nConnection : 「 %s 」%nHeaders : %n「 %s 」" - , hasRequestBody ? parseParams(request.newBuilder().build().body()) : "Null" - , chain.connection() - , request.headers()); + if (request.body() != null && isParseable(request.body().contentType())) { + FormatPrinter.printJsonRequest(request, parseParams(request)); + } else { + FormatPrinter.printFileRequest(request); + } } boolean logResponse = printLevel == Level.ALL || (printLevel != Level.NONE && printLevel == Level.RESPONSE); @@ -99,15 +100,34 @@ public Response intercept(Chain chain) throws IOException { } long t2 = logResponse ? System.nanoTime() : 0; - if (logResponse) { - String bodySize = originalResponse.body().contentLength() != -1 ? originalResponse.body().contentLength() + "-byte" : "unknown-length"; - //打印响应时间以及响应头 - Timber.tag(getTag(request, "Response_Info")).w("Received response in [ %d-ms ] , [ %s ]%n%s" - , TimeUnit.NANOSECONDS.toMillis(t2 - t1), bodySize, originalResponse.headers()); - } + ResponseBody responseBody = originalResponse.body(); //打印响应结果 - String bodyString = printResult(request, originalResponse.newBuilder().build(), logResponse); + String bodyString = null; + if (responseBody != null && isParseable(responseBody.contentType())) { + bodyString = printResult(request, originalResponse, logResponse); + } + + if (logResponse) { + final List segmentList = request.url().encodedPathSegments(); + final String header = originalResponse.headers().toString(); + final int code = originalResponse.code(); + final boolean isSuccessful = originalResponse.isSuccessful(); + final String message = originalResponse.message(); + final String url = originalResponse.request().url().toString(); + + if (responseBody != null && isParseable(responseBody.contentType())) { + FormatPrinter.printJsonResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1), + isSuccessful, code, header, + isJson(responseBody.contentType()) ? + CharacterHandler.jsonFormat(bodyString) : isXml(responseBody.contentType()) ? + CharacterHandler.xmlFormat(bodyString) : bodyString, segmentList, message, url); + } else { + FormatPrinter.printFileResponse(TimeUnit.NANOSECONDS.toMillis(t2 - t1), + isSuccessful, code, header, segmentList, message, url); + } + + } if (mHandler != null)//这里可以比客户端提前一步拿到服务器返回的结果,可以做一些操作,比如token超时,重新获取 return mHandler.onHttpResultResponse(bodyString, chain, originalResponse); @@ -126,47 +146,29 @@ public Response intercept(Chain chain) throws IOException { */ @Nullable private String printResult(Request request, Response response, boolean logResponse) throws IOException { - //读取服务器返回的结果 - ResponseBody responseBody = response.body(); - String bodyString = null; - if (isParseable(responseBody.contentType())) { - try { - BufferedSource source = responseBody.source(); - source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.buffer(); - - //获取content的压缩类型 - String encoding = response - .headers() - .get("Content-Encoding"); - - Buffer clone = buffer.clone(); - - - //解析response content - bodyString = parseContent(responseBody, encoding, clone); - } catch (IOException e) { - e.printStackTrace(); - } - if (logResponse) { - Timber.tag(getTag(request, "Response_Result")).w(isJson(responseBody.contentType()) ? - CharacterHandler.jsonFormat(bodyString) : isXml(responseBody.contentType()) ? - CharacterHandler.xmlFormat(bodyString) : bodyString); - } - - } else { - if (logResponse) { - Timber.tag(getTag(request, "Response_Result")).w("This result isn't parsed"); - } + try { + //读取服务器返回的结果 + ResponseBody responseBody = response.newBuilder().build().body(); + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.buffer(); + + //获取content的压缩类型 + String encoding = response + .headers() + .get("Content-Encoding"); + + Buffer clone = buffer.clone(); + + //解析response content + return parseContent(responseBody, encoding, clone); + } catch (IOException e) { + e.printStackTrace(); + return "{\"error\": \"" + e.getMessage() + "\"}"; } - return bodyString; } - private String getTag(Request request, String tag) { - return String.format(" [%s] 「 %s 」>>> %s", request.method(), request.url().toString(), tag); - } - /** * 解析服务器响应的内容 @@ -194,27 +196,26 @@ private String parseContent(ResponseBody responseBody, String encoding, Buffer c /** * 解析请求服务器的请求参数 * - * @param body + * @param request * @return * @throws UnsupportedEncodingException */ - public static String parseParams(RequestBody body) throws UnsupportedEncodingException { - if (isParseable(body.contentType())) { - try { - Buffer requestbuffer = new Buffer(); - body.writeTo(requestbuffer); - Charset charset = Charset.forName("UTF-8"); - MediaType contentType = body.contentType(); - if (contentType != null) { - charset = contentType.charset(charset); - } - return URLDecoder.decode(requestbuffer.readString(charset), convertCharset(charset)); - - } catch (IOException e) { - e.printStackTrace(); + public static String parseParams(Request request) throws UnsupportedEncodingException { + try { + RequestBody body = request.newBuilder().build().body(); + if (body == null) return ""; + Buffer requestbuffer = new Buffer(); + body.writeTo(requestbuffer); + Charset charset = Charset.forName("UTF-8"); + MediaType contentType = body.contentType(); + if (contentType != null) { + charset = contentType.charset(charset); } + return CharacterHandler.jsonFormat(URLDecoder.decode(requestbuffer.readString(charset), convertCharset(charset))); + } catch (IOException e) { + e.printStackTrace(); + return "{\"error\": \"" + e.getMessage() + "\"}"; } - return "This params isn't parsed"; } /** @@ -224,26 +225,39 @@ public static String parseParams(RequestBody body) throws UnsupportedEncodingExc * @return */ public static boolean isParseable(MediaType mediaType) { - if (mediaType == null) return false; - return mediaType.toString().toLowerCase().contains("text") + return isText(mediaType) || isPlain(mediaType) || isJson(mediaType) || isForm(mediaType) || isHtml(mediaType) || isXml(mediaType); } + public static boolean isText(MediaType mediaType) { + if (mediaType == null || mediaType.type() == null) return false; + return mediaType.type().equals("text"); + } + + public static boolean isPlain(MediaType mediaType) { + if (mediaType == null || mediaType.subtype() == null) return false; + return mediaType.subtype().toLowerCase().contains("plain"); + } + public static boolean isJson(MediaType mediaType) { - return mediaType.toString().toLowerCase().contains("json"); + if (mediaType == null || mediaType.subtype() == null) return false; + return mediaType.subtype().toLowerCase().contains("json"); } public static boolean isXml(MediaType mediaType) { - return mediaType.toString().toLowerCase().contains("xml"); + if (mediaType == null || mediaType.subtype() == null) return false; + return mediaType.subtype().toLowerCase().contains("xml"); } public static boolean isHtml(MediaType mediaType) { - return mediaType.toString().toLowerCase().contains("html"); + if (mediaType == null || mediaType.subtype() == null) return false; + return mediaType.subtype().toLowerCase().contains("html"); } public static boolean isForm(MediaType mediaType) { - return mediaType.toString().toLowerCase().contains("x-www-form-urlencoded"); + if (mediaType == null || mediaType.subtype() == null) return false; + return mediaType.subtype().toLowerCase().contains("x-www-form-urlencoded"); } public static String convertCharset(Charset charset) { @@ -253,4 +267,5 @@ public static String convertCharset(Charset charset) { return s; return s.substring(i + 1, s.length() - 1); } + }