Tuesday, 27 March 2012

Dansguardian Log Analyser

I recently started to learn Dojo and thought that writing an application would be a fun way of doing so.

When writing a new web application I wanted to create something useful, so I reflected back to when I installed Dansguardian on the My Book Live.

After installation I found out there wasn't any good log analyser available (at least not outside webmin). So when deciding to write an application, the choice was easy. The application turned out to be "Dansguardian Log Analyser" which is based on Dojo and PHP.

As with my other small projects I published the final product on github for everyone's use.

Read on for details and installation instructions...


Details

There's three sections available:
  • Summary
    Shows a graph of today's usage.
  • Denied requests
    A list of today's denied requests. By clicking on a row it will open the denied page in the iframe below the grid.
  • Realtime log
    This simply tail the Dansguardian logfile to the browser. It displays all requests logged in the access.log from the time the tab is selected.


Screenshots



Installation

Download the package from here.
Copy dla/ directory to your web/proxy server running Dansguardian. (E.g. to /var/www/dla/)
Access it through: http://your.web.server/dla/

Notes

Make sure "logfileformat = 1" is set in dansguardian.conf
If "loglocation" is change from default, update getDansguardianLog.php with the correct path.


Thursday, 1 March 2012

My Book Live with Squid and fakeauth

Next for the new My Book Live was to install and configure Squid. Since MBL runs Debian, it is fairly straight forward to get Squid up and running. But here we also want to setup fakeauth which complicate things a little bit.

The reason for using fakeauth is to authenticate users on a home network without the need for any central authentication database. It is far from secure, but for the sole purpose of identifying the user it is good enough.


Install Squid3
First we need to update the package repositories from where we wish to install.
Simply run the following in the shell:
echo 'deb http://ftp.us.debian.org/debian/ squeeze main
deb-src http://ftp.us.debian.org/debian/ squeeze main
#deb http://ftp.us.debian.org/debian/ sid main
#deb http://ftp.us.debian.org/debian/ experimental main' > /etc/apt/sources.list
Then execute the next line to update the local cache and install Squid 3:
apt-get update && apt-get -y install squid3
This will download and install Squid3 followed by starting it up listening on port 3128.


Install fakeauth
Fakeauth is part of Squid3 but it is not bundled with the installable package. This forces us to build it from source.

Let's start by installing dependencies:
apt-get -y install gcc g++ make patch
Next we download and unpack the source for Squid 3.1.6:
cd ~/ && apt-get source squid3
tar xzf squid3_3.*.orig.tar.gz && cd squid-*/
Below we build fakeauth with a patch required for the MBL:
./configure
cd compat/ && make
cd ../lib && make
cd ../helpers/ntlm_auth/fakeauth/
echo '279a280,281
>     // Fix for platform
>     auth->flags = le32toh(auth->flags);
282c284
<     debug("ntlmDecodeAuth: usr o(%d) l(%d)\n", auth->user.offset, auth->user.len);
---
>     debug("ntlmDecodeAuth: usr o(%d) l(%d)\n", le32toh(auth->user.offset), le16toh(auth->user.len));
' | patch fakeauth_auth.c
make
This would produce an executable file called fakeauth_auth in the current directory compatible with MBL.
Copy this file to Squid's library path to keep it all in one place:
cp fakeauth_auth /usr/lib/squid3/
chmod 755 /usr/lib/squid3/fakeauth_auth

Configure fakeauth
Fakeauth is a NTLM authentication helper which we configure in /etc/squid3/squid.conf by adding the following:
auth_param ntlm program /usr/lib/squid3/fakeauth_auth -S
auth_param ntlm children 5
auth_param ntlm keep_alive on
This will enable the module but won't restrict any users accessing the proxy.

At approx. line 760 add the following two lines to enable fakeauth module:
(Note that http_access deny needs to be above any other http_access allow to work properly)
acl dummyAuth proxy_auth REQUIRED
http_access deny !dummyAuth all
This basically tells Squid only to allow clients that support NTLM. This will only work for Windows users so additional rules needs to be added to allow other clients to bypass the authentication.

