Invariant Properties

  • rss
  • Home

Capturing Application System.out With jQuery

Bear Giles | March 19, 2014

One of my first tasks at my new job has involved cleaning up an existing data preprocessing application. The legacy version had to be run via ant(!) with a very nasty dependency tree. After a bottle or two of Tylenol I reduced that to a standalone jar and two configuration files.

Which GUI is best?

That’s a good first step but it still requires the user to manually edit the configuration files. Could we do better?

The old school thought is that we can use X/Motif. No, wait, not that old old school. Swing. We can use swing!

Ugh. I’m not sure that’s much of an improvement.

The new school thought is that we should be able to use our standard browsers. More people can do more things more quickly leveraging the standard javascript frameworks than using swing. It’s not even close – from a management perspective (how hard to find qualified people? how hard to maintain?) it’s easily a 100:1 benefit. Don’t be mistaken – there are times when there’s no substitute for swing or a comparable framework. But an internal application that will only be used by us and a Professional Services Group? Browser with javascript, without a doubt.

Embedded Jetty and AWT Desktop class

Our first step is to launch an embedded jetty server and the associated browser. This is a very easy process:

  1. public class WebWrapper {
  2.     private Server server = new Server(8080);
  3.  
  4.     /**
  5.      * Set up embedded Jetty server. We don't need a full webapp, typically
  6.      * just a handler for static content (html, css, javascript) and maybe
  7.      * 6-10 handlers for REST calls.
  8.      */
  9.     public void startWebServer() throws Exception {
  10.         // set up Jetty handlers...
  11.         final HandlerWrapper handler1 = ...
  12.         final HandlerWrapper handler2 = ...
  13.         final HandlerWrapper handler3 = ...
  14.        
  15.         // register handlers.
  16.         final HandlerList handlers = new HandlerList();
  17.         handlers.setHandlers(new Handler[] { handler1, handler2, handler3, new DefaultHandler() });
  18.         server.setHandler(handlers);
  19.  
  20.         // start server, wait for it to be fully launched.
  21.         server.start();
  22.         server.join();
  23.     }
  24.  
  25.     /**
  26.      * Main application - start web server then launch user's default browser
  27.      */
  28.     public static void main(String[] args) throws Exception {
  29.         final WebWrapper w = new WebWrapper();
  30.         if (Desktop.isDesktopSupported()) {
  31.             w.startWebServer();
  32.             final URI uri = URI.create("http://localhost:8080/");
  33.             Desktop.getDesktop().browse(uri);
  34.         }
  35.     }
  36. }
public class WebWrapper {
    private Server server = new Server(8080);

    /**
     * Set up embedded Jetty server. We don't need a full webapp, typically
     * just a handler for static content (html, css, javascript) and maybe
     * 6-10 handlers for REST calls.
     */
    public void startWebServer() throws Exception {
        // set up Jetty handlers...
        final HandlerWrapper handler1 = ...
        final HandlerWrapper handler2 = ...
        final HandlerWrapper handler3 = ...
       
        // register handlers. 
        final HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] { handler1, handler2, handler3, new DefaultHandler() });
        server.setHandler(handlers);

        // start server, wait for it to be fully launched.
        server.start();
        server.join();
    }

    /**
     * Main application - start web server then launch user's default browser
     */
    public static void main(String[] args) throws Exception {
        final WebWrapper w = new WebWrapper();
        if (Desktop.isDesktopSupported()) {
            w.startWebServer();
            final URI uri = URI.create("http://localhost:8080/");
            Desktop.getDesktop().browse(uri);
        }
    }
}

It’s important to remember that this is the frontend to a desktop application, not a general-purpose webapp, and we can take advantage of that. For instance we don’t need to worry about network latency. IMPORTANT: we still need to code defensively unless we can guarantee that nobody else will access the Jetty instance! Fortunately it is not hard to transparently add authentication information as we launch the browser.

Sidenote: we don’t have to use a full webapp with embedded Jetty but it is an option – this could be a convenient midway point between running an application traditionally and running it remotely as a classic webapp.

