For logging the custom logs for Server and Client request/response using the LoggingFeature, you need to create a custom class that extends LoggingFeature and implement ContainerRequestFilter, ContainerResponseFilter, ClientRequestFilter, ClientResponseFilter and WriterInterceptor.
You need to override the LoggingFeature method public boolean configure(FeatureContext context)
and register your CustomLoggingFeature object with the context
@Override
public boolean configure(FeatureContext context) {
context.register(this);
return true;
}
ContainerRequestFilter and ContainerResponseFilter interface has methods used to log the Server request and response.
ClientRequestFilter and ContainerResponseFilter interface has methods used to log the Client request and response.
Also, you need to implement the WriterInterceptor
to log the Client request and Server response body.
Finally registered your CustomLoggingFeature class with jersey.
environment.jersey().register(new CustomLoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));
I am using Dropwizard v1.3.5.
Here is my implementation.
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;
}
new LoggingFeature(Logger.getLogger(getClass().getName()), LoggingFeature.Verbosity.PAYLOAD_ANY)
resulted in exactly the same output. According to the documentationPAYLOAD_TEXT
corresponds to (among others) content typeapplication/json
. – Teleost