56

I've used Jakarta commons HttpClient in another project and I would like the same wire logging output but using the "standard" HttpUrlConnection.

I've used Fiddler as a proxy but I would like to log the traffic directly from java.

Capturing what goes by the connection input and output streams is not enough because the HTTP headers are written and consumed by the HttpUrlConnection class, so I will not be able to log the headers.

Vadzim
  • 24,954
  • 11
  • 143
  • 151
Serxipc
  • 6,639
  • 9
  • 40
  • 51

8 Answers8

66

According to Sun's HttpURLConnection source there is some logging support via JUL.

Setup (adjust path as required):

-Djava.util.logging.config.file=/full/path/to/logging.properties

logging.properties:

handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINEST
sun.net.www.protocol.http.HttpURLConnection.level=ALL

This will log to the console, adjust as required to e.g. log to a file.

Example output:

2010-08-07 00:00:31 sun.net.www.protocol.http.HttpURLConnection writeRequests
FIN: sun.net.www.MessageHeader@16caf435 pairs: {GET /howto.html HTTP/1.1: null}{User-Agent: Java/1.6.0_20}{Host: www.rgagnon.com}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}
2010-08-07 00:00:31 sun.net.www.protocol.http.HttpURLConnection getInputStream
FIN: sun.net.www.MessageHeader@5ac0728 pairs: {null: HTTP/1.1 200 OK}{Date: Sat, 07 Aug 2010 04:00:33 GMT}{Server: Apache}{Accept-Ranges: bytes}{Content-Length: 17912}{Keep-Alive: timeout=5, max=64}{Connection: Keep-Alive}{Content-Type: text/html}

Note that this prints only headers without body.

See http://www.rgagnon.com/javadetails/java-debug-HttpURLConnection-problem.html for details.

There is also system property -Djavax.net.debug=all. But it's mainly useful for SSL debugging.

sleske
  • 81,358
  • 34
  • 189
  • 227
Vadzim
  • 24,954
  • 11
  • 143
  • 151
  • 14
    programmatically you can do this using `sun.util.logging.PlatformLogger .getLogger("sun.net.www.protocol.http.HttpURLConnection") .setLevel(PlatformLogger.Level.ALL);` – Erich Eichinger Feb 12 '16 at 22:38
  • 1
    This will reference the file `JRE_HOME\lib\logging.properties` which should already exist. If you don't see any output, the logging level might be too low, Find the line `java.util.logging.ConsoleHandler.level` and set it to `FINEST`. – user3792852 Aug 30 '16 at 08:51
  • 1
    @user3792852: Good point. I took the liberty of adding it to the answer, hope you don't mind. – sleske Jul 12 '17 at 12:00
  • If you are using Tomcat: bear in mind that the famous catalina.sh (or catalina.bat on Windows) does sets the -Djava.util.logging.config.file which may override your own command line option... – A. Masson Aug 14 '17 at 20:07
16

I've been able to log all SSL traffic implementing my own SSLSocketFactory on top of the default one.

This worked for me because all of our connections are using HTTPS and we can set the socket factory with the method HttpsURLConnection.setSSLSocketFactory.

A more complete solution that enables monitoring on all sockets can be found at http://www.javaspecialists.eu/archive/Issue169.html Thanks to Lawrence Dol for pointing in the right direction of using Socket.setSocketImplFactory

Here is my not ready for production code:

