Retrofit打印网络请求日志
用过Retrofit的朋友肯定知道使用Retrofit进行网络网络请求非常的方便简洁,但是要打印网络请求的日志还是要自己另想办法。昨天在网上找了一圈,发现要打印日志,大部分的帖子都是引入OkHttp3的日志库:
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
这个库使用起来很简单,在你创建OkHttpClient的时候增加拦截器即可
new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).readTimeout(15, TimeUnit.SECONDS).writeTimeout(15, TimeUnit.SECONDS).addInterceptor(new HttpLoggingInterceptor())// 在此处添加拦截器即可,默认日志级别为BASIC.build();
日志级别
日志的级别设置,日志级别包括:NONE(无日志),BASIC(基础日志),HEADERS(包含请求头),BODY(包含请求体)。默认的日志级别是BASIC,最大级别是BODY,会打印整个网络请求的所有信息,让我们来看一下效果:
// Request请求
--> POST http://app.buddha.net.cn/SzgServer/api/getRequestOrder http/1.1 // 请求行
Content-Type: application/x-www-form-urlencoded // 请求头
Content-Length: 9 // 请求头长度
test=test // 请求体
--> END POST (9-byte body)
// Response响应
<-- 200 OK http://app.buddha.net.cn/SzgServer/api/getRequestOrder (79ms)
Server: nginx/1.9.3
Date: Sat, 15 Jul 2017 04:00:29 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 38
Connection: keep-alive
Cache-Control: no-cache, must-revalidate
{"msg":"参数错误","status":"2005"} // 响应结果
<-- END HTTP (38-byte body)
困惑
以上就是日志级别为BODY所打印出的信息,可以看到非常的全面,包含了Http请求的所有信息,但是!但是!作者并不满足于这种,我想让打印的日志更加直观,就比如说我想把json数据格式化输出,就像这样:
{"msg": "参数错误","status": "2005"
}
由于HttpLoggingInterceptor使用的是安卓自带的Logger来打印日志,所以是不具备把json数据格式化的功能的。这难免会让我们在开发中感到困惑,因为大部分网络请求数据都为json格式,我们希望打印出来的json数据是排版过的可以吗?当然可以,作者决定自己做点小修改,其实也很简单,我们只需要把Logger替换成我们自己的日志框架不就好了吗?现在作者修改后的日志打印如下,作者使用的是KLog,读者可以自行替换:
[ (HttpLoggingInterceptor.java:139)#intercept ] --> POST http://app.buddha.net.cn/SzgServer/api/getRequestOrder http/1.1
[ (HttpLoggingInterceptor.java:146)#intercept ] Content-Type: application/json; charset=UTF-8
[ (HttpLoggingInterceptor.java:149)#intercept ] Content-Length: 25
[ (HttpLoggingInterceptor.java:176)#intercept ]
╔═══════════════════════════════════════════════════════════════════════════════════════
║ [ (HttpLoggingInterceptor.java:178)#intercept ]
║ {
║ "age": 1,
║ "name": "liuwei"
║ }
╚═══════════════════════════════════════════════════════════════════════════════════════
[ (HttpLoggingInterceptor.java:179)#intercept ] --> END POST (25-byte body)
[ (HttpLoggingInterceptor.java:201)#intercept ] <-- 200 OK http://app.buddha.net.cn/SzgServer/api/getRequestOrder (35ms)
[ (HttpLoggingInterceptor.java:208)#intercept ] Server: nginx/1.9.3
[ (HttpLoggingInterceptor.java:208)#intercept ] Date: Sat, 15 Jul 2017 04:17:43 GMT
[ (HttpLoggingInterceptor.java:208)#intercept ] Content-Type: application/json;charset=UTF-8
[ (HttpLoggingInterceptor.java:208)#intercept ] Content-Length: 38
[ (HttpLoggingInterceptor.java:208)#intercept ] Connection: keep-alive
[ (HttpLoggingInterceptor.java:208)#intercept ] Cache-Control: no-cache, must-revalidate
[ (HttpLoggingInterceptor.java:233)#intercept ]
╔═══════════════════════════════════════════════════════════════════════════════════════
║ [ (HttpLoggingInterceptor.java:234)#intercept ]
║ {
║ "msg": "参数错误",
║ "status": "2005"
║ }
╚═══════════════════════════════════════════════════════════════════════════════════════
[ (HttpLoggingInterceptor.java:237)#intercept ] <-- END HTTP (38-byte body)
别的都没变,更换了一下日志框架就让日志输出的这么爽,json结果和requestBody都排版出来了,一眼明了,同样还可以设置日志的级别和自定义修改,美滋滋
自定义的HttpLoggingInterceptor
最后附上修改后的HttpLoggingInterceptor,读者可以自定义日志输出,自己决定打印哪些不打印哪些,很方便:
import com.socks.library.KLog;import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.platform.Platform;
import okio.Buffer;
import okio.BufferedSource;import static okhttp3.internal.platform.Platform.INFO;/*** 什么都没做,只是把Logger替换成KLog,读者可自行替换日志框架*/
public final class HttpLoggingInterceptor 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 {void log(String message);/** A {@link Logger} defaults output appropriate for the current platform. */Logger DEFAULT = new Logger() {@Override public void log(String message) {Platform.get().log(INFO, message, null);}};}public HttpLoggingInterceptor() {}private volatile Level level = Level.NONE;/** Change the level at which this interceptor logs. */public HttpLoggingInterceptor 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();Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;if (!logHeaders && hasRequestBody) {requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";}KLog.d(requestStartMessage);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) {KLog.d("Content-Type: " + requestBody.contentType());}if (requestBody.contentLength() != -1) {KLog.d("Content-Length: " + requestBody.contentLength());}}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)) {KLog.d(name + ": " + headers.value(i));}}if (!logBody || !hasRequestBody) {KLog.d("--> END " + request.method());} else if (bodyEncoded(request.headers())) {KLog.d("--> END " + request.method() + " (encoded body omitted)");} else {Buffer buffer = new Buffer();requestBody.writeTo(buffer);Charset charset = UTF8;MediaType contentType = requestBody.contentType();if (contentType != null) {charset = contentType.charset(UTF8);}KLog.d("");if (isPlaintext(buffer)) {KLog.json(buffer.readString(charset));KLog.d("--> END " + request.method()+ " (" + requestBody.contentLength() + "-byte body)");} else {KLog.d("--> END " + request.method() + " (binary "+ requestBody.contentLength() + "-byte body omitted)");}}}long startNs = System.nanoTime();Response response;try {response = chain.proceed(request);} catch (Exception e) {KLog.d("<-- HTTP FAILED: " + e);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";KLog.d("<-- " + response.code() + ' ' + response.message() + ' '+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "+ bodySize + " body" : "") + ')');if (logHeaders) {Headers headers = response.headers();for (int i = 0, count = headers.size(); i < count; i++) {KLog.d(headers.name(i) + ": " + headers.value(i));}if (!logBody || !HttpHeaders.hasBody(response)) {KLog.d("<-- END HTTP");} else if (bodyEncoded(response.headers())) {KLog.d("<-- END HTTP (encoded body omitted)");} else {BufferedSource source = responseBody.source();source.request(Long.MAX_VALUE); // Buffer the entire body.Buffer buffer = source.buffer();Charset charset = UTF8;MediaType contentType = responseBody.contentType();if (contentType != null) {charset = contentType.charset(UTF8);}if (!isPlaintext(buffer)) {KLog.d("");KLog.d("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");return response;}if (contentLength != 0) {KLog.d("");KLog.json(buffer.clone().readString(charset));}KLog.d("<-- END HTTP (" + buffer.size() + "-byte body)");}}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 bodyEncoded(Headers headers) {String contentEncoding = headers.get("Content-Encoding");return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");}
}
Retrofit打印网络请求日志相关推荐
- Android 开发之Okhttp网络请求日志打印
这里写自定义目录标题 Android 开发之Okhttp 网络请求日志打印 OkHTTP网络日志打印 Android 开发之Okhttp 网络请求日志打印 网络请求是开发的日常工作内容之一,网络日志打 ...
- android studio放置在函数上面看_Android中用Kotlin协程和Retrofit进行网络请求和取消请求...
前面两篇文章介绍了协程的一些基本概念和基本知识,这篇则介绍在Android中如何使用协程配合Retrofit发起网络请求,同时介绍在使用协程时如何优雅的取消已经发起的网络请求. 需要文章中demo完整 ...
- Android中使用Kotlin协程(Coroutines)和Retrofit进行网络请求(二)之文件下载
写在前面 下载功能是非常常用的功能,今天我们要通过kotlin协程和retrofit来是实现文件下载的功能.retorfit本身可以将请求结果以InputStream的形式返回,拿到InputStre ...
- android搭建网络框架,Android 搭建MVP+Retrofit+RxJava网络请求框架(三)
上一篇中主要是将mvp+rxjava+retrofit进行了结合,本篇主要是对mvp框架的优化:建议先去看上一篇:Android 搭建MVP+Retrofit+RxJava网络请求框架(二) 针对vi ...
- 轻松搞定Retrofit不同网络请求方式的请求参数配置,及常用注解使用
<一>四种请求方式: GET 向服务器发起数据请求,获取信息.类似于数据库的select操作,只是查询,不会影响资源的内容. POST 向服务器发送数据,该请求会改变数据的种类等资源.类似 ...
- Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
目录 1.什么是MVP? 2.什么是Retrofit? 3.RxJava 4.实践 之前公司的项目用到了MVP+Retrofit+RxJava的框架进行网络请求,所以今天特此写一篇文章以做总结.相信很 ...
- Retrofit+RxJava网络请求失败,报HTTP 400 Bad Request,没有返回errorBody的信息
网络请求失败返回的结果肯定是到了onFaild()里面了,throwable获取的信息只有"HTTP 400 Bad Request",并没有返回postman上面的body信息. ...
- MVP+Retrofit+Rxjava网络请求购物车
//依赖 compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.squareup.okhttp3:okhttp:3.9.0' com ...
- 使用Axios拦截器打印前端请求日志和后端后返回日志
在main.ts引入 import axios from 'axios'; axios.defaults.baseURL = process.env.VUE_APP_SERVER;/*** axios ...
最新文章
- 关于GDPR的六大理解
- VTK:小部件之CheckerboardWidget
- oracle视图执行脚本,oracle 视图,函数,过程,触发器自动编译脚本
- 《深入浅出玩转FPGA》笔记
- smarty模板概念及应用场合
- 小程序导航组件navigator活学活用
- jvm感知docker容器参数
- React开发(283):控制文件不会被git追踪
- 【计算机网络】比较TCP与UDP
- 一休自评应聘:我是如何进入51CTO的?
- Android工程项目打包成SDK(jar或aar格式)
- mat opencv 修改roi_OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体...
- 电子学会2022年9月青少年软件编程(图形化)等级考试试卷(四级)答案解析
- 两种方法去除页眉页脚:基于OCR识别后的文本/基于图片切割
- Android TextView带背景图片和自定义边框
- N、XR、XD、DR”各代表什么意思
- 【MDT】MacBook Air 横评 MateBook 13
- java程序设计第四版张弛答案,2020学堂云Java编程概论——第一部分单元测试答案...
- Android闹钟APP
- WPF登录界面及程序主界面设计