0
public class SessionLogger {

    private final String sessionId;

    public SessionLogger(String sessionId) {
        this.sessionId = sessionId;
    }

    public void info(Log log, String message, Object... args) {
        log.info(formatMessage(message, args));
    }

    public void error(Log log, String message, Throwable t, Object... args) {
        log.error(formatMessage(message, args), t);
    }

    private String formatMessage(String message, Object... args) {
        for (int i = 0; i < args.length; i++) {
            message = message.replaceFirst("\\{\\}", args[i].toString());
        }
        return String.format("SessionId [%s]: %s", sessionId, message);
    }
}

What I want to do is to pass, Logger instance to the SessionLogger class and I would like to see the class name, where Logger was initialized.

public class A {
  private static final Log log = LogFactory.getLog(A.class)
  public void doIt() {
     sessionLogger.info(log, "hello world");
  }
}

I would expect to see class A in the log message instead of SessionLogger:

2013-10-07 00:29:27,328  INFO [main] (SessionLogger.java:17) - SessionId [123]: Hello world

I've commons-logging.jar and log4j-1.2.16.jar in classpath. Logger is an instance of org.apache.commons.logging.Log

Update

Just released that it is expected behavior, cause Logger logs the line of the code, where log method was invoked. So it should be done another way somehow

user12384512
  • 3,362
  • 10
  • 61
  • 97
  • >> cause Logger logs the line of the code, where log method was invoked << . . . This depends on configuration of your log4j.properties - you can also output the name of the Logger instead (which should be "A") – cljk Oct 06 '13 at 21:36
  • Yep. But it would be nice to log line of code too. Since log4j calls Thread.currentThread().getStackTrace() ... to get the line number at runtime, I can't use this approach in this case – user12384512 Oct 06 '13 at 21:48

2 Answers2

1

I think the solution is to write a custom Log4j pattern layout as presented in this article: http://fw-geekycoder.blogspot.com/2010/07/creating-log4j-custom-patternlayout.html

Then you will not need SessionLogger, which will simplify your code significantly.

Layout:

public class MyPatternLayout extends PatternLayout {

    @Override
    protected PatternParser createPatternParser(String pattern) {
        return new MyPatternParser(pattern);
    }
}

Pattern parser:

public class MyPatternParser extends PatternParser {

    private static final char USERNAME_CHAR = 'S';

    public MyPatternParser(String pattern) {
        super(pattern);
    }

    @Override
    protected void finalizeConverter(char c) {
        switch (c) {
            case USERNAME_CHAR:
                currentLiteral.setLength(0);
                addConverter(new MyPatternConverter());
                break;
            default:
                super.finalizeConverter(c);
        }
    }
}

Pattern converter:

public class MyPatternConverter extends PatternConverter {
    @Override
    protected String convert(LoggingEvent event) {
        // Retrieve SessionID
        return "123";
    }
}

Log4j configuration:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <layout class="MyPatternLayout">
            <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %-5p (%F:%L) - Session ID:%S %m%n"/>
        </layout>
    </appender>

    <root>
        <priority value ="debug" />
        <appender-ref ref="console" />
    </root>

</log4j:configuration>
Rafal Borowiec
  • 5,124
  • 1
  • 24
  • 20
1

After a glance on what you are trying to achieve, I believe instead of doing that piece of tricky SessionLogger stuff, using MDC may be a more reasonable choice.

Setup the session ID in MDC (depends on your app design. For web app, having an servlet filter doing the MDC setup work is reasonable), and let everyone simply use the logger as normal. By having an appropriate pattern, you can put the session ID in the result log message.

Not sure if MDC is exposed in Apache Commons Logging, but it is available in SLF4J or Log4J.

Just curious, is there any reason to use ACL (which is known to have quite a lot of problem). Consider switching to SLF4J which is more widely-adopted in recent years.

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
  • Thanks for sharing MDC approach. But it's not suitable for me, cause it relies on ThreadLocal variables. – user12384512 Nov 04 '13 at 18:32
  • I am interested to know why relying on ThreadLocal is a problem? Unless you are doing some tricky multi-threading stuff, ThreadLocal is rarely a problem. ThreadLocal is something being used a lot more than you expected, e.g. in transaction management etc. – Adrian Shum Nov 05 '13 at 01:47
  • Yes, there are lot of multithreading, executors, producers/consumers etc. It's not a simple web application – user12384512 Nov 05 '13 at 14:27
  • However, I still making use of MDC is the way to go. In your original approach, caller of Logger is supposed to pass in the Session ID, or someone need to create a logger with session id and pass the logger around. There is no special reason that you cannot pass around the session info, and do corresponding MDC setup in appropriate places. The beauty is such kind of MDC setup can mostly be hidden from the actual processing logic (while currently processing logic is aware of the session ID/special session logger), and processing logic can do normal logging. – Adrian Shum Nov 06 '13 at 01:51