建立Okhttp自定義Log

原文連接:建立OkHttp自定義Loghtml

背景

本文重點講解如何在使用OkHttp做爲網絡請求框架的前提下,如何自定義一個適合本身項目的Http Log,從而提高網絡Api開發、調試效率。java

Http協議

只有對Http協議有基本的瞭解,才能更好的調試網絡接口。git

一個故事

男女主一次偶然的機會,開始信件傳情,但他們的信件並非直接寄給對方,而是先寄到某個地點A,由地點A的主人轉發。(好吧,這是《北京趕上西雅圖之不二情書》的情節)。咱們來看男主發信的過程:github

  1. 先購買有效郵票;
  2. 填寫信件的收件人、收件地址等信息;
  3. 把信件放到信封裏寄出去。

顯然,女主收到信後回覆信件也是一樣的流程。json

類比

咱們如今將Http協議的消息結構和故事主人公收發信的過程作一個類比:
api

類比圖

下面是使用Fiddler工具抓取www.xitu.io的網絡請求和響應圖:
稀土網絡響應圖

Http消息結構

由前面的分析可知,Http請求消息由三部分組成:服務器

  1. 請求行由3部分組成:①請求的方法,POST仍是GET等;②請求路徑;③Http協議
  2. 請求頭,每一行都是name:value的結構,包含各類來自請求客服端的信息
  3. 請求體,提交給服務器的信息,GET方法沒有此項。

Http響應體跟請求體格式大體同樣。網絡

  1. 請求體有協議和響應碼組成,200爲響應成功
  2. 響應頭,每一行都是name:value的結構,包含各類來自服務器的信息
  3. 響應體,返回客戶端須要的數據

自定義LogInterceptor

OkHttp Interceptor

OkHttp的一大特色就是能夠在發出請求體和收到響應體之間添加任意個數任意任意功能的攔截器,對請求體或者響應體進行操做。仍是用《北京趕上西雅圖之不二情書》的故事來講,那麼地點A的主人就是充當攔截器的角色,在故事中他不只轉發信件,還閱讀了信件的內容。session

根據需求肯定須要Log的信息

Log的信息和劃分兩類,一類是跟業務相關的信息,一類是與業務無關。app

  1. 業務相關包括:
    請求地址url;
    請求頭:token、sessionId;
    請求體:POST提交的內容;
    響應頭:token/sessionId;
    響應體:服務器返回數據;

  2. 業務無關包括:
    網絡狀態碼:200爲正常反應;
    網絡請求時間:從發出網絡請求到響應所消耗的時間;
    網絡協議:http一、http2等;
    網絡Method:POST、GET等;
    不論是業務相關的數據仍是業務無關的數據,都是來自於Http請求體和響應體的消息結構中。

Log效果圖

以公司項目的測試服務器自動登陸接口,log效果以下:

POST
acid->1075
userId->-1
network code->200
url->http://mobileapi.app100688440.twsapp.com/app/open/open.do?ACID=1075&userId=-1&vendorId=7999&VERS=6.5.1&fromType=1110
time->84.473
request headers->sessionId: 
request->{"data":{"pagenum":0,"uid":-1,"flag":"00000000"},"requeststamp":"20161212151841836466"}
body->{"code":200,"responsestamp":"20161212151841836466","data":{"uid":-1,"nickname":"遊客258","uploadUrl":"http://mobileapi.app100688440.twsapp.com/uploadservlet","wxUrl":"http://wx.app100688440.twsapp.com","isEmcee":0,"canLive":0,"goodnum":0,"index":{},"revGift":{},"msgRemind":{},"freshmanMission":{},"account":{},"onlineLimit":"600","userSecretKey":"brjefjzw37ocw46"}}複製代碼

log解釋:
POST:此接口使用POST方法;
acid:標識此接口的id,每一個接口有惟一的acid,根據acid可查詢到此接口的功能,例如此接口acid = 1075爲自動登陸接口;
userId:用戶id
network code:返回200證實服務器響應成功;
url:此接口請求的url地址;
time:爲響應時間,若是某接口響應時間過長,排除網絡環境的緣由,就能夠跟服務端商量是否可優化;
request header:此項目須要傳sessionId;
request:此處打印Post方法的請求體,若後臺返回參數錯誤,檢查此行log便可;
body:後臺數據返回,採用json格式;

代碼實現

/** * 添加Log */
public class LogInterceptor implements Interceptor {

    private static final String TAG = "LogInterceptor";
    private static final Charset UTF8 = Charset.forName("UTF-8"); //urf8編碼

    @Override
    public Response intercept(Chain chain) throws IOException {  //實現Interceptor接口方法
        Log.d(TAG,"before chain,request()");
        Request request = chain.request();  //獲取request
        String acid = request.url().queryParameter("ACID"); //在url中獲取ACID的參數值;
        Response response;
        try {
            long t1 = System.nanoTime();
            response = chain.proceed(request); //OkHttp鏈式調用
            long t2 = System.nanoTime();
            double time = (t2 - t1) / 1e6d;   //用請求後的時間減去請求前的時間獲得耗時

            String userId = request.url().queryParameter("userId");
            String type = "";
            if (request.method().equals("GET")) {    //判斷Method類型
                type = "GET";
            } else if (request.method().equals("POST")) {
                type = "POST";
            } else if (request.method().equals("PUT")) {
                type = "PUT";
            } else if (request.method().equals("DELETE")) {
                type = "DELETE";
            }
            BufferedSource source = response.body().source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();
            String logStr = "\n--------------------".concat(TextUtils.isEmpty(acid) ? "" : acid).concat(" begin--------------------\n")
                    .concat(type)
                    .concat("\nacid->").concat(TextUtils.isEmpty(acid) ? "" : acid)
                    .concat("\nuserId->").concat(TextUtils.isEmpty(userId) ? "" : userId)
                    .concat("\nnetwork code->").concat(response.code() + "")
                    .concat("\nurl->").concat(request.url() + "")
                    .concat("\ntime->").concat(time + "")
                    .concat("\nrequest headers->").concat(request.headers() + "")
                    .concat("request->").concat(bodyToString(request.body()))
                    .concat("\nbody->").concat(buffer.clone().readString(UTF8)); //響應體轉String
            Log.i(TAG, logStr);
        } catch (Exception e) {
            Log.d(TAG,e.getClass().toString()+", error:acid = "+acid);  //網絡出錯,log 出錯的acid
            throw e; //不攔截exception,由上層處理網絡錯誤
        }
        return response;
    }

    /** * 請求體轉String * @param request * @return */
    private static String bodyToString(final RequestBody request) {

        try {
            final Buffer buffer = new Buffer();
            request.writeTo(buffer);
            return buffer.readUtf8();
        } catch (final IOException e) {
            return "did not work";
        }
    }

}複製代碼

開源項目okhttp-logging-interceptor

若是不想本身編寫代碼,也可使用開源項目okhttp-logging-interceptor,支持:1.設置Log的等級;2.使用自定義Log。此開源項目也僅是一個Interceptor。但我的以爲log的樣式並不適合調試使用。同時log的內容比較通用,若想突出對應項目的信息,建議仍是自定義Http Log。

Okhttp+Retrofit+Rxjava

此Interceptor配合OkHttp使用,關於Okhttp+Retrofit+Rxjava的整合,可查看RxJava+Retrofit+OkHttp封裝

引用

okhttp-logging-interceptor
okhttp Interceptors
Http協議詳解

相關文章
相關標籤/搜索