We now need a Jetty handler to launch our application. I’m demonstrating the case where we’re running the application asynchronously – the AJAX call returns immediately and we’ll pick up the results later.

  1. public class ApplicationHandler extends HandlerWrapper {
  2.     private WrappedPrintStream writer;
  3.  
  4.     /**
  5.      * Application handler.
  6.      *
  7.      * @param target
  8.      * @param baseRequest
  9.      * @param request
  10.      * @param response
  11.      * @throws IOException
  12.      * @throws ServletException
  13.      */
  14.     public void handle(String target, final Request baseRequest, HttpServletRequest request,
  15.             HttpServletResponse response) throws IOException, ServletException {
  16.  
  17.         final String path = request.getPathInfo();
  18.         if ("POST".equals(request.getMethod())) {
  19.             if ("/rest/doIt".equals(path)) {
  20.                 doIt(target, baseRequest, request, response);
  21.                 baseRequest.setHandled(true);
  22.             } else if ("/rest/poll".equals(path)) {
  23.                 poll(target, baseRequest, request, response);
  24.                 baseRequest.setHandled(true);
  25.             }
  26.         }
  27.     }
  28.  
  29.     /**
  30.      * "Do it" - launch long-lived application in separate thread.
  31.      *
  32.      * @param target
  33.      * @param baseRequest
  34.      * @param request
  35.      * @param response
  36.      * @throws IOException
  37.      * @throws ServletException
  38.      */
  39.     public void doIt(String target, final Request baseRequest, HttpServletRequest request,
  40.             HttpServletResponse response) throws IOException, ServletException {
  41.  
  42.         response.setContentType("text/plain;charset=utf-8");
  43.  
  44.         // read json payload
  45.         String json = null;
  46.         final Scanner s = null;
  47.         try {
  48.             s = new Scanner(request.getInputStream());
  49.             s.useDelimiter("^Z");
  50.             if (s.hasNext()) {
  51.                 json = s.next();
  52.             }
  53.         } finally {
  54.             if (s != null) {
  55.                 s.close();
  56.             }
  57.         }
  58.  
  59.         // return an error response if there's no payload
  60.         if (s == null) {
  61.             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  62.             return;
  63.         }
  64.  
  65.         // create Jackson JSON mapper.
  66.         final ObjectMapper mapper = new ObjectMapper();
  67.         final JsonNode root = mapper.readTree(json);
  68.  
  69.         // wrap System.out so we write to both command shell and browser.
  70.         final WrappedPrintStream wps = WrappedPrintStream.newInstance(System.out);
  71.  
  72.         try {
  73.             Thread t = new Thread(new Runnable() {
  74.                 public void run() {
  75.                     try {
  76.                         // reset System.out so it points to our wrapper.
  77.                         System.setOut(wps);
  78.  
  79.                         // create the application, run it.
  80.                         final DoItApplication app = new DoItApplication(root);
  81.                         app.init(root);
  82.                         app.execute();
  83.                     } catch (IOException e) {
  84.                         e.printStackTrace(wps);
  85.                     } finally {
  86.                         // mark the wrapper closed.
  87.                         wps.setClosed(true);
  88.                         // reset System.out
  89.                         System.setOut(wps.getOut());
  90.                     }
  91.                 }
  92.             });
  93.  
  94.             // cache the writer
  95.             writer = wps;
  96.  
  97.             // start the thread
  98.             t.start();
  99.  
  100.             // a typical response might be the threadId of the new process.
  101.             response.setStatus(HttpServletResponse.SC_OK);
  102.             response.setContentType("text/plain;charset=utf-8");
  103.             final PrintWriter pw = response.getWriter();
  104.             pw.printf("{ \"threadId\": \"%s\" }\n", t.getId());
  105.         } catch (Exception e) {
  106.             // oops - something went wrong.
  107.             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  108.             response.setContentType("text/plain;charset=utf-8");
  109.             final PrintWriter pw = response.getWriter();
  110.             e.printStackTrace(pw);
  111.             e.printStackTrace();
  112.         }
  113.     }
  114.  
  115.     ...
public class ApplicationHandler extends HandlerWrapper {
    private WrappedPrintStream writer;

    /**
     * Application handler.
     * 
     * @param target
     * @param baseRequest
     * @param request
     * @param response
     * @throws IOException
     * @throws ServletException
     */
    public void handle(String target, final Request baseRequest, HttpServletRequest request,
            HttpServletResponse response) throws IOException, ServletException {

        final String path = request.getPathInfo();
        if ("POST".equals(request.getMethod())) {
            if ("/rest/doIt".equals(path)) {
                doIt(target, baseRequest, request, response);
                baseRequest.setHandled(true);
            } else if ("/rest/poll".equals(path)) {
                poll(target, baseRequest, request, response);
                baseRequest.setHandled(true);
            }
        }
    }

    /**
     * "Do it" - launch long-lived application in separate thread.
     * 
     * @param target
     * @param baseRequest
     * @param request
     * @param response
     * @throws IOException
     * @throws ServletException
     */
    public void doIt(String target, final Request baseRequest, HttpServletRequest request,
            HttpServletResponse response) throws IOException, ServletException {

        response.setContentType("text/plain;charset=utf-8");

        // read json payload
        String json = null;
        final Scanner s = null;
        try {
            s = new Scanner(request.getInputStream());
            s.useDelimiter("^Z");
            if (s.hasNext()) {
                json = s.next();
            }
        } finally {
            if (s != null) {
                s.close();
            }
        }

        // return an error response if there's no payload
        if (s == null) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        // create Jackson JSON mapper.
        final ObjectMapper mapper = new ObjectMapper();
        final JsonNode root = mapper.readTree(json);

        // wrap System.out so we write to both command shell and browser.
        final WrappedPrintStream wps = WrappedPrintStream.newInstance(System.out);

        try {
            Thread t = new Thread(new Runnable() {
                public void run() {
                    try {
                        // reset System.out so it points to our wrapper.
                        System.setOut(wps);

                        // create the application, run it.
                        final DoItApplication app = new DoItApplication(root);
                        app.init(root);
                        app.execute();
                    } catch (IOException e) {
                        e.printStackTrace(wps);
                    } finally {
                        // mark the wrapper closed.
                        wps.setClosed(true);
                        // reset System.out
                        System.setOut(wps.getOut());
                    }
                }
            });

            // cache the writer
            writer = wps;

            // start the thread
            t.start();

            // a typical response might be the threadId of the new process.
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("text/plain;charset=utf-8");
            final PrintWriter pw = response.getWriter();
            pw.printf("{ \"threadId\": \"%s\" }\n", t.getId());
        } catch (Exception e) {
            // oops - something went wrong.
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.setContentType("text/plain;charset=utf-8");
            final PrintWriter pw = response.getWriter();
            e.printStackTrace(pw);
            e.printStackTrace();
        }
    }

    ...

We also need a handler that returns anything recently written to System.out. The handler needs to be able to distinguish between “no data” and “no more data ever”, and a more robust solution would probably limit the amount of data transferred in any single call. It doesn’t really matter when you’re running the app and browser on the same machine but it will probably not take long for users to realize that they can access the application remotely unless you write your handlers to explicitly check the ‘remote ip’ of the caller.

  1.     /**
  2.      * Poll for new content.
  3.      *
  4.      * @param target
  5.      * @param baseRequest
  6.      * @param request
  7.      * @param response
  8.      * @throws IOException
  9.      * @throws ServletException
  10.      */
  11.     public void poll(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response)
  12.             throws IOException, ServletException {
  13.         response.setContentType("text/plain;charset=utf-8");
  14.  
  15.         if (writer != null) {
  16.             final String s = writer.poll();
  17.             if (s != null) {
  18.                 response.setStatus(HttpServletResponse.SC_OK);
  19.                 response.getWriter().print(s);
  20.             } else {
  21.                 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  22.             }
  23.         } else {
  24.             response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  25.         }
  26.     }
  27. }
    /**
     * Poll for new content.
     * 
     * @param target
     * @param baseRequest
     * @param request
     * @param response
     * @throws IOException
     * @throws ServletException
     */
    public void poll(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        response.setContentType("text/plain;charset=utf-8");

        if (writer != null) {
            final String s = writer.poll();
            if (s != null) {
                response.setStatus(HttpServletResponse.SC_OK);
                response.getWriter().print(s);
            } else {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            }
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}

Finally we need a way to capture the contents of our output stream. Again this should be familiar to any Java developer.

  1. public class WrappedPrintStream extends PrintStream {
  2.     private final PrintStream ps;
  3.     private final ByteArrayOutputStream baos;
  4.     private final Lock lock = new ReentrantLock();
  5.     private boolean closed = false;
  6.  
  7.     /**
  8.      * Public factory method. We need to do this because of need to call super
  9.      * constructor below.
  10.      */
  11.     public static WrappedPrintStream newInstance(PrintStream ps) {
  12.         return new WrappedPrintStream(ps, new ByteArrayOutputStream());
  13.     }
  14.  
  15.     /**
  16.      * Private constructor.
  17.      *
  18.      * @param pw
  19.      */
  20.     private WrappedPrintStream(PrintStream ps, ByteArrayOutputStream baos) {
  21.         super(baos);
  22.         this.baos = baos;
  23.         this.ps = ps;
  24.     }
  25.  
  26.     /**
  27.      * Has this writer been marked closed?
  28.      *
  29.      * @return
  30.      */
  31.     public boolean isClosed() {
  32.         return closed;
  33.     }
  34.  
  35.     /**
  36.      * Set this writer closed.
  37.      *
  38.      * @param closed
  39.      */
  40.     public void setClosed(boolean closed) {
  41.         this.closed = closed;
  42.     }
  43.  
  44.     /**
  45.      * Get original output stream.
  46.      */
  47.     public PrintStream getOut() {
  48.         return ps;
  49.     }
  50.  
  51.     /**
  52.      * One of many methods...
  53.      */
  54.     @Override
  55.     public void println(String str) {
  56.         lock.lock();
  57.         try {
  58.             baos.write(str.getBytes());
  59.             baos.write("\n".getBytes());
  60.             super.println(str);
  61.         } finally {
  62.             lock.unlock();
  63.         }
  64.     }
  65.  
  66.     /**
  67.      * Flush the output stream.
  68.      */
  69.     @Override
  70.     public void flush() {
  71.         lock.lock();
  72.         try {
  73.             super.flush();
  74.         } finally {
  75.             lock.unlock();
  76.         }
  77.     }
  78.  
  79.     /**
  80.      * Poll writer for new content since last call.
  81.      *
  82.      * @return
  83.      * @throws RuntimeException
  84.      */
  85.     public String poll() throws RuntimeException, IOException {
  86.         String s = "";
  87.         lock.lock();
  88.         try {
  89.             s = new String(baos.toByteArray());
  90.             baos.reset();
  91.         } finally {
  92.             lock.unlock();
  93.         }
  94.  
  95.         // write copy to prior printstream, typically system.out.
  96.         ps.print(s);
  97.         ps.flush();
  98.  
  99.         if (isClosed() && s.isEmpty()) {
  100.             return null;
  101.         }
  102.  
  103.         return s;
  104.     }
  105. }
public class WrappedPrintStream extends PrintStream {
    private final PrintStream ps;
    private final ByteArrayOutputStream baos;
    private final Lock lock = new ReentrantLock();
    private boolean closed = false;

    /**
     * Public factory method. We need to do this because of need to call super
     * constructor below.
     */
    public static WrappedPrintStream newInstance(PrintStream ps) {
        return new WrappedPrintStream(ps, new ByteArrayOutputStream());
    }

    /**
     * Private constructor.
     * 
     * @param pw
     */
    private WrappedPrintStream(PrintStream ps, ByteArrayOutputStream baos) {
        super(baos);
        this.baos = baos;
        this.ps = ps;
    }

    /**
     * Has this writer been marked closed?
     * 
     * @return
     */
    public boolean isClosed() {
        return closed;
    }

    /**
     * Set this writer closed.
     * 
     * @param closed
     */
    public void setClosed(boolean closed) {
        this.closed = closed;
    }

    /**
     * Get original output stream.
     */
    public PrintStream getOut() {
        return ps;
    }

    /**
     * One of many methods...
     */
    @Override
    public void println(String str) {
        lock.lock();
        try {
            baos.write(str.getBytes());
            baos.write("\n".getBytes());
            super.println(str);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Flush the output stream.
     */
    @Override
    public void flush() {
        lock.lock();
        try {
            super.flush();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Poll writer for new content since last call.
     * 
     * @return
     * @throws RuntimeException
     */
    public String poll() throws RuntimeException, IOException {
        String s = "";
        lock.lock();
        try {
            s = new String(baos.toByteArray());
            baos.reset();
        } finally {
            lock.unlock();
        }

        // write copy to prior printstream, typically system.out.
        ps.print(s);
        ps.flush();

        if (isClosed() && s.isEmpty()) {
            return null;
        }

        return s;
    }
}

I’m using an explicit lock as per Effective Java.

As a practical matter it will probably be enough to only wrap println(), println(String), print(String), and printf(String, Object…). On the other hand the best way to guarantee that your class will be reused in unexpected ways is to cut corners!

In the browser

For the sake of presentation I am assuming that we already have a working browser-enabled app and we only need to add a “console” that shows the contents of the application’s System.out.

We start with a one-line addition to our HTML:

  1. <div id="content"/>
<div id="content"/>

with a bit of CSS chrome:

  1. #console {
  2.   height: 360px;
  3.   overflow: auto;
  4.   font-family: monospace;
  5.   background-position: center center;
  6.   //background-image: url('/images/ajax_loader_red_256.gif');
  7.   background-repeat: no-repeat;
  8. }
#console {
  height: 360px;
  overflow: auto;
  font-family: monospace;
  background-position: center center;
  //background-image: url('/images/ajax_loader_red_256.gif');
  background-repeat: no-repeat;
}

The background image is a spinner that shows when the browser is waiting for the application. We don’t need to set the actual image here – we’ll do that with jQuery later – but it’s a nice reminder if someone is tempted to add their own background image.

We also need to add a bit of javascript to our pages. In this case I’m also overriding the standard javascript console.log() method but this isn’t required.

  1. $(document).ready(function() {
  2.     // override console so it appears on the website.
  3.     console.log = function(str) {
  4.         $('#console').append(str);
  5.         $('#console').append('<br/>');
  6.     }
  7.                        
  8.     console.logSuccess = function(str) {
  9.         $('#console').append('<font color="green">' + str + '</font><br/>');
  10.     }
  11.  
  12.     console.logApp = function(str) {
  13.         $('#console').append('<font color="blue">' + str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>') + '</font>');
  14.     }
  15.  
  16.     console.logError = function(str) {
  17.         $('#console').append('<font color="red">' + str + '</font><br/>');
  18.     }
  19. }
$(document).ready(function() {
    // override console so it appears on the website.
    console.log = function(str) {
        $('#console').append(str);
        $('#console').append('<br/>');
    }
						
    console.logSuccess = function(str) {
        $('#console').append('<font color="green">' + str + '</font><br/>');
    }

    console.logApp = function(str) {
        $('#console').append('<font color="blue">' + str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>') + '</font>');
    }

    console.logError = function(str) {
        $('#console').append('<font color="red">' + str + '</font><br/>');
    }
}

Updating the AJAX call

Finally we need to modify our AJAX calls back to the application. Our original code probably looks something like this:

  1. function doIt(json) {
  2.     $.ajax({
  3.         url : '/rest/doIt',
  4.         processData : false,
  5.         type : 'POST',
  6.         contentType : 'application/json',
  7.         data : json,
  8.         success : function(response, statusText, xhr) {
  9.             console.log("> doit: " + statusText);
  10.         },
  11.         error : function(xhr, statusText, exception) {
  12.             console.log("> doit: " + statusText + ' (' + exception + ')');
  13.         }
  14.     });
  15. }
function doIt(json) {
    $.ajax({
        url : '/rest/doIt',
        processData : false,
        type : 'POST',
        contentType : 'application/json',
        data : json,
        success : function(response, statusText, xhr) {
            console.log("> doit: " + statusText);
        },
        error : function(xhr, statusText, exception) {
            console.log("> doit: " + statusText + ' (' + exception + ')');
        }
    });
}

We want to add a beforeSend function that sets the background spinner and starts polling the application for updates.

  1. //
  2. // Poll the application's system.out, write it to console.
  3. //
  4. function pollSystemOut() {
  5.     $.ajax({
  6.         url : '/rest/poll',
  7.         type : 'POST',
  8.         success : function(response, statusText, xhr) {
  9.             // update console, schedule next call.
  10.             console.logApp(response);
  11.             setTimeout(pollSystemOut, 250);
  12.         },
  13.         error : function(xhr, statusText, exception) {
  14.             // turn off spinner
  15.             $('#console').css('background-image', '');
  16.         }
  17.     });
  18. }
  19.  
  20. //
  21. // Our existing function adds a 'beforeSend' function.
  22. //
  23. function doIt(json) {
  24.     $.ajax({
  25.         url : '/rest/buildDictionary',
  26.         processData : false,
  27.         type : 'POST',
  28.         contentType : 'application/json',
  29.         data : json,
  30.         beforeSend : function() {
  31.             // show spinner
  32.             $('#console').css('background-image', "url('/images/ajax_loader_red_256.gif')");
  33.             // initial delay in case the app takes a moment to start up.
  34.             setTimeout(pollSystemOut, 500);
  35.             return true;
  36.         },
  37.         success : function(response, statusText, xhr) {
  38.             console.logSuccess("> doit: " + statusText);
  39.         },
  40.         error : function(xhr, statusText, exception) {
  41.             console.logError("> doit: " + statusText + ' (' + exception + ')');
  42.             $('#console').css('background-image', '');
  43.         }
  44.     });
  45. }
//
// Poll the application's system.out, write it to console.
//
function pollSystemOut() {
    $.ajax({
        url : '/rest/poll',
        type : 'POST',
        success : function(response, statusText, xhr) {
            // update console, schedule next call.
            console.logApp(response);
            setTimeout(pollSystemOut, 250);
        },
        error : function(xhr, statusText, exception) {
            // turn off spinner
            $('#console').css('background-image', '');
        }
    });
}

//
// Our existing function adds a 'beforeSend' function.
//
function doIt(json) {
    $.ajax({
        url : '/rest/buildDictionary',
        processData : false,
        type : 'POST',
        contentType : 'application/json',
        data : json,
        beforeSend : function() {
            // show spinner
            $('#console').css('background-image', "url('/images/ajax_loader_red_256.gif')");
            // initial delay in case the app takes a moment to start up.
            setTimeout(pollSystemOut, 500);
            return true;
        },
        success : function(response, statusText, xhr) {
            console.logSuccess("> doit: " + statusText);
        },
        error : function(xhr, statusText, exception) {
            console.logError("> doit: " + statusText + ' (' + exception + ')');
            $('#console').css('background-image', '');
        }
    });
}

Conclusion

This is not the only way to capture an application’s System.out. It may not even be the best way. But it’s probably a Good Enough balance between cost and performance for most uses, esp. with a few obvious enhancements.

Update: 4/10/14

A little Google goes a long way. A simple change will keep the page scrolled to the bottom. This may work a little too well since a user won’t be able to scroll back while the data is still updating but that often won’t be a problem.

  1. $(document).ready(function() {
  2.     // override console so it appears on the website.
  3.     console.log = function(str) {
  4.         $('#console').append(str);
  5.         $('#console').append('<br/>');
  6.         $('#console').scrollTop($('#console')[0].scrollHeight);
  7.     }
  8.                        
  9.     console.logSuccess = function(str) {
  10.         $('#console').append('<font color="green">' + str + '</font><br/>');
  11.         $('#console').scrollTop($('#console')[0].scrollHeight);
  12.     }
  13.  
  14.     console.logApp = function(str) {
  15.         $('#console').append('<font color="blue">' + str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>') + '</font>');
  16.         $('#console').scrollTop($('#console')[0].scrollHeight);
  17.     }
  18.  
  19.     console.logError = function(str) {
  20.         $('#console').append('<font color="red">' + str + '</font><br/>');
  21.         $('#console').scrollTop($('#console')[0].scrollHeight);
  22.     }
  23. }
$(document).ready(function() {
    // override console so it appears on the website.
    console.log = function(str) {
        $('#console').append(str);
        $('#console').append('<br/>');
        $('#console').scrollTop($('#console')[0].scrollHeight);
    }
						
    console.logSuccess = function(str) {
        $('#console').append('<font color="green">' + str + '</font><br/>');
        $('#console').scrollTop($('#console')[0].scrollHeight);
    }

    console.logApp = function(str) {
        $('#console').append('<font color="blue">' + str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>') + '</font>');
        $('#console').scrollTop($('#console')[0].scrollHeight);
    }

    console.logError = function(str) {
        $('#console').append('<font color="red">' + str + '</font><br/>');
        $('#console').scrollTop($('#console')[0].scrollHeight);
    }
}
Comments
No Comments »
Categories
java, javascript
Comments rss Comments rss
Trackback Trackback

Archives

  • May 2020 (1)
  • March 2019 (1)
  • August 2018 (1)
  • May 2018 (1)
  • February 2018 (1)
  • November 2017 (4)
  • January 2017 (3)
  • June 2016 (1)
  • May 2016 (1)
  • April 2016 (2)
  • March 2016 (1)
  • February 2016 (3)
  • January 2016 (6)
  • December 2015 (2)
  • November 2015 (3)
  • October 2015 (2)
  • August 2015 (4)
  • July 2015 (2)
  • June 2015 (2)
  • January 2015 (1)
  • December 2014 (6)
  • October 2014 (1)
  • September 2014 (2)
  • August 2014 (1)
  • July 2014 (1)
  • June 2014 (2)
  • May 2014 (2)
  • April 2014 (1)
  • March 2014 (1)
  • February 2014 (3)
  • January 2014 (6)
  • December 2013 (13)
  • November 2013 (6)
  • October 2013 (3)
  • September 2013 (2)
  • August 2013 (5)
  • June 2013 (1)
  • May 2013 (2)
  • March 2013 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • May 2012 (6)
  • January 2012 (2)
  • December 2011 (12)
  • July 2011 (1)
  • June 2011 (2)
  • May 2011 (5)
  • April 2011 (6)
  • March 2011 (4)
  • February 2011 (3)
  • October 2010 (6)
  • September 2010 (8)

Recent Posts

  • 8-bit Breadboard Computer: Good Encapsulation!
  • Where are all the posts?
  • Better Ad Blocking Through Pi-Hole and Local Caching
  • The difference between APIs and SPIs
  • Hadoop: User Impersonation with Kerberos Authentication

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Pages

  • About Me
  • Notebook: Common XML Tasks
  • Notebook: Database/Webapp Security
  • Notebook: Development Tips

Syndication

Java Code Geeks

Know Your Rights

Support Bloggers' Rights
Demand Your dotRIGHTS

Security

  • Dark Reading
  • Krebs On Security Krebs On Security
  • Naked Security Naked Security
  • Schneier on Security Schneier on Security
  • TaoSecurity TaoSecurity

Politics

  • ACLU ACLU
  • EFF EFF

News

  • Ars technica Ars technica
  • Kevin Drum at Mother Jones Kevin Drum at Mother Jones
  • Raw Story Raw Story
  • Tech Dirt Tech Dirt
  • Vice Vice

Spam Blocked

53,793 spam blocked by Akismet
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox