I have a rest service developed with jersey, I have a ContainerRequestFilters for print the request, as follows:
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
and I have logger in the post methods using log4j. But the LoggingFilter print in the log different log4j. Is there any way that LogginFilter use the log4j's configuration?
I tried this in the log4j.xml file:
<logger name="com.sun.jersey.api.container.filter.LoggingFilter">
<level value="info" />
<appender-ref ref="ROOT" />
<appender-ref ref="CONSOLE" />
</logger>
but it don't work :(
One way to handle this is to write a filter for the java logger where you simply log to message with the log4j logger, or which ever you prefer and you sink the java logging by always returning false.
Logger javaLogger = Logger.getLogger(YourClass.class.getName());
javaLogger.setFilter(new Filter() {
@Override
public boolean isLoggable(LogRecord record) {
log4jLogger.info(new Date(record.getMillis()) + " " + record.getLevel() + " " + record.getMessage());
return false;
}
});
this.jerseyClient.register(new LoggingFilter(javaLogger, true));
Jersey uses indeed the jdk logger. You can use the SLF4JBridgeHandler to transfer the JDK log messages to your logging infrastructure. (see Claus Nielsen's Blogpost)
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
So to get this picked up in your Spring application, I used the @PostConstruct
annotation.
@Component
public class JerseyLoggingBridge {
@PostConstruct
private void init() {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
}
}
This will allow you to use any logger you want.
Implementation
import javax.annotation.Priority;
import javax.ws.rs.container.PreMatching;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@PreMatching
@Priority(Integer.MIN_VALUE)
public final class Log4j2JerseyLoggingFilter extends AbstractJerseyLoggingFilter {
private final Logger logger;
public Log4j2JerseyLoggingFilter() {
this(LogManager.getLogger(Log4j2JerseyLoggingFilter.class));
}
public Log4j2JerseyLoggingFilter(final Logger logger) {
this(logger, false);
}
public Log4j2JerseyLoggingFilter(final Logger logger, boolean printEntity) {
super(printEntity);
this.logger = logger;
}
public Log4j2JerseyLoggingFilter(final Logger logger, int maxEntitySize) {
super(maxEntitySize);
this.logger = logger;
}
@Override
protected void log(StringBuilder b) {
if (logger != null) {
logger.info(b.toString());
}
}
}
Abstract Logging Filter Class
This is straight from the Jersey Logging filter class but making the log method abstract.
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Priority;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.message.MessageUtils;
/**
* Universal logging filter.
* <p/>
* Can be used on client or server side. Has the highest priority.
*
* @author Pavel Bucek (pavel.bucek at oracle.com)
* @author Martin Matula
*/
@PreMatching
@Priority(Integer.MIN_VALUE)
public abstract class AbstractJerseyLoggingFilter implements ContainerRequestFilter, ClientRequestFilter,
ContainerResponseFilter, ClientResponseFilter, WriterInterceptor {
private static final String NOTIFICATION_PREFIX = "* ";
private static final String REQUEST_PREFIX = "> ";
private static final String RESPONSE_PREFIX = "< ";
private static final String ENTITY_LOGGER_PROPERTY = LoggingFilter.class.getName() + ".entityLogger";
private static final String LOGGING_ID_PROPERTY = LoggingFilter.class.getName() + ".id";
private static final Comparator<Map.Entry<String, List<String>>> COMPARATOR = new Comparator<Map.Entry<String, List<String>>>() {
@Override
public int compare(final Map.Entry<String, List<String>> o1, final Map.Entry<String, List<String>> o2) {
return o1.getKey().compareToIgnoreCase(o2.getKey());
}
};
private static final int DEFAULT_MAX_ENTITY_SIZE = 8 * 1024;
private final AtomicLong _id = new AtomicLong(0);
private final boolean printEntity;
private final int maxEntitySize;
/**
* Create a logging filter logging the request and response to a default JDK
* logger, named as the fully qualified class name of this class. Entity
* logging is turned off by default.
*/
public AbstractJerseyLoggingFilter() {
this(false);
}
/**
* Create a logging filter with custom logger and custom settings of entity
* logging.
*
* @param logger
* the logger to log requests and responses.
* @param printEntity
* if true, entity will be logged as well up to the default
* maxEntitySize, which is 8KB
*/
public AbstractJerseyLoggingFilter(final boolean printEntity) {
this.printEntity = printEntity;
this.maxEntitySize = DEFAULT_MAX_ENTITY_SIZE;
}
/**
* Creates a logging filter with custom logger and entity logging turned on,
* but potentially limiting the size of entity to be buffered and logged.
*
* @param logger
* the logger to log requests and responses.
* @param maxEntitySize
* maximum number of entity bytes to be logged (and buffered) -
* if the entity is larger, logging filter will print (and buffer
* in memory) only the specified number of bytes and print
* "...more..." string at the end. Negative values are
* interpreted as zero.
*/
public AbstractJerseyLoggingFilter(final int maxEntitySize) {
this.printEntity = true;
this.maxEntitySize = Math.max(0, maxEntitySize);
}
protected abstract void log(final StringBuilder b);
private StringBuilder prefixId(final StringBuilder b, final long id) {
b.append(Long.toString(id)).append(" ");
return b;
}
private void printRequestLine(final StringBuilder b, final String note, final long id, final String method,
final URI uri) {
prefixId(b, id).append(NOTIFICATION_PREFIX).append(note).append(" on thread ")
.append(Thread.currentThread().getName()).append("\n");
prefixId(b, id).append(REQUEST_PREFIX).append(method).append(" ").append(uri.toASCIIString()).append("\n");
}
private void printResponseLine(final StringBuilder b, final String note, final long id, final int status) {
prefixId(b, id).append(NOTIFICATION_PREFIX).append(note).append(" on thread ")
.append(Thread.currentThread().getName()).append("\n");
prefixId(b, id).append(RESPONSE_PREFIX).append(Integer.toString(status)).append("\n");
}
private void printPrefixedHeaders(final StringBuilder b, final long id, final String prefix,
final MultivaluedMap<String, String> headers) {
for (final Map.Entry<String, List<String>> headerEntry : getSortedHeaders(headers.entrySet())) {
final List<?> val = headerEntry.getValue();
final String header = headerEntry.getKey();
if (val.size() == 1) {
prefixId(b, id).append(prefix).append(header).append(": ").append(val.get(0)).append("\n");
} else {
final StringBuilder sb = new StringBuilder();
boolean add = false;
for (final Object s : val) {
if (add) {
sb.append(',');
}
add = true;
sb.append(s);
}
prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString()).append("\n");
}
}
}
private Set<Map.Entry<String, List<String>>> getSortedHeaders(final Set<Map.Entry<String, List<String>>> headers) {
final TreeSet<Map.Entry<String, List<String>>> sortedHeaders = new TreeSet<Map.Entry<String, List<String>>>(
COMPARATOR);
sortedHeaders.addAll(headers);
return sortedHeaders;
}
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;
}
@Override
public void filter(final ClientRequestContext context) throws IOException {
final long id = _id.incrementAndGet();
context.setProperty(LOGGING_ID_PROPERTY, id);
final StringBuilder b = new StringBuilder();
printRequestLine(b, "Sending client request", id, context.getMethod(), context.getUri());
printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getStringHeaders());
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 Object requestId = requestContext.getProperty(LOGGING_ID_PROPERTY);
final long id = requestId != null ? (Long) requestId : _id.incrementAndGet();
final StringBuilder b = new StringBuilder();
printResponseLine(b, "Client response received", id, responseContext.getStatus());
printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getHeaders());
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 long id = _id.incrementAndGet();
context.setProperty(LOGGING_ID_PROPERTY, id);
final StringBuilder b = new StringBuilder();
printRequestLine(b, "Server has received a request", id, context.getMethod(),
context.getUriInfo().getRequestUri());
printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getHeaders());
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)
throws IOException {
final Object requestId = requestContext.getProperty(LOGGING_ID_PROPERTY);
final long id = requestId != null ? (Long) requestId : _id.incrementAndGet();
final StringBuilder b = new StringBuilder();
printResponseLine(b, "Server responded with a response", id, responseContext.getStatus());
printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getStringHeaders());
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 class LoggingStream extends FilterOutputStream {
private final StringBuilder b;
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
LoggingStream(final StringBuilder b, final OutputStream inner) {
super(inner);
this.b = b;
}
StringBuilder getStringBuilder(final Charset charset) {
// write entity to the builder
final byte[] entity = baos.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;
}
@Override
public void write(final int i) throws IOException {
if (baos.size() <= maxEntitySize) {
baos.write(i);
}
out.write(i);
}
}
}
Register it the same way as ususal.
public class RestApplication extends ResourceConfig {
/**
* Registers this package as a list of resources that should be scanned for
* other resources.
*/
public RestApplication() {
packages(RestApplication.class.getPackage().getName());
register(Log4j2JerseyLoggingFilter.class);
}
}
As of log4j2, you can simply hand over all of JDK's logging to log4j (unless you explicitly require JDK logging to handle part of the rest of the logging), using the log4j-jul
library and the -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
VM argument.
Source: https://logging.apache.org/log4j/2.0/log4j-jul/