java - Logging all network traffic in Spring mvc -
i have spring mvc application using requestbody , responsebody annotations. configured mappingjackson2httpmessageconverter. have slf4j set up. log json comes in , out controller. did extend
mappingjackson2httpmessageconverter @override public object read(type type, class<?> contextclass, httpinputmessage inputmessage) throws ioexception, httpmessagenotreadableexception { logstream(inputmessage.getbody()); return super.read(type, contextclass, inputmessage); }
i can input stream, if read content becomes empty , loose message. mark() , reset() not supported. implemented pushbackinputstream, tried read it's content , push this:
public void logstream(inputstream is) { if (is instanceof pushbackinputstream) try { pushbackinputstream pushbackinputstream = (pushbackinputstream) is; byte[] bytes = new byte[20000]; stringbuilder sb = new stringbuilder(is.available()); int red = is.read(); int pos =0; while (red > -1) { bytes[pos] = (byte) red; pos=1 + pos; red = is.read(); } pushbackinputstream.unread(bytes,0, pos-1); log.info("json payload " + sb.tostring()); } catch (exception e) { log.error("ignoring exception in logger ", e); } }
but exception
java.io.ioexception: push buffer full
i tried turn on logging on http level described here:spring resttemplate - how enable full debugging/logging of requests/responses? without luck.
after more whole work day of experimenting got working solution. consists of logging filter, 2 wrappers request , response , registration of logging filter:
the filter class is:
/** * http logging filter, wraps around request , response in * each http call , logs * whole request , response bodies. enabled * putting instance filter chain * overriding getservletfilters() in * abstractannotationconfigdispatcherservletinitializer. */ public class loggingfilter extends abstractrequestloggingfilter { private static final logger log = loggerfactory.getlogger(loggingfilter.class); @override protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain) throws servletexception, ioexception { long id = system.currenttimemillis(); requestloggingwrapper requestloggingwrapper = new requestloggingwrapper(id, request); responseloggingwrapper responseloggingwrapper = new responseloggingwrapper(id, response); log.debug(id + ": http request " + request.getrequesturi()); super.dofilterinternal(requestloggingwrapper, responseloggingwrapper, filterchain); log.debug(id + ": http response " + response.getstatus() + " finished in " + (system.currenttimemillis() - id) + "ms"); } @override protected void beforerequest(httpservletrequest request, string message) { } @override protected void afterrequest(httpservletrequest request, string message) { } }
this class using stream wrappers, suggested master slave , david ehrmann.
request wrapper looks this:
/** * request logging wrapper using proxy split stream extract request body */ public class requestloggingwrapper extends httpservletrequestwrapper { private static final logger log = loggerfactory.getlogger(requestloggingwrapper.class); private final bytearrayoutputstream bos = new bytearrayoutputstream(); private long id; /** * @param requestid , id gets logged output file. it's used bind request * response * @param request request want extract post data */ public requestloggingwrapper(long requestid, httpservletrequest request) { super(request); this.id = requestid; } @override public servletinputstream getinputstream() throws ioexception { final servletinputstream servletinputstream = requestloggingwrapper.super.getinputstream(); return new servletinputstream() { private teeinputstream tee = new teeinputstream(servletinputstream, bos); @override public int read() throws ioexception { return tee.read(); } @override public int read(byte[] b, int off, int len) throws ioexception { return tee.read(b, off, len); } @override public int read(byte[] b) throws ioexception { return tee.read(b); } @override public boolean isfinished() { return servletinputstream.isfinished(); } @override public boolean isready() { return servletinputstream.isready(); } @override public void setreadlistener(readlistener readlistener) { servletinputstream.setreadlistener(readlistener); } @override public void close() throws ioexception { super.close(); // logging logrequest(); } }; } public void logrequest() { log.info(getid() + ": http request " + new string(tobytearray())); } public byte[] tobytearray() { return bos.tobytearray(); } public long getid() { return id; } public void setid(long id) { this.id = id; } }
and response wrapper different in close/flush method (close doesn't called)
public class responseloggingwrapper extends httpservletresponsewrapper { private static final logger log = loggerfactory.getlogger(responseloggingwrapper.class); private final bytearrayoutputstream bos = new bytearrayoutputstream(); private long id; /** * @param requestid , id gets logged output file. it's used bind response * response (they have same id, currenttimemilis used) * @param response response want extract stream data */ public responseloggingwrapper(long requestid, httpservletresponse response) { super(response); this.id = requestid; } @override public servletoutputstream getoutputstream() throws ioexception { final servletoutputstream servletoutputstream = responseloggingwrapper.super.getoutputstream(); return new servletoutputstream() { private teeoutputstream tee = new teeoutputstream(servletoutputstream, bos); @override public void write(byte[] b) throws ioexception { tee.write(b); } @override public void write(byte[] b, int off, int len) throws ioexception { tee.write(b, off, len); } @override public void flush() throws ioexception { tee.flush(); logrequest(); } @override public void write(int b) throws ioexception { tee.write(b); } @override public boolean isready() { return servletoutputstream.isready(); } @override public void setwritelistener(writelistener writelistener) { servletoutputstream.setwritelistener(writelistener); } @override public void close() throws ioexception { super.close(); // logging logrequest(); } }; } public void logrequest() { byte[] tolog = tobytearray(); if (tolog != null && tolog.length > 0) log.info(getid() + ": http response " + new string(tolog)); } /** * method clear buffer, * * @return captured bytes stream */ public byte[] tobytearray() { byte[] ret = bos.tobytearray(); bos.reset(); return ret; } public long getid() { return id; } public void setid(long id) { this.id = id; }
}
at last loggingfilter needs registered in abstractannotationconfigdispatcherservletinitializer this:
@override protected filter[] getservletfilters() { loggingfilter requestloggingfilter = new loggingfilter(); return new filter[]{requestloggingfilter}; }
i know, there maven lib this, don't want include whole lib because of small logging utility. harder thought. expected achieve modifying log4j.properties. still think should part of spring.
Comments
Post a Comment