public class WireLogSSLSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory delegate;

    public WireLogSSLSocketFactory(SSLSocketFactory sf0) {
        this.delegate = sf0;
    }

    public Socket createSocket(Socket s, String host, int port,
            boolean autoClose) throws IOException {
        return new WireLogSocket((SSLSocket) delegate.createSocket(s, host, port, autoClose));
    }

    /*
    ...
    */

    private static class WireLogSocket extends SSLSocket {

        private SSLSocket delegate;

        public WireLogSocket(SSLSocket s) {
            this.delegate = s;
        }

        public OutputStream getOutputStream() throws IOException {
            return new LoggingOutputStream(delegate.getOutputStream());
        }

        /*
        ...
        */

        private static class LoggingOutputStream extends FilterOutputStream {
            private static final Logger logger = Logger.getLogger(WireLogSocket.LoggingOutputStream.class);
            //I'm using a fixed charset because my app always uses the same. 
            private static final String CHARSET = "ISO-8859-1";
            private StringBuffer sb = new StringBuffer();

            public LoggingOutputStream(OutputStream out) {
                super(out);
            }

            public void write(byte[] b, int off, int len)
                    throws IOException {
                sb.append(new String(b, off, len, CHARSET));
                logger.info("\n" + sb.toString());
                out.write(b, off, len);
            }

            public void write(int b) throws IOException {
                sb.append(b);
                logger.info("\n" + sb.toString());
                out.write(b);
            }

            public void close() throws IOException {
                logger.info("\n" + sb.toString());
                super.close();
            }
        }
    }
}
Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
Serxipc
  • 6,639
  • 9
  • 40
  • 51
  • 1
    Socket.setSocketImplFactory(), for non-SSL, maybe?? – Lawrence Dol Feb 23 '10 at 04:06
  • 1
    @Serhill: Some detailed examples and/or code excerpts might earn you the accept. – Lawrence Dol Feb 23 '10 at 04:07
  • I think "SSL traffic implementing my own SSLSocketFactory on top of the default one" solution from javaspecialist is best one. – Gladwin Burboz Feb 24 '10 at 02:27
  • 1
    Also use system property "-Dssl.SocketFactory.provider=WireLogSSLSoketFactory" [http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#InstallationAndCustomization]. – Gladwin Burboz Feb 24 '10 at 03:48
  • See if you can implement some framework that uses interceptor pattern here. You implement different interceptors and register them with framework so as to intercept certain api's before delegating to next one. Similar to FilterChain in servlet technology. – Gladwin Burboz Feb 24 '10 at 13:51
  • 2
    @Serhill: Trying to use this class; added all the necessary overrides and set the HttpsURLConnection socketFactory to an instance of this class (instantiated with the default SocketFactory). However, it does not connect when calling HttpsURLConnection.getOutputStream() whereas it does connect at that point using the default SSLSocketFactory. I must be missing something. Any ideas? – Brian Reinhold Aug 27 '15 at 09:24
10

Solution#1: Use Decorator Pattern

You will have to use Decorator pattern on HttpURLConnection class to extend it's functionality. Then override all HttpURLConnection method(s) and delegate operation to Component pointer as well as capture required information and log it.

Also make sure you override parent class URLConnection.getOutputStream() : OutputStream and URLConnection.html#getInputStream() : InputStream methods to return decorated OutputStream and InputStream objects as well.

.

Solution#2: Use custom, in-memory http proxy

Write a simple http proxy server and have it start in it's separate thread during application startup and initialization. See Example simple proxy server.

Have your application configured to use above HTTP proxy for all your requests. See configuring Java to use Proxies.

Now all your traffic is going through above proxy, just like how it happens in fiddler. Hence you have access to raw http stream "from client to server" as well as "back from server to client". You will have to interpret this raw information and log it as required.

Update: Use HTTP Proxy as Adapter to SSL based Web Server.

  == Client System =============================== 
  |                                              | 
  |    ------------------------------            | 
  |   |                              |           | 
  |   |    Java process              |           | 
  |   |                       ----   |           | 
  |   |        ----------    |    |  |           | 
  |   |       |          |    -O  |  |           | 
  |   |       |  Logging |        |  |           | 
  |   |       |   Proxy <---HTTP--   |    -----  | 
  |   |       |  Adapter |           |   |     | | 
  |   |       |  Thread o------------------>   | | 
  |   |       |        o |           |   |     | | 
  |   |        --------|-            |   | Log | | 
  |   |                |             |    -----  | 
  |    ----------------|-------------            | 
  |                    |                         | 
  =====================|========================== 
                       |                           
                       |                           
                     HTTPS                         
                      SSL                          
                       |                           
  == Server System ====|========================== 
  |                    |                         | 
  |    ----------------|----------------         | 
  |   |                V                |        | 
  |   |                                 |        | 
  |   |   Web Server                    |        | 
  |   |                                 |        | 
  |    ---------------------------------         | 
  |                                              | 
  ================================================ 
Gladwin Burboz
  • 3,519
  • 16
  • 15
  • Looking at the JDK6 source the headers are sent at the private method sun.net.www.protocol.http.HttpURLConnection.writeRequests. Using a decorated HttpURLConnection I will be able to log the method calls and stream content, but I will not have access to the underlying sun.net.HttpClient instance that is responsible of creating the socket and writing to it. – Serxipc Feb 19 '10 at 12:12
  • Added Solution#2: Use custom, in-memory http proxy – Gladwin Burboz Feb 19 '10 at 16:31
  • A custom proxy can not handle ssl. That is the reason I'm looking to log the traffic from java directly instead of using fiddler. – Serxipc Feb 22 '10 at 18:33
  • For SSL see my update "Use HTTP Proxy as Adapter to SSL based Web Server." – Gladwin Burboz Feb 24 '10 at 01:53
  • Thanks for your effort. The problem with sniffing SSL through a proxy is that in order to view the unencrypted content the certificate trust chain must be broken. Fiddler is able to log SSL because it generates fake server certificates. If I do the same directly on our app, before it is encrypted, we will not break the certificate chain. – Serxipc Feb 24 '10 at 09:11
  • +one just because of the "drawing" :o) – Lonzak Jan 13 '21 at 13:06
