如何使用Dropwizard 1.0.2中的LoggingFeature打印服务器响应?

时间:2016-10-21 07:59:08

标签: java json jersey dropwizard jersey-client

以下代码导致在Dropwizard 0.9.2和1.0.2中打印JSON服务器响应:

return ClientBuilder
        .newBuilder()
        .build()
        .register(new LoggingFilter(Logger.getLogger(LoggingFilter.class.getName()), true))

例如:

Oct 21, 2016 7:57:42 AM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Client response received on thread main
1 < 401
1 < Connection: keep-alive
1 < Content-Length: 49
1 < Content-Type: text/plain
1 < Date: Fri, 21 Oct 2016 07:57:42 GMT
1 < Server: […]
1 < WWW-Authenticate: Basic realm="[…]"
Credentials are required to access this resource.

javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized

但是,LoggingFilter在1.0.2中已弃用,建议您使用LoggingFeature。在documentation of LoggingFeature中,它表示默认详细程度为LoggingFeature.Verbosity.PAYLOAD_TEXT,因此我希望以下代码仍然在Dropwizard 1.0.2中打印JSON服务器响应:

return ClientBuilder
        .newBuilder()
        .build()
        .register(new LoggingFeature(Logger.getLogger(getClass().getName())))

而是日志包含这个:

javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized

5 个答案:

答案 0 :(得分:5)

这就是诀窍:

new LoggingFeature(Logger.getLogger(getClass().getName()), Level.OFF, LoggingFeature.Verbosity.PAYLOAD_TEXT, 8192)

我猜测客户端中的日志记录功能就像过滤器,而不是预期的包含。

答案 1 :(得分:5)

一个简短的例子来说明一个常见问题,它使开发人员认为日志记录功能无效。

private static final LOG = Logger.getLogger(getClass().getName());

public void test() {
    Client client = ClientBuilder.newBuilder()
            .register(new LoggingFeature(LOG, Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ALL, 8192))
            .build();
    // all requests and responses using this client will now be logged
    // with the log-level FINE to the logger LOG, but the logger
    // will simply ignore them, because it's default level is INFO
}

创建的记录器实例LOG使用默认日志级别,即INFO。这意味着它将接受级别至少为INFO或更高级别的所有日志消息(WARNING,SEVERE,...),但它将忽略具有较低级别的所有消息,如FINE。 (它仍会将消息传递给它的父记录器,如果有的话)

注意:处理程序的默认日志级别是Level.ALL,他们不应该拒绝任何日志记录,只要你不修改他们的级别。

因此,您需要提高LoggingFeatures级别或降低记录器级别以查看消息。

解决方案1:提高LoggingFeature的级别:

new LoggingFeature(LOG, Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ALL, 8192)

解决方案2:降低记录器的级别:

LOG.setLevel(Level.FINE)

答案 2 :(得分:1)

所以 - 我测试了这个。这可能是有关记录器配置和功能配置的问题,而不是实际功能。我给你的提示确实打印了你想要的东西,这是证据:

public class MainClientTest {

    public static void main(String[] args) {

        Client build = ClientBuilder.newBuilder().build();

        // Configure Logger to log it all
        Logger logger = Logger.getLogger("test");
        logger.setLevel(Level.ALL);
        logger.setUseParentHandlers(false);
        Handler[] handlers = logger.getHandlers();
        for(Handler h : handlers) logger.removeHandler(h);
        logger.addHandler(buildseh());
        logger.info("Logger");
        build = build.register(new LoggingFeature(logger, Level.ALL, Verbosity.PAYLOAD_ANY, null));

        build.target("https://www.google.com").request().get();
    }

    public static StreamHandler buildseh() {
        final StreamHandler seh = new StreamHandler(System.err, new JdkLoggerFormatter()) {
            @Override
            public synchronized void publish(final LogRecord record) {
                super.publish(record);
                flush();
            }
        };
        seh.setLevel(Level.ALL); // Default StdErr Setting
        return seh;
    }
}

打印:

1477055066111 I test Logger
1477055066397   test 1 * Sending client request on thread main
1 > GET https://www.google.com

1477055067350   test 1 * Client response received on thread main
1 < 200
1 < Accept-Ranges: none
1 < Alt-Svc: quic=":443"; ma=2592000; v="36,35,34,33,32"
1 < Cache-Control: private, max-age=0
1 < Content-Type: text/html; charset=ISO-8859-1
1 < Date: Fri, 21 Oct 2016 13:04:27 GMT
1 < Expires: -1
1 < P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
1 < Server: gws
1 < Set-Cookie: NID=89=YPr3UcI5rcA4qiaXfm9zkA0uAWrnSDEbxN3TcFuhZ9PkLNvkSHBCHHLcYeXa7tNpzpM_9p7AFAreYq3kR9awqqKrhv5W6pWavfx5bZM7Jjbt559a4aEv20exEPJRmS1N; expires=Sat, 22-Apr-2017 13:04:27 GMT; path=/; domain=.google.co.uk; HttpOnly
1 < Transfer-Encoding: chunked
1 < Vary: Accept-Encoding
1 < X-Frame-Options: SAMEORIGIN
1 < X-XSS-Protection: 1; mode=block
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en-GB"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script>(function(){window.google={kEI:'WxIKWJOeEcGwa_nDmNgC',kEXPI:'1351633,1351901,3700318,3700400,4029815,4031109,4032678,4036527,4038012,4039268,4043492,4045841,4048347,4052304,4062185,4063220,4065786,4066665,4068550,4068816,4069839,4069841,4070140,4070329,4071229,4072287,4072364,4072602,4072705,4072773,4073248,4073405,4073758,4073913,4073959,4074684,4074809,4076096,4076315,4076930,4076999,4077037,4077119,4077

所以,这里有陷阱:

您的功能必须配置正确的级别。参见:

new LoggingFeature(logger, Level.ALL, Verbosity.PAYLOAD_ANY, null)

必须将记录器配置为记录正确的内容。

seh.setLevel(Level.ALL); // Default StdErr Setting
logger.setLevel(Level.ALL);

该功能必须具有正确的详细程度:Verbosity.PAYLOAD_ANY

这就是你需要做的一切。

此致

Artur

答案 3 :(得分:0)

对我来说,@ l0b0的解决方案确实记录了请求/响应有效负载,但是我的访问日志将请求和响应记录为错误。以下行对我来说正常工作:

new LoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_TEXT, 8192)

答案 4 :(得分:0)

要使用LoggingFeature记录服务器和客户端请求/响应的自定义日志,您需要创建一个自定义类来扩展LoggingFeature并实现ContainerRequestFilter,ContainerResponseFilter,ClientRequestFilter,ClientResponseFilter和WriterInterceptor。

您需要重写LoggingFeature方法public boolean configure(FeatureContext context),并在上下文中注册CustomLoggingFeature对象

@Override
public boolean configure(FeatureContext context) {
    context.register(this);
    return true;
}

ContainerRequestFilter和ContainerResponseFilter接口具有用于记录服务器请求和响应的方法。

ClientRequestFilter和ContainerResponseFilter接口具有用于记录客户端请求和响应的方法。

此外,您需要实现WriterInterceptor来记录客户端请求和服务器响应正文。

最后用球衣注册了CustomLoggingFeature类。

environment.jersey().register(new CustomLoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));

我正在使用Dropwizard v1.3.5

这是我的实现方式。

