使用Retrofit 2进行日志记录

374

我想要获取请求中发送的完整JSON数据。以下是我的代码:

OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor(){
   @Override public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      Log.e(String.format("\nrequest:\n%s\nheaders:\n%s",
                          request.body().toString(), request.headers()));
      com.squareup.okhttp.Response response = chain.proceed(request);
      return response;
   }
});
Retrofit retrofit = new Retrofit.Builder()
   .baseUrl(API_URL)
   .addConverterFactory(GsonConverterFactory.create())
   .client(client).build();

但是我只在日志中看到这个:

request:
com.squareup.okhttp.RequestBody$1@3ff4074d
headers:
Content-Type: application/vnd.ll.event.list+json

考虑到Retrofit 1中我们曾使用的setLog()setLogLevel()被移除了,我该如何进行正确的日志记录呢?

22个回答

809
在Retrofit 2中,您应该使用HttpLoggingInterceptor
将依赖项添加到build.gradle。截至2023年5月,最新版本为:
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'

创建一个如下所示的Retrofit对象:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://backend.example.com")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

return retrofit.create(ApiClient.class);

如果出现弃用警告,请将 setLevel 更改为:

interceptor.level(HttpLoggingInterceptor.Level.BODY);

上述解决方案为您提供了与旧日志消息非常相似的logcat消息

setLogLevel(RestAdapter.LogLevel.FULL)

如果出现 java.lang.ClassNotFoundException:

旧版本的 Retrofit 可能需要较旧的 logging-interceptor 版本。请查看评论部分获取详细信息。


52
这个功能在我提问仅15天后就被添加到了OkHttp中,很棒,社区的需求能够产生如此迅速的影响! - Gabor
1
你不再需要将Sonatype快照存储库添加到你的build.gradle文件中。 - James Goodwin
13
使用Retrofit 2.1.0时,请添加以下依赖:compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'。此依赖是用于记录网络请求和响应的日志拦截器。 - jayellos
2
@jayellos 感谢您指出这一点。如果您使用的版本低于3.3.1,您将会遇到没有此类方法的异常。 - guydemossyrock
2
我得到了:没有找到类“okhttp3.internal.Platform”,有什么想法吗? - corlaez
显示剩余17条评论

43

我遇到了与您相似的问题,尝试询问《Retrofit: Love working with APIs on Android》一书的作者(此处是链接)。

(不是在打广告哦……但他们确实是非常好的人 :) )

作者很快就回复了我,并提供了关于Retrofit 1.9和Retrofit 2.0-beta上Log方法的代码。

下面是Retrofit 2.0-beta的代码:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();  
// set your desired log level
logging.setLevel(Level.BODY);

OkHttpClient httpClient = new OkHttpClient();  
// add your other interceptors …

// add logging as last interceptor
httpClient.interceptors().add(logging);  // <-- this is the important line!

Retrofit retrofit = new Retrofit.Builder()  
   .baseUrl(API_BASE_URL)
   .addConverterFactory(GsonConverterFactory.create())
   .client(httpClient)
   .build();

这就是如何使用 HttpLoggingInterceptor 添加日志记录方法。此外,如果你阅读过我之前提到的那本书,你可能会发现上面说 Retrofit 2.0 中没有日志方法,这不正确,我已经向作者询问并得到了回复,他们明年将更新该书解释这个问题。

// 如果你对 Retrofit 中的日志方法不太熟悉,我想再分享一些内容。

还应该注意到有一些日志级别可以选择。我大多数时候使用的是 Level.BODY,其输出结果类似于:

enter image description here

你可以在图片中找到几乎所有的 HTTP 细节:请求头、内容和响应等。

有时你真的不需要在派对上招待所有客人:我只想知道是否成功连接,以及在我的活动和碎片中是否成功进行了网络调用。这时你可以使用 Level.BASIC,它将返回类似于以下的结果:

enter image description here

你是否发现了状态码为 200 OK 的存在?就是它 :)

还有另一个日志级别,Level.HEADERS,它只会返回网络请求的头信息。当然,这里也附上一张图片:

enter image description here

以上就是所有的日志技巧 ;)

我还想和你分享一下我学到很多的教程 这里。他们有很多关于 Retrofit 几乎所有相关内容的文章,并且他们在不断更新这些文章,同时 Retrofit 2.0 也正在不断更新。请看看这些文章,我认为它们可以节省你很多时间。


HttpLoggingInterceptor 是从哪里来的? - Radu
5
这对我没有用。httpClient.interceptors.add需要一个com.squareup.okhttp.Interceptor而不是okhttp3.logging.HttpLoggingInterceptor。 - Radu
@Radu 你用的是哪个版本的 Retrofit?你的 gradle 应该有类似这样的代码:compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' - peitek
它无法用于记录请求头,因为需要添加像网络拦截器这样的拦截器!请参见:https://dev59.com/A1wY5IYBdhLWcg3wh4Pc#36847027 - Yazon2006

16

这里是一个Interceptor,它记录了请求和响应的数据(使用了Timber库,参考了OkHttp文档和其他一些Stack Overflow回答):