7

In Linux you can run the VM under strace:

strace -o strace.out -s 4096 -e trace=network -f java ...

weberjn
  • 1,840
  • 20
  • 24
7

To refresh with Java 8 environment:

Following @sleske answer

System.setProperty("javax.net.debug","all");

worked for me out of the box.

Also was @weberjn suggestion of

strace -o strace.out -s 4096 -e trace=network -f java

but not useful if handling with SSL traffic as it dumps encoded stream.

All other code tricks did not work for me, but maybe not trying enough hard.

pl.square
  • 499
  • 5
  • 5
2

I don't think you can do that automatically, but you could subclass FilterOutputStream and FilterInputStream with the HttpUrlConnection's output and input streams as parameters. Then as bytes are written/read, log them as well as pass them through to the underlying streams.

Adam Batkin
  • 51,711
  • 9
  • 123
  • 115
  • Will I be able to capture the http headers with this solution? As I understand your proposal only the post or get contents will pass through the streams. – Serxipc Sep 19 '09 at 20:01
  • @Serhii this will include everything that goes in and out, so headers included. – Bozho Feb 21 '10 at 07:48
  • 2
    @Bozho: Actually, I don't think it will include the headers. By the time you get the streams, the headers are already sent (for the output stream) or received and parsed (for the input stream). – Lawrence Dol Feb 23 '10 at 04:00
2

What about using AspectJ to insert a Pointcut to add logging advice around a method? I believe AspectJ can weave it's way into private/protected methods.

It appears that sun.net.www.protocol.http.HttpURLConnection.writeRequest may call sun.net.www.http.HttpClient.writeRequest which takes the MessageHeader Object as an input so that would be your target.

In the end this might work but will be awfully fragile and only work on the Sun JVM; and really you could only trust the exact version you are using.

danpaq
  • 324
  • 1
  • 5
2

On the off chance, that you're only interested in getting at the content on the wire (headers, body etc), you might want to give wireshark a go.

This has the advantage of not having to change any code, though if enabling logging through code was what you're after, this answer is not applicable.

beny23
  • 34,390
  • 5
  • 82
  • 85