After restarting Squid (/etc/init.d/squid3 restart) the username should now appear in the logfile when using Squid as a proxy:
# tail /var/log/squid3/access.log
1221111156.178    170 10.0.0.9 TCP_MISS/204 351 GET http://www.google.com/csi? charlie DIRECT/74.125.237.112 image/gif





My Book Live not working with iTunes 10.5

I recently bought a My Book Live and wanted to use it as a streaming server for Apple appliances.
During initial setup there was a prompt for a new firmware which I installed straight away. Then after moving some music over to the shared drive, I tried to connect to it in iTunes which obviously failed.

After searching around for a while I found a lot of people experiencing similar issue with not only the MBL, but with a lot of other NAS' as well.
Amongst the suggested resolutions were:
  1. Make sure "Media Serving" option on the share was set to "All"
  2. Under Settings > Media > iTunes, execute the "rescan"

None of them worked, iTunes just showed the spinning wheel as it tried to connect.

Then spending a bit more time checking debug logs from forked-daapd (the iTunes Server) and the network traffic, everything looked good, it just wouldn't work.

Since the firmware already been updated, I didn't have much hope when visiting WD's support page to look for a new firmware to support the iTunes 10.5. Surprisingly there was a much newer version available. After installing it and the library was rescanned, it worked perfectly. Well, for music at least.
As it turns out the iTunes 10.5 and My Book Live currently only support streaming of music. For videos, iTunes 10.2 must be used.


Resolution
Install latest firmware (MyBookLive 02.10.09-124 : Core F/W).

For instructions on how to update the firmware, please follow the instructions provided by WD: http://wdc.custhelp.com/app/answers/detail/a_id/5735


Note
Clearly Apple is causing all these issues for the sole purpose of saying, "You should be using our products!". Time will tell if it is right or wrong.

Monday, 27 February 2012

Flotr2JFExamplesWeb - How to use Flotr2JF with Flotr2

Flotr2JFExamplesWeb is a web project built to illustrate how to use Flotr2JF together with Flotr2. Flotr2 is a branch of Flotr, which is a replacement for Plotr.
Behind the scenes it is very similar interface to Flot, which is why FlotJF was used as a base when creating Flotr2JF and Flotr2JFExamplesWeb.

This is a follow-up article from FlotJFExamplesWeb - How to use FlotJF with Flot.
In the previous article we go through every example one by one and explain the source code. This article assumes you already read this previous article and will only discuss differences between the two.

First, check out the running version here: http://flotr2jfexamplesweb.appspot.com/







Screenshots
ExampleGraph1.java ExampleGraph2.java ExampleGraph3.java
ExampleGraph4.java ExampleGraph5.java ExampleGraph6.java




Getting FlotJFExamplesWeb

The best way is to build it from source and import it to IDE of choice. See instructions under Building: https://github.com/dunse/Flotr2JFExamplesWeb/blob/master/README.md

To just checkout the running examples, download https://github.com/downloads/dunse/Flotr2JFExamplesWeb/Flotr2JFExampleWeb-0.2-SNAPSHOT.war and deploy it to application server of choice.
After deployment open http://hostname/context-root/ (For Tomcat the default would be http://localhost:8080/Flotr2JFExampleWeb-0.2-SNAPSHOT/)







The Application

The application is quite simple. There is a static landing page (index.html) which has links to all the examples. These examples are displayed using example.jsp which we will take a look at first.



example.jsp

First change is the included plotting library, which in this case is Flotr2 on Line 16:
<script src="javascripts/flotr2/flotr2.js" type="text/javascript" charset="UTF-8"></script>


The code to draw the graph is only one function Flotr.draw().
    } )
    
    function update() {
     Flotr.draw(document.getElementById('placeholder'), getGraph(), options);




ExampleGraph1.java

ExampleGraph1 will return a simple graph with one data series based on sin().

This code is identical to FlotJFExamplesWeb so please read FlotJFExamplesWeb - How to use FlotJF with Flot for more information.


ExampleGraph2.java

ExampleGraph2 will return a simple graph with two data series based on sin() and cos()+5 using two y axis.

Flotr2 handles y axes slightly different, so we had to change our GetOptions() method to use the method addY2Axis() on line 18:
  chart.addY2Axis(yAxisRight);


ExampleGraph3.java

ExampleGraph3 will return a multi-type graph with three data series representing line, dot and bar series.

This code is identical to FlotJFExamplesWeb so please refer to FlotJFExamplesWeb - How to use FlotJF with Flot for more information.


ExampleGraph4.java

ExampleGraph4 is a bit more advanced where we use the poll parameter to update the graph every 3 seconds.

Since Flotr2 doesn't autoscale the x axis when using time mode we had to compensate for this by adding line 21:
  xAxis.setTimeFormat("%H:%M:%S");


ExampleGraph5.java

ExampleGraph5 shows the use of colour and opacity gradient.

In this example we put in some additional options available in Flotr2. First we specify the Title which is printed above the plotting area.
Then we specify the y axis title and rotate this 90 degrees. Important is to disable HtmlText as done on line 13 or the text rotation will not be possible.
 chart.setTitle("This is the Graph Title");
 chart.setHtmlText(false);

 Axis yAxis = new Axis();
 yAxis.setTitle("Y Title");
 yAxis.setTitleAngle(90);
 chart.addYAxis(yAxis);

 Axis xAxis = new Axis();
 xAxis.setTitle("X Title");
 chart.addXAxis(xAxis);


ExampleGraph6.java

ExampleGraph6 shows the use of tooltips (Based on Example 3).

To enable a simple tooltip in Flotr2 is much simpler than in Flot. In the Java code we just call useMouse() to enable tooltip on the graph. This is a helper method in Flotr2JF which enables the following mouse options: track, trackAll and relative.
 
 // Enable interaction with grid. (In this case for tooltip)
 chart.useMouse()

Tuesday, 21 February 2012

FlotJFExamplesWeb - How to use FlotJF with Flot

FlotJFExamplesWeb is a web project built to illustrate how to use FlotJF together with Flot. Flot is a pure JavaScript plotting library for jQuery while FlotJF is a Java Framework to generate the JSON data for use with Flot.
FlotJF was built to simplify the process of generating graphs with J2EE applications using Flot.

Check out the running version here: http://flotjfexamplesweb.appspot.com/






Screenshots
ExampleGraph1.java ExampleGraph2.java ExampleGraph3.java
ExampleGraph4.java ExampleGraph5.java ExampleGraph6.java




Getting FlotJFExamplesWeb

The best way is to build it from source and import it to IDE of choice. See instructions under Building: https://github.com/dunse/FlotJFExamplesWeb/blob/master/README.md

To just checkout the running examples, download https://github.com/downloads/dunse/FlotJFExamplesWeb/FlotJFExampleWeb-0.2-SNAPSHOT.war and deploy it to application server of choice.
After deployment open http://hostname/context-root/ (For Tomcat the default would be http://localhost:8080/FlotJFExampleWeb-0.2-SNAPSHOT/)







The Application

The application is quite simple. There is a static landing page (index.html) which has links to all the examples. These examples are displayed using example.jsp which we will take a look at first.



example.jsp

At the top of example.jsp we have some Java code to create session scope variables which we will use when calling the backend system for the correct data.
exampleId is passed from index.html using number 1 to 4. (If not set it default to 1, or we would get a NullPointerException).
poll is either set or not set. If not set it is null which we programmatically check later in the code.
<%
String exampleId = request.getParameter("id");
String poll = request.getParameter("poll");
String tooltip = request.getParameter("tooltip");
if (exampleId == null) {
 exampleId = "1";
}
%>

In the HEAD section the jQuery and Flot scripts needs to be included. Line 15 and 17 are the main scripts that are required while 16 is optional to support Internet Explorer 6 and 7.
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>FlotJF Example <%=exampleId %></title>
<link rel="stylesheet" media="screen" href="stylesheets/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<!--[if lte IE 8]><script src="javascripts/flot/excanvas.min.js" type="text/javascript" charset="UTF-8"></script><![endif]-->
<script src="javascripts/flot/jquery.flot.js" type="text/javascript" charset="UTF-8"></script>
<script src="javascripts/flotjf.tooltip.js" type="text/javascript" charset="UTF-8"></script>
</head>

Line 22 is the <div> container which will be used to draw the graph on screen. We named it placeholder which will be used to reference this object from the JavaScript code.
The custom size set bye the style argument cannot be bigger than the <iframe> that has been specified in index.html.
<body id="example">
<h2>Example <%=exampleId %></h2>
<div id="placeholder" style="width:95%;height:400px;" ></div>

This determine if tooltips should be used. If tooltip is set useTooltip() inside flotjf.tooltip.js is called to bind the necessary events to handle this.
   <% if (tooltip != null) { %>
 useTooltip();
   <% } %>

In the first section of the JavaScript we define data which will hold the JSON data object fetched from the backend server. This data variable is populated using the getGraph() function. This in turn uses jQuery to perform a HTTP POST request to the /GetGraph URI to retrieve the JSON data.
In the code below the 'GetGraph' gets translated to /GetGraph by jQuery.
{example: <%=exampleId %>} will add one request parameter to the HTTP call named example using the exampleId from line 2.
The response from the HTTP POST call is passed back into data and can be used by other parts of the JavaScript and Flot.
   var data = [];
     function getGraph() {
      $.post('GetGraph',{example: <%=exampleId %>},
        function(response) {
         data = response
         } )
      return data;
     }

Next we setup the plot. Below we first get the GraphOptions using HTTP POST in the same way as the GetGraph, which we then pass to $.plot to create a new graph.
Important to note is the async:false which disable asynchronous calls while initiating the graph. This is to ensure options and data have values before first use.
    // setup plot
    var options = [];
    var updateInterval = 3000;

    $.ajaxSetup({async:false});
    $.post('GetGraphOptions',{example: <%=exampleId %>},
   function(response) {
       options = response
    } )
    
 var plot = $.plot($("#placeholder"), getGraph(), options);

The update() function is used to draw the graph. This is done in three steps.
  1. setData() which retrieves the data using getGraph() function described above.
  2. setupGrid() to adjust the grid to the new data.
  3. draw() the graph(plot) on the screen.
If poll was passed as a request parameter, setTimeout() will create a timer which will call update() every 3seconds (updateInterval = 3000ms on line 39) to update the graph with new data from the backend server.
    function update() {
        plot.setData(getGraph());
        plot.setupGrid();
        plot.draw();
        
        <% if (poll != null) { %>
        setTimeout(update, updateInterval);
        <% } %>
    }

Finally we call update() to initiate the drawing of the graph for the first time and set jQuery to use asynchronous calls again.
    update();
    $.ajaxSetup({async:true});


The above will of couse not do anything without the backend servlets which we will look closer at next.



GetGraph.java

This is one of the servlet which is used to retrieve data by the frontend JavaScript. Servlet mapping is defined in web.xml as /GetGraph.
/**
 * Servlet implementation class GetGraph
 */
public class GetGraph extends HttpServlet {

In the below code we process the HTTP POST request starting by extracting the request scope parameter example. Then we set the response content type to application/json so the client will interpret the response correctly.
 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  String example = request.getParameter("example");
  response.setContentType("application/json");
  PrintWriter out = response.getWriter();

The rest of the code is to print the data in the response body based on the example parameter.
  switch (Integer.valueOf(example)) {
  case 1:
   out.println(ExampleGraph1.getGraph());



GetGraphOptions.java

The same as for GetGraph.java, the servlet mapping declaration of /GetGraphOptions is located in web.xml.
/**
 * Servlet implementation class GetGraphOptions
 */
public class GetGraphOptions extends HttpServlet {

Again, the same as for GetGraph.java with the minor difference that the response body consist of the options from getGraphOptions().
   out.println(ExampleGraph1.getGraphOptions());



ExampleGraph1.java

ExampleGraph1 will return a simple graph with one data series based on sin().

We start by creating a new Chart object which is the container for options and data.
Next we create both X and Y Axis from the Axis class. Flot use the same options for both axis which is why we use the common class Axis.
In this example we use the default options so we just add the axis to the chart object which will make them visible on the graph.
 public static String getGraphOptions() {
  Chart chart = new Chart();
 
  Axis yAxis = new Axis();
  chart.addYAxis(yAxis);
 
  Axis xAxis = new Axis();
  chart.addXAxis(xAxis);
 
  return chart.printChartOptions();
 }

When creating a data series we use the PlotData object. The PlotData constructor takes two optional arguments, Label and Colour. In this example we want to use the default colour and therefore we pass null. Using null to set properties will set them to the default value.
Next we create 100 data points using addPoint(x,y). The addPoint() method takes two arguments of type Object to make it as flexible as Flot itself. In this example however we use simple data types int and double.
Last we create the Chart object and add the data serie which will populate data and then print it in JSON format using printChart() method.
 public static String getGraph() {
  PlotData sinPlot = new PlotData("sin(x)", null);
  
  int i = 0;
  while( i++ < 100 ) {
   sinPlot.addPoint(i, Math.sin(i));
  }
 
  Chart chart = new Chart();
  chart.addElements(sinPlot);

  return chart.printChart();
 }



ExampleGraph2.java

ExampleGraph2 will return a simple graph with two data series based on sin() and cos() using two y axis.

Here we add a second y axis and use setPosition() to define location of the axis (valid locations are "bottom", "top", "left" or "right").
  Axis yAxisRight = new Axis();
  yAxisRight.setPosition("right");
  chart.addYAxis(yAxisRight);

To use the right y axis, simply call the setRightYAxis() on the dataseries.
  PlotData cosPlot = new PlotData("cos(x)+5", null);

  cosPlot.setRightYAxis();



ExampleGraph3.java

ExampleGraph3 will return a multi-type graph with three data series representing line, dot and bar series.

To change the type from default line, just call setBarOptions() or setPointOptions().
 // Flot use Line as default type. To change the type, just call the helper method use<Type>.
 sqrtPlot.setBarOptions();
 cosPlot.setPointOptions();



ExampleGraph4.java

ExampleGraph4 is a bit more advanced where we use the poll parameter to update the graph every 3 seconds.

Here we set max property on the y axis which will lock the maximum to 550 instead of the default dynamic setting.
The x axis is set to time mode which by default automatically scales the axis depending on the range of the data series. In this case we use 5 minutes range which defaults to 30 second ticks.
 Axis yAxis = new Axis();
 yAxis.setMax(550L);
 chart.addYAxis(yAxis);

 Axis xAxis = new Axis();
 xAxis.setMode("time");
 chart.addXAxis(xAxis);

This getGraph() method is a bit more advanced to illustrate the use of realtime updates of the graph.
First we create two Calendar objects, one using local time and one UTC. Flot only use UTC which means the time must be adjusted before Flot process it.
Variable timeSpan will be used to limit the returned data series x range to 5 minutes.
 public static String getGraph() {
 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0000"));
 Calendar localCal = Calendar.getInstance();
 // Timespan in seconds
 Integer timeSpan = 300;

Since we are simulating a realtime graph the highest x value must be current time while startTime 5 minutes (timeSpan) earlier. We use time in milliseconds since it makes data manipulation easier and Flot expect this format.
On line 34 we adjust the endTime to compensate for the limitation of Flot only supporting UTC. This is by adjusting the time in milliseconds by the offset between UTC and local timezone. In Flot this will make sure the time is presented as it would be using the local timezone.
 Long endTime = cal.getTimeInMillis();
 // Adjust endTime
 endTime += localCal.getTimeZone().getOffset(cal.getTimeInMillis());
 Long startTime = endTime-(timeSpan*1000);

 PlotData sinPlot = new PlotData("rand(x)", null);

The next few lines is using a Java's Random class to create a identical sequences of number which we navigate through using a subset of the time in milliseconds. The sole purpose is to simulate retrieval data from a persistent layer such as a database where we end up with a realtime feel on the client side.
 // Create new random generator using seed 1
 Random rand = new Random(1);
 // Create sequence based on time to be used to retrieve numbers from random seed
 int startIntSeed = (int)Math.floor((startTime % 10000000) / 1000);
 // Forward to the current next random number
 for (int seq = 0; seq < startIntSeed; seq++) {
  rand.nextInt(500);
 }



ExampleGraph5.java

ExampleGraph5 shows the use of colour and opacity graident.

We start by defining we want a Bar series. Then specify the options such as width (0.8 = 80%) and the line width (0 = no border lines).
Next we define a three colors to use as a gradient, blue, red then blue. This is all that is requied to create a gradient based on colour. We also use setColors() to set another level of gradient using opacity 0.8 to 0.1 in order to show how it can be used..
 sqrtPlot.setBarOptions();
 sqrtPlot.getBars().setBarWidth(0.8);
 sqrtPlot.getBars().setLineWidth(0);
 Colors colors = new Colors("#afefef", "#ff5522","#afefef");
 colors.setColors(new Gradient(0.8,null), new Gradient(0.1, null));
 sqrtPlot.getBars().setFillColor(colors);



ExampleGraph6.java

ExampleGraph6 shows the use of tooltips (Based on Example 3).

In the Java code the only requirement is to specify that the grid is hoverable.
  // Enable interaction with grid. (In this case for tooltip)
  Grid grid = new Grid();
  grid.setHoverable(true);
  chart.addGrid(grid);

This alone will not make tooltip appear so we borrow some code from one of the Flot examples, http://people.iola.dk/olau/flot/examples/interacting.html
The only change to notice is on line 15 where we put the bind into a function so we can control whether to use tooltips or not from example.jsp (line 26).
function showTooltip(x, y, contents) {
 $('<div id="tooltip">' + contents + '</div>').css({
  position : 'absolute',
  display : 'none',
  top : y + 5,
  left : x + 5,
  border : '1px solid #fdd',
  padding : '2px',
  'background-color' : '#fee',
  opacity : 0.80
 }).appendTo("body").fadeIn(200);
}

var previousPoint = null;
function useTooltip() {
 $("#placeholder").bind(
   "plothover",
   function(event, pos, item) {
    $("#x").text(pos.x.toFixed(2));
    $("#y").text(pos.y.toFixed(2));
 
    if (item) {
     if (previousPoint != item.dataIndex) {
      previousPoint = item.dataIndex;
 
      $("#tooltip").remove();
      var x = item.datapoint[0].toFixed(2), y = item.datapoint[1]
        .toFixed(2);
 
      showTooltip(item.pageX, item.pageY, item.series.label
        + " of " + x + " = " + y);
     }
    } else {
     $("#tooltip").remove();
     previousPoint = null;
    }
  });
}

Monday, 21 November 2011

Cygwin - Get that Linux feeling - on Windows!

Ever get frustrated about the limitations of CMD?

Many companies enforce a SOE installation of Windows to keep all client systems alike. For the company it isusually a good strategy, but for IT professionals it can sometimes be somewhat challanging.

From the day I started working in companies where it wasn't suitable running a *NIX flavour on the PC, I have used Cygwin.

As it states on their homepage (http://cygwin.com/) "Get that Linux feeling - on Windows!"... That's exactly what it does.

Here's a quick guide to get Cygwin installed with X server and xterm.

Installation
  1. Download and run setup.exe from http://cygwin.com/
  2. Follow the on-screen instructions up until the dialog Select Packages.
    To find packages, enter the name in the Search box.

    1. First select the package X-start-menu-icons

    2. Then select patchutils

    3. Click Next to install the packages
    4. (If you have 7GB to spare, you could of course just select everything to install)
  3. The next screen will automatically select all dependencies. Just click Next to start the installation phase.
Running Cygwin - XWin Server
During installation, start menu icons is created by default. To use the XWin Server (and xterm), click on the Start icon:
At startup an xterm session is started as well:
 The default look is quite plain and straining on the eye, so I tend to make it a bit more retro as described in the next section...

To start additional xterm sessions, find the X icon next to the clock and click Applications > xterm:



Customisation
At startup, a new xterm window will appear, use this window to patch the system.XWinrc:
  1. Execute the following, which will create a new config file for XTerm:
    echo 'XTerm*Background: black
    XTerm*Foreground: white
    XTerm*toolBar: false
    XTerm*geometry: 160x40' > ~/XTerm
    
As a result, you will end up with a much nicer look and feel:

Sunday, 13 November 2011

Runtime log4j configuration

Recently I got struck with the question what log level the rootLogger in log4j was set to.
Normally a quick check of the log4j's property file would give you the answer, but in this case there was a third party application that had multiple ways of defining the log level both at startup and then runtime.

After a quick search I found this blog post: http://nelz.net/2008/04/08/log4j-runtime-configuration/
This will not only show you the runtime value of the loggers but also enable you to change this without require a restart of the application.

In order to rename log4jAdmin.jsp, grab it from the blog and perform the following steps:
Find: "/log4jAdmin.jsp
Replace with: request.getContextPath() + request.getServletPath() + "
Find: log4jAdmin.jsp
Replace with: <%=request.getContextPath() + request.getServletPath() %>

Screenshot:

The JSP with the customisations:
<%@ page language="java" contentType="text/html;charset=UTF-8" %>
<%@ page import="org.apache.log4j.Level" %>
<%@ page import="org.apache.log4j.LogManager" %>
<%@ page import="org.apache.log4j.Logger" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.util.Set" %>
<%@ page import="java.util.Arrays" %>
<% long beginPageLoadTime = System.currentTimeMillis();%>

<html>
<head>
    <title>Log4J Administration</title>
    <style type="text/css">
        <!--
        #content {
            margin: 0px;
            padding: 0px;
            text-align: center;
            background-color: #ccc;
            border: 1px solid #000;
            width: 100%;

        }

        body {
            position: relative;
            margin: 10px;
            padding: 0px;
            color: #333;
        }

        h1 {
            margin-top: 20px;
            font: 1.5em Verdana, Arial, Helvetica sans-serif;
        }

        h2 {
            margin-top: 10px;
            font: 0.75em Verdana, Arial, Helvetica sans-serif;
            text-align: left;
        }

        a, a:link, a:visited, a:active {
            color: red;
            text-decoration: none;
            text-transform: uppercase;
        }

        table {
            width: 100%;
            background-color: #000;
            padding: 3px;
            border: 0px;
        }

        th {
            font-size: 0.75em;
            background-color: #ccc;
            color: #000;
            padding-left: 5px;
            text-align: center;
            border: 1px solid #ccc;
            white-space: nowrap;
        }

        td {
            font-size: 0.75em;
            background-color: #fff;
            white-space: nowrap;
        }

        td.center {
            font-size: 0.75em;
            background-color: #fff;
            text-align: center;
            white-space: nowrap;
        }

        .filterForm {
            font-size: 0.9em;
            background-color: #000;
            color: #fff;
            padding-left: 5px;
            text-align: left;
            border: 1px solid #000;
            white-space: nowrap;
        }

        .filterText {
            font-size: 0.75em;
            background-color: #fff;
            color: #000;
            text-align: left;
            border: 1px solid #ccc;
            white-space: nowrap;
        }

        .filterButton {
            font-size: 0.75em;
            background-color: #000;
            color: #fff;
            padding-left: 5px;
            padding-right: 5px;
            text-align: center;
            border: 1px solid #ccc;
            width: 100px;
            white-space: nowrap;
        }

        -->
    </style>
</head>
<body onLoad="javascript:document.logFilterForm.logNameFilter.focus();">

<%
    String containsFilter = "Contains";
    String beginsWithFilter = "Begins With";
    String[] logLevels = {"debug", "info", "warn", "error", "fatal", "off"};
    String targetOperation = (String) request.getParameter("operation");
    String targetLogger = (String) request.getParameter("logger");
    String targetLogLevel = (String) request.getParameter("newLogLevel");
    String logNameFilter = (String) request.getParameter("logNameFilter");
    String logNameFilterType = (String) request.getParameter("logNameFilterType");
%>
<div id="content">
<h1>Log4J Administration</h1>

<div class="filterForm">

    <form action="<%=request.getContextPath() + request.getServletPath() %>" name="logFilterForm">
       Filter Loggers:&nbsp;&nbsp;
        <input name="logNameFilter" type="text" size="50" value="<%=(logNameFilter == null ? "":logNameFilter)%>"
               class="filterText"/>
        <input name="logNameFilterType" type="submit" value="<%=beginsWithFilter%>" class="filterButton"/>&nbsp;
        <input name="logNameFilterType" type="submit" value="<%=containsFilter%>" class="filterButton"/>&nbsp;
        <input name="logNameClear" type="button" value="Clear" class="filterButton"
               onmousedown='javascript:document.logFilterForm.logNameFilter.value="";'/>
        <input name="logNameReset" type="reset" value="Reset" class="filterButton"/>
        <param name="operation" value="changeLogLevel"/>
    </form>
</div>

<table cellspacing="1">
    <tr>
        <th width="25%">Logger</th>
        <th width="25%">Parent Logger</th>
        <th width="15%">Effective Level</th>
        <th width="35%">Change Log Level To</th>
    </tr>

    <%
        Enumeration loggers = LogManager.getCurrentLoggers();
        HashMap loggersMap = new HashMap(128);
        Logger rootLogger = LogManager.getRootLogger();

        if (!loggersMap.containsKey(rootLogger.getName())) {
            loggersMap.put(rootLogger.getName(), rootLogger);
        }

        while (loggers.hasMoreElements()) {
            Logger logger = (Logger) loggers.nextElement();
            if (logNameFilter == null || logNameFilter.trim().length() == 0) {
                loggersMap.put(logger.getName(), logger);
            } else if (containsFilter.equals(logNameFilterType)) {
                if (logger.getName().toUpperCase().indexOf(logNameFilter.toUpperCase()) >= 0) {
                    loggersMap.put(logger.getName(), logger);
                }
            } else {
// Either was no filter in IF, contains filter in ELSE IF, or begins with in ELSE
                if (logger.getName().startsWith(logNameFilter)) {

                    loggersMap.put(logger.getName(), logger);
                }
            }
        }
        Set loggerKeys = loggersMap.keySet();
        String[] keys = new String[loggerKeys.size()];
        keys = (String[]) loggerKeys.toArray(keys);
        Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < keys.length; i++) {
            Logger logger = (Logger) loggersMap.get(keys[i]);

// MUST CHANGE THE LOG LEVEL ON LOGGER BEFORE GENERATING THE LINKS AND THE
// CURRENT LOG LEVEL OR DISABLED LINK WON'T MATCH THE NEWLY CHANGED VALUES
            if ("changeLogLevel".equals(targetOperation) && targetLogger.equals(logger.getName())) {
                Logger selectedLogger = (Logger) loggersMap.get(targetLogger);
                selectedLogger.setLevel(Level.toLevel(targetLogLevel));
            }

            String loggerName = null;
            String loggerEffectiveLevel = null;
            String loggerParent = null;
            if (logger != null) {
                loggerName = logger.getName();
                loggerEffectiveLevel = String.valueOf(logger.getEffectiveLevel());
                loggerParent = (logger.getParent() == null ? null : logger.getParent().getName());
            }
    %>
    <tr>
        <td><%=loggerName%></td>
        <td><%=loggerParent%></td>
        <td><%=loggerEffectiveLevel%></td>
        <td class="center">
            <%
                for (int cnt = 0; cnt < logLevels.length; cnt++) {

                    String url = request.getContextPath() + request.getServletPath() + 
                        "?operation=changeLogLevel&logger=" + loggerName + "&newLogLevel=" + logLevels[cnt] +
                        "&logNameFilter=" + (logNameFilter != null ? logNameFilter : "") +
                        "&logNameFilterType=" + (logNameFilterType != null ? logNameFilterType : "");

                    if (logger.getLevel() == Level.toLevel(logLevels[cnt]) || 
                        logger.getEffectiveLevel() == Level.toLevel(logLevels[cnt])) {

            %>
            [<%=logLevels[cnt].toUpperCase()%>]
            <%
            } else {
            %>
            <a href='<%=url%>'>[<%=logLevels[cnt]%>]</a>&nbsp;
            <%
                    }
                }
            %>
        </td>
    </tr>
    <%
        }
    %>
</table>
<h2>
    Revision: 1.0<br/>
    Page Load Time (Millis): <%=(System.currentTimeMillis() - beginPageLoadTime)%>

</h2>
</div>
</body>
</html>