public class CustomLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,
    ClientRequestFilter, ClientResponseFilter, WriterInterceptor {

private static final boolean printEntity = true;
private static final int maxEntitySize = 8 * 1024;
private final Logger logger = Logger.getLogger("CustomLoggingFeature");
private static final String ENTITY_LOGGER_PROPERTY = CustomLoggingFeature.class.getName();
private static final String NOTIFICATION_PREFIX = "* ";
private static final String REQUEST_PREFIX = "> ";
private static final String RESPONSE_PREFIX = "< ";
private static final String AUTHORIZATION = "Authorization";
private static final String EQUAL = " = ";
private static final String HEADERS_SEPARATOR = ", ";
private static List<String> requestHeaders;

static {
    requestHeaders = new ArrayList<>();
    requestHeaders.add(AUTHORIZATION);
}

public CustomLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {
    super(logger, level, verbosity, maxEntitySize);
}

@Override
public boolean configure(FeatureContext context) {
    context.register(this);
    return true;
}

@Override
public void filter(final ClientRequestContext context) {
    final StringBuilder b = new StringBuilder();
    printHeaders(b, context.getStringHeaders());
    printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());

    if (printEntity && context.hasEntity()) {
        final OutputStream stream = new LoggingStream(b, context.getEntityStream());
        context.setEntityStream(stream);
        context.setProperty(ENTITY_LOGGER_PROPERTY, stream);
        // not calling log(b) here - it will be called by the interceptor
    } else {
        log(b);
    }
}

@Override
public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {
    final StringBuilder b = new StringBuilder();
    printResponseLine(b, "Client response received", responseContext.getStatus());

    if (printEntity && responseContext.hasEntity()) {
        responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),
                MessageUtils.getCharset(responseContext.getMediaType())));
    }
    log(b);
}

@Override
public void filter(final ContainerRequestContext context) throws IOException {
    final StringBuilder b = new StringBuilder();
    printHeaders(b, context.getHeaders());
    printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());

    if (printEntity && context.hasEntity()) {
        context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));
    }
    log(b);
}

@Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {
    final StringBuilder b = new StringBuilder();
    printResponseLine(b, "Server responded with a response", responseContext.getStatus());

    if (printEntity && responseContext.hasEntity()) {
        final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());
        responseContext.setEntityStream(stream);
        requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);
        // not calling log(b) here - it will be called by the interceptor
    } else {
        log(b);
    }
}

@Override
public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {
    final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);
    writerInterceptorContext.proceed();
    if (stream != null) {
        log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));
    }
}

private static class LoggingStream extends FilterOutputStream {
    private final StringBuilder b;
    private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    LoggingStream(final StringBuilder b, final OutputStream inner) {
        super(inner);

        this.b = b;
    }

    StringBuilder getStringBuilder(Charset charset) {
        // write entity to the builder
        final byte[] entity = byteArrayOutputStream.toByteArray();

        b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));
        if (entity.length > maxEntitySize) {
            b.append("...more...");
        }
        b.append('\n');

        return b;
    }

    public void write(final int i) throws IOException {
        if (byteArrayOutputStream.size() <= maxEntitySize) {
            byteArrayOutputStream.write(i);
        }
        out.write(i);
    }
}

private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {
    for (String header : requestHeaders) {
        if (Objects.nonNull(headers.get(header))) {
            b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);
        }
    }
    int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);
    if (lastIndex != -1) {
        b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());
        b.append("\n");
    }
}

private void log(final StringBuilder b) {
    String message = Util.mask(b.toString());
    if (logger != null) {
        logger.info(message);
    }
}

private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {
    b.append(NOTIFICATION_PREFIX)
            .append(note)
            .append(" on thread ").append(Thread.currentThread().getId())
            .append(REQUEST_PREFIX).append(method).append(" ")
            .append(uri.toASCIIString()).append("\n");
}

private void printResponseLine(final StringBuilder b, final String note, final int status) {
    b.append(NOTIFICATION_PREFIX)
            .append(note)
            .append(" on thread ").append(Thread.currentThread().getId())
            .append(RESPONSE_PREFIX)
            .append(Integer.toString(status))
            .append("\n");
}

private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {
    if (!stream.markSupported()) {
        stream = new BufferedInputStream(stream);
    }
    stream.mark(maxEntitySize + 1);
    final byte[] entity = new byte[maxEntitySize + 1];
    final int entitySize = stream.read(entity);
    b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));
    if (entitySize > maxEntitySize) {
        b.append("...more...");
    }
    b.append('\n');
    stream.reset();
    return stream;
}