com.microsoft.rest.interceptors.LoggingInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client-runtime Show documentation
Show all versions of client-runtime Show documentation
This package contains the basic runtime for AutoRest generated Java clients.
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/
package com.microsoft.rest.interceptors;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.io.CharStreams;
import com.microsoft.rest.LogLevel;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
/**
* An OkHttp interceptor that handles logging of HTTP requests and responses.
*/
public class LoggingInterceptor implements Interceptor {
private static final String LOGGING_HEADER = "x-ms-logging-context";
private static final String BODY_LOGGING = "x-ms-body-logging";
private static final ObjectMapper MAPPER = new ObjectMapper();
private LogLevel logLevel;
/**
* Creates an interceptor with a LogLevel enum.
* @param logLevel the level of traffic to log
*/
public LoggingInterceptor(LogLevel logLevel) {
this.logLevel = logLevel;
}
/**
* Process the log using an SLF4j logger and an HTTP message.
* @param logger the SLF4j logger with the context of the request
* @param s the message for logging
*/
protected void log(Logger logger, String s) {
logger.info(s);
}
@Override
public Response intercept(Chain chain) throws IOException {
// get logger
Request request = chain.request();
String context = request.header(LOGGING_HEADER);
String bodyLoggingHeader = request.header(BODY_LOGGING);
boolean bodyLogging = bodyLoggingHeader == null || Boolean.parseBoolean(bodyLoggingHeader);
if (context == null) {
context = "";
}
Logger logger = LoggerFactory.getLogger(context);
// log URL
if (logLevel != LogLevel.NONE) {
log(logger, String.format("--> %s %s", request.method(), request.url()));
}
// log headers
if (logLevel == LogLevel.HEADERS || logLevel == LogLevel.BODY_AND_HEADERS) {
for (String header : request.headers().names()) {
if (!LOGGING_HEADER.equals(header)) {
log(logger, String.format("%s: %s", header, Joiner.on(", ").join(request.headers(header))));
}
}
}
// log body
if (bodyLogging && (logLevel == LogLevel.BODY || logLevel == LogLevel.BODY_AND_HEADERS)) {
if (request.body() != null) {
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
Charset charset = Charset.forName("UTF8");
MediaType contentType = request.body().contentType();
if (contentType != null) {
charset = contentType.charset(charset);
}
if (isPlaintext(buffer)) {
String content = buffer.clone().readString(charset);
if (logLevel.isPrettyJson()) {
try {
content = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readValue(content, JsonNode.class));
} catch (Exception e) {
// swallow, keep original content
}
}
log(logger, String.format("%s-byte body:\n%s", request.body().contentLength(), content));
log(logger, "--> END " + request.method());
} else {
log(logger, "--> END " + request.method() + " (binary "
+ request.body().contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
if (logLevel != LogLevel.NONE) {
log(logger, "<-- 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";
// log URL
if (logLevel != LogLevel.NONE) {
log(logger, String.format("<-- %s %s %s (%s ms, %s body)", response.code(), response.message(), response.request().url(), tookMs, bodySize));
}
// log headers
if (logLevel == LogLevel.HEADERS || logLevel == LogLevel.BODY_AND_HEADERS) {
for (String header : response.headers().names()) {
log(logger, String.format("%s: %s", header, Joiner.on(", ").join(response.headers(header))));
}
}
// log body
if (bodyLogging && (logLevel == LogLevel.BODY || logLevel == LogLevel.BODY_AND_HEADERS)) {
if (response.body() != null) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = Charset.forName("UTF8");
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(charset);
} catch (UnsupportedCharsetException e) {
log(logger, "Couldn't decode the response body; charset is likely malformed.");
log(logger, "<-- END HTTP");
return response;
}
}
boolean gzipped = response.header("content-encoding") != null && StringUtils.containsIgnoreCase(response.header("content-encoding"), "gzip");
if (!isPlaintext(buffer) && !gzipped) {
log(logger, "<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
String content;
if (gzipped) {
content = CharStreams.toString(new InputStreamReader(new GZIPInputStream(buffer.clone().inputStream())));
} else {
content = buffer.clone().readString(charset);
}
if (logLevel.isPrettyJson()) {
try {
content = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readValue(content, JsonNode.class));
} catch (Exception e) {
// swallow, keep original content
}
}
log(logger, String.format("%s-byte body:\n%s", buffer.size(), content));
log(logger, "<-- END HTTP");
}
}
return response;
}
/**
* @return the current logging level.
*/
public LogLevel logLevel() {
return logLevel;
}
/**
* Sets the current logging level.
* @param logLevel the new logging level
* @return the interceptor
*/
public LoggingInterceptor withLogLevel(LogLevel logLevel) {
this.logLevel = logLevel;
return this;
}
private static boolean isPlaintext(Buffer buffer) throws EOFException {
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;
}
}
}