public class TimberLoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        Timber.i("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers());
        Timber.v("REQUEST BODY BEGIN\n%s\nREQUEST BODY END", bodyToString(request));

        Response response = chain.proceed(request);

        ResponseBody responseBody = response.body();
        String responseBodyString = response.body().string();

        // now we have extracted the response body but in the process
        // we have consumed the original reponse and can't read it again
        // so we need to build a new one to return from this method

        Response newResponse = response.newBuilder().body(ResponseBody.create(responseBody.contentType(), responseBodyString.getBytes())).build();

        long t2 = System.nanoTime();
        Timber.i("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers());
        Timber.v("RESPONSE BODY BEGIN:\n%s\nRESPONSE BODY END", responseBodyString);

        return newResponse;
    }

    private static String bodyToString(final Request request){

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

9
我面临的主要问题是动态添加请求头并将其记录到调试日志中。我尝试添加两个拦截器,一个用于记录日志,另一个用于即时添加请求头(令牌授权)。问题在于我们可以使用.addInterceptor或.addNetworkInterceptor。正如Jake Wharton对我说的那样:“网络拦截器始终在应用程序拦截器之后执行。请参见https://github.com/square/okhttp/wiki/Interceptors。”因此,下面是可以正常工作的示例,其中包含请求头和日志:
OkHttpClient httpClient = new OkHttpClient.Builder()
            //here we can add Interceptor for dynamical adding headers
            .addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request().newBuilder().addHeader("test", "test").build();
                    return chain.proceed(request);
                }
            })
            //here we adding Interceptor for full level logging
            .addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(httpClient)
            .baseUrl(AppConstants.SERVER_ADDRESS)
            .build();

9

我也陷入了类似的境地,当我试图使用HttpLoggingInterceptor的实例调用setLevel()方法时,该方法并未出现,就像这样:

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

这是我解决Retrofit2生成日志问题的方法:

假设您已经添加了依赖关系,

implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"

您可以检查最新版本,请访问以下链接:

https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor )

他们还解释了如何添加。

我创建了一个名为AddLoggingInterceptor的类,这是我的代码:

public class AddLoggingInterceptor {

    public static OkHttpClient setLogging(){
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();

        return okHttpClient;
    }
}

然后,在我们实例化Retrofit的地方,

 public static Retrofit getRetrofitInstance() {
    if (retrofit == null) {
        retrofit = new retrofit2.Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(AddLoggingInterceptor.setLogging()) // here the method is called inside client() method, with the name of class, since it is a static method.
                .build();
    }
    return retrofit;
}

现在你可以在Android Studio中查看生成的日志,你可能需要搜索okHttp进行过滤处理。这适用于我。如果有任何问题,请在此发送消息给我。


1
干得好,@Jwala。谢谢。 - Kenny Dabiri

7

试试这个:

Request request = chain.request();
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
String body = buffer.readUtf8();

接下来,在 body 中有您感兴趣的 JSON 数据。


1
如何打印响应正文? - Gilberto Ibarra
3
@GilbertoIbarra 使用以下代码:String bodyString = response.body().string(); log(bodyString); response = response.newBuilder() .body(ResponseBody.create(response.body().contentType(), bodyString)) .build();(由于不能重复读取响应体,因此需要使用构建器创建一个新的响应。) - Anton Ryabyh
这是我们不再需要的一个拐杖。以下是一个工作日志记录的示例:https://dev59.com/A1wY5IYBdhLWcg3wh4Pc#36847027 - Yazon2006

6
如果您正在使用Retrofit2和okhttp3,则需要知道拦截器是按队列工作的。因此,在其他拦截器之后添加loggingInterceptor:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG)
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);

 new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .addInterceptor(new CatalogInterceptor(context))//first
                .addInterceptor(new OAuthInterceptor(context))//second
                .authenticator(new BearerTokenAuthenticator(context))
                .addInterceptor(loggingInterceptor)//third, log at the end
                .build();

5
我不确定setLogLevel()方法是否会在最终的Retrofit 2.0版本中存在,但目前您可以使用拦截器进行记录日志。
在OkHttp wiki中可以找到一个很好的示例:https://github.com/square/okhttp/wiki/Interceptors
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new LoggingInterceptor());

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://www.yourjsonapi.com")
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build();

你要么没有读懂我的问题,要么没有仔细阅读你所链接的页面...因为LoggingInterceptor并不尝试记录请求体(因此无法解决我的问题)。 - Gabor
我收到了“UnsupportedOperationException”错误。 - Choletski

4

Kotlin代码

        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
        val retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        return retrofit.create(PointApi::class.java)

4

对于那些需要在Retrofit中进行高级别记录的人,可以像这样使用拦截器:

public static class LoggingInterceptor implements Interceptor {
    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        String requestLog = String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers());
        //YLog.d(String.format("Sending request %s on %s%n%s",
        //        request.url(), chain.connection(), request.headers()));
        if(request.method().compareToIgnoreCase("post")==0){
            requestLog ="\n"+requestLog+"\n"+bodyToString(request);
        }
        Log.d("TAG","request"+"\n"+requestLog);

        Response response = chain.proceed(request);
        long t2 = System.nanoTime();

        String responseLog = String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers());

        String bodyString = response.body().string();

        Log.d("TAG","response"+"\n"+responseLog+"\n"+bodyString);

        return response.newBuilder()
                .body(ResponseBody.create(response.body().contentType(), bodyString))
                .build();
        //return response;
    }
}

public static String bodyToString(final Request request) {
    try {
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        copy.body().writeTo(buffer);
        return buffer.readUtf8();
    } catch (final IOException e) {
        return "did not work";
    }
}`

感谢: https://github.com/square/retrofit/issues/1072#

这是关于it技术的链接,感谢提供。

你应该提到你从 https://github.com/square/retrofit/issues/1072# 复制了代码(以便给予来源的信用)。 - Gabor
这是一个我们不再需要的支撑。这里有一个工作日志记录的例子:https://dev59.com/A1wY5IYBdhLWcg3wh4Pc#36847027 - Yazon2006

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接