Friday, January 22, 2010

A poor man's conditional logging configuration

It is often desirable to keep the same logging configuration file during development and production environments. (I use logback as my logging framework.)

Ideally, one would check whether the application was running on the production environment and enable parts of the configuration file accordingly. Unfortunately, logback does not support conditional (if-then-else) configuration statements.

However, it supports default substitution for variables which can be used to solve a variety of problems. For example, I would like to have a file FileAppender which logs under the logs/ folder of my Tomcat server. Here is the relevant configuration snippet:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<File>${catalina.home}/logs/myApp.log</File>
...
</appender>
As long as the catalina.home property is defined, the above works as expected. However, during development I use the jetty server which does not define the said property. I can still instruct logback to fallback to a reasonable value by specifying a default substitution value for ${catalina.home}. The current directory, symbolized by a dot, i.e. '.', is quite a reasonable default. The configuration snipped is thus modified as:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<File>${catalina.home:-.}/logs/myApp.log</File>
...
</appender>
This will have the log file of my application write to
./logs/myApp.log within my development environment. Easy as pie.

301 redirects with Wicket

Sometimes a web-page needs to redirect to a new address. A 301 redirect means that a page moved permanently, which in most cases is indistinguishable from a 302 "moved temporarily" redirect. Search engines however react differently to a 301 response than to a 302 response. If a page has moved permanently, sending a 301 will cause the search engine to update its index to the new page. For "important" pages, you would thus prefer sending 301 redirect from an old url to a new url.

Assuming the old url was a .jsp, here is a JSP snippet to send a 301 redirect:
<% 
String redirectURL = "http://.../";
response.setStatus(301);
response.setHeader( "Location", redirectURL);
response.setHeader( "Connection", "close" );
%>

If you are using Wicket, you can instruct the wicket filter in your web.xml file to ignore the said url, otherwise wicket filter would try to serve the JSP page as a Wicket page, which would not work.
<filter>
<filter-name>a name</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>ignorePaths</param-name>
<param-value>oldURL.jsp,other_urls_to_ignore,...</param-value>
</init-param>
</filter>
For an arbitrary (non-jspP) url, you might actually need to create a wicket page redirecting from the old url to the new one. Here is the relevant code:
public class MyRedirectingPage extends WebPage {
public MyRedirectingPage() {
final String newURL = "http://...";
RedirectRequestTarget target = new RedirectRequestTarget(redirectURL) {
@Override
public void respond(RequestCycle requestCycle) {
WebResponse response = (WebResponse) requestCycle.getResponse();
HttpServletResponse servletResponse = response.getHttpServletResponse();
servletResponse.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
servletResponse.setHeader("Location", newURL);
servletResponse.setHeader("Connection", "close");
}
};
getRequestCycle().setRequestTarget(target);
}
}
Within your WebApplication class, you also need to mount to the old url.
  mountBookmarkablePage("path/to/old/url", MyRedirectingPage.class);

Wednesday, January 20, 2010

Marker based email notifications

Part of the SLF4J API, Markers allow developers to add meta-data to log statements. Not all errors are equal, with some errors being more unusual than others. If you wish to color log statement related to "unusual" errors you could write:
  Marker unusualMarker = MarkerFactory.getMarker("UNUSUAL");
Logger logger = LoggerFactory.getLogger("myPackage.myClass");
logger.error(unusualMarker, "An unusual error occurred.");

whereas a "regular" error might be logged as
  Logger logger = LoggerFactory.getLogger("myPackage.myClass");
logger.error("An error occurred.");

So what do markers give you? Well, for one, you can perform marker-based filtering. For example, turn off logging except for errors marked as UNUSUAL. At QOS.ch, we use markers to trigger outgoing emails for certain types of unusual errors.

Here is the configuration snippet:
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%date%-5level%thread%mdc{user-id}%logger%msg</pattern>
<throwableRenderer class="ch.qos.logback.classic.html.DefaultThrowableRenderer" />
</layout>
<From>...</From>
<SMTPHost>...</SMTPHost>
<Subject>%msg</Subject>
<To>email address of @ the admin</To>
<evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
<marker>UNUSUAL</marker>
<marker>ANOTHER_UNUSUAL_EVENT</marker>
</evaluator>
</appender>

This way whenever an "unusual" error occurs, the admin is notified via email. The definition of unusual is highly dependent on the application, but the SLF4J and logback combo offers a flexible way of defining and processing "unusualness".