Thursday, July 19, 2007

Managing Log4j Appenders @ Runtime

Many a times when debugging production issues we wish the log level would have been @ DEBUG level or some other lower level to get more insight into the issue. The usual way to debug non-obvious production issue is to reproduce it in you local development environment. In many cases it works but in some cases its just hard to reproduce the exact environment which caused the error. For instance trying to reproduce a complex hibernate transaction error and that sort. In our application we use a hell lot of webservice calls to interact with various third party hosted services. Its almost impractical to log all the SOAP interactions and many a times reproducing the environment which generated the faulty SOAP call is not possible.

For this reason we manage our Log4j appenders @ runtime wherein we can add new appenders and change priority levels of existing appenders @ runtime. Once the stacktrace or the required log is gathered, the priority levels can be reverted back to the non-voluminous/required levels.

We have a Log4j administration page in our application which can only be accessed by super ninjas a.k.a developers. This page can be used to change/add appenders at runtime. The page has sections to add new appenders and change priority levels of existing appenders.

To start with we display all the current appenders with their current priority levels. This can be achieved using the code below (for simplicity sake I am :



// collect all current appenders
List appenders = new ArrayList(50);
Enumeration e = LogManager.getCurrentLoggers();

while ( e.hasMoreElements() )
{
appenders.add(e.nextElement());
}
request.setAttribute("appenders",appenders);

// all possible priority levels
Priority [] prios = Priority.getAllPossiblePriorities();
request.setAtrribute("possiblePriorities", prios);



In the JSP page you can use a simple JSTL tag to display the above collected information in a tabular format.



<%@ page import="java.util.*,org.apache.log4j.*"

....
....

<table>
<tr>
<td> Appender </td>
<c:forEach var="priority" items='${possiblePriorities}'>
<td><c:out value='${priority}'/></td>
</c:forEach>
</tr>

<c:forEach var="appender" items='${appenders}'>
<tr>
<td><c:out value='${appender.getName()}'/></td>
<c:forEach var="priority" items='${possiblePriorities}'>
<td>
<input type="radio" name="'${appender.getName()}'" value="'${priority}'"
<c:if test='${appender.getChainedPriority() == priority)}'> checked </c:if>
>
</td>
</c:forEach>
</tr>
</c:forEach>

<tr>
<td rowspan=5 align="center"> <button type="submit" name="submit" value="update">Update</button></td>
</tr>

</table>

<!-- section of page to add new appender-->

<table style="width:auto;">
<tr>
<td align="center">
<input type="text" name="newLogger" size="70">
<td>
<td>
<select name="newLoggerLevel">
<c:forEach var="priority" items='${possiblePriorities}'>
<option value="<c:out value='${priority}' />"><c:out value='${priority}' /></option>
</c:forEach>
</select>
<td>
<td>
<button type="submit" name="submit" value="Add">Add Logger</button>
</td>
</tr>
</table>



The idea is to get a view of this sort:




Two events can generate from the above page:
1) add a new appender
2) update level of existing appender

Both of these events can be handled with the below servlet code:



import org.apache.log4j.*;

....
....

Enumeration e = LogManager.getCurrentLoggers();
while (e.hasMoreElements())
{
Logger logger = (Logger) e.nextElement();
String prio = request.getParameter(logger.getName());
if (prio != null &&amp; prio.length() > 0)
{
Level p = Level.toLevel(prio);
if (p != null && ! p.equals(logger.getEffectiveLevel()))
{
logger.setLevel(p);
}
}
}

// add new loggers desired
String newLogger = request.getParameter("newLogger");
String newLoggerLevel = request.getParameter("newLoggerLevel");
if (newLogger != null)
{
Level p = Level.toLevel(newLoggerLevel);
Logger logger = Logger.getLogger(newLogger);
logger.setLevel(p);
}

No comments:

Post a Comment