Nelz's Blog

8 April 2008

Log4J Runtime Configuration

Filed under: Java — nelz9999 @ 13:25

Ok… I know that in past projects, my teams have been able to put together a JSP to modify Log4J configuration at runtime. However, doing a google search really only found a couple of descriptions of HOW to do it, but I wanted a more cut & paste solution.

So, after finding one in some code, I thought I would re-post it here. I give you “log4jAdmin.jsp”:

<%@ 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="log4jAdmin.jsp" 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 = "/log4jAdmin.jsp?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>

If you’ve got a basic Log4J setup running, this should be a trivial add, allowing you to modify logging levels at runtime.

Here are a couple of caveats:

  1. You will need to have a base log4j.configuration file set up at startup. A log4j.properties file is fine for basic setups, but a log4j.xml file is reportedly more flexible and powerful.
  2. Standard JSP security is advised… You may need/want to hid this JSP behind whatever MVC framework you use… You DEFINITELY don’t want this JSP exposed to just anyone hitting your server!
  3. Towards the bottom of this file, there’s a hard-coded URL back to this page… If you change the name/url, make sure to update this… (I haven’t looked to hard at it, but I assume there’s a way to access the REQUEST object to find out the base URL for self reference.)

Good Luck!

Update (20008-04-10): Jon Mann pointed out that there are two hard-coded URLs in the JSP. I updated them both to be “log4jAdmin.jsp” on lines 51 and 140. Thanks Jon!

Update (20008-09-09): Sudheer pointed out a problem with the url that is created. I have updated the code with his suggestion. Thank you Sudheer!

6 Comments »

  1. Thanks, that is very useful.
    There are 2 hard-coded URLs in the page:
    1. “/admin/log4j.do” (line #51)
    2. “/log4jAdmin.jsp” (line #140)
    After changing both of these, the JSP worked perfectly.

    Comment by Jon Mann — 10 April 2008 @ 02:05

  2. Its a Nice and useful script.
    However I ran into a small issue.
    Attempting to change the log level on the filtered result set, is not working.
    Its becuase of the missing ‘LogFilterType’ parameter in the links.
    To fix it, Change the URL part (Line 139) as follows:
    Change From:
    String url = “/log4jAdmin.jsp?operation=changeLogLevel&logger=” + loggerName + “&newLogLevel=” + logLevels[cnt] + “&logNameFilter=” + (logNameFilter != null ? logNameFilter : “”);
    Change To:
    String url = “/log4jAdmin.jsp?operation=changeLogLevel&logger=” + loggerName + “&newLogLevel=” + logLevels[cnt] + “&logNameFilter=” + (logNameFilter != null ? logNameFilter : “”) + “&logNameFilterType=” + (logNameFilterType != null ? logNameFilterType : “”);
    (Add the param “logNameFilterType” in the query string)

    Comment by Sudheer — 5 August 2008 @ 17:54

  3. Jon and Sudheer, thank you for your help. I have updated the code with your suggestions.

    Comment by Nelz — 9 September 2008 @ 22:42

  4. Nice script!

    I found it useful to add a “Reset Configuration” button, which invokes this code before the rest of the code:

    if (“resetConfiguration”.equals(targetOperation)) {
    LogManager.resetConfiguration();
    URL url = Loader.getResource(“log4j.xml”);
    DOMConfigurator.configure(url);
    }

    Comment by Ulrik Skyt — 20 August 2010 @ 02:11

  5. Thanks for this script!

    To make it work wherever you want to put it, add the following line at the beginning somewhere and replace the hardcoded “log4jAdmin.jsp” strings with it:

    String servletLocation = request.getContextPath() + request.getServletPath();

    Comment by Anonymous — 7 January 2011 @ 00:46

  6. [...] “Nelz” Carpentier posted this piece of jsp – listed here below, that enables you to change the log4j loglevel runtime, instead of [...]

    Pingback by Change log4j loglevels runtime | Iamkristian — 27 April 2011 @ 05:13


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.