Log4j2: How can I get Class Name and Line Number without using Throwable?
Asked Answered
R

3

6

I have developed a wrapper class on Log4j2. Using declarative services of OSGi I have published a custom logger service using my own logger interface with the wrapper class being the implementation. The wrapper class is only used to configure the logger programmatically, message formatting & add a few more methods, at last it is calling the logging methods of Log4j2.

I want to print source class/file name and line number of each logs requested in the log file. The options %C/%F and %L only print information about the location inside my wrapper class where I actually call the log method.

So as a workout I am passing new Throwable as an argument each time so that I can use the layout %throwable{short.lineNumber}. But this is an expensive process for embedded applications.

My main problem is in getting the line number because for the file name I could at least request a new logger from Log4j2 with the name of each service requesting for logger service and keep it in a map.

Is there a solution to trace back the caller? I hope there is a similar solution for applications where you don't want to have the LOG4j2 jars on each consumer of the logger service. Just for info, I don't want to use any XML files, all configurations are made programmatically.

Rowney answered 20/1, 2015 at 19:55 Comment(2)
Interesting question. Perhaps look at the source of log4j and see how they do it.Eleen
Log4j also used stack trace to identify those information.Rowney
S
4

You can use

StackTraceElement[] stes = Thread.currentThread().getStackTrace();

however I am not sure this is much cheaper.

What I do is make each message unique (for the class) and avoid including the line number. You can search for the unique message in your IDE to find the line number. The class should be in the name of the logger.

Stoat answered 20/1, 2015 at 20:2 Comment(1)
Hi Peter, thank you for the answer. I have not seen other alternatives so I think calling the stack trace from my wrapper class on each log might be the current solution(if I understand your answer correctly). And because of project requirement, I'll need the line number. But Log4j2 only takes one message argument, do you have any suggestion on how to pass the extracted information other than concatenating the string in the message to be logged?Rowney
D
4

Log4j2 internally walks the stacktrace to extract location information. It knows where to stop in the stack trace by specifying the correct FQCN (fully qualified class name) to the org.apache.logging.log4j.spi.ExtendedLogger#logMessage method.

Log4j2 contains a tool that generates code for logger wrappers. The documentation is here (in the Custom Log Levels manual page): http://logging.apache.org/log4j/2.x/manual/customloglevels.html#CustomLoggers

Try generating a logger wrapper with this tool and base your custom wrapper on the generated code. This generated code will use the correct FQCN and will be able to produce the correct location information.

Dunigan answered 29/1, 2015 at 8:16 Comment(0)
M
0

as @Remko Popma said, here is a simple implementation:
First create your ExtendedLogger class:

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;

public class ExtLogWithLine extends ExtendedLoggerWrapper {
    private static final long serialVersionUID = 8239280349129059055L;

    // define your wrapper class here
    private static final String FQCN = WrapperLog.class.getName();

    private final ExtendedLoggerWrapper logger;

    private ExtLogWithLine(final Logger logger) {
        super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());
        this.logger = this;
    }

    public static ExtLogWithLine create(final String name) {
        final Logger wrapped = LogManager.getLogger(name);
        return new ExtLogWithLine(wrapped);
    }

    @Override
    public void debug(String info) {
        if (isDebugEnabled()) {
            logger.logIfEnabled(FQCN, Level.DEBUG, null, info, (Throwable) null);
        }
    }

    @Override
    public void info(String info) {
        if (isInfoEnabled()) {
            logger.logIfEnabled(FQCN, Level.INFO, null, info, (Throwable) null);
        }
    }

    @Override
    public void warn(String info) {
        if (isWarnEnabled()) {
            logger.logIfEnabled(FQCN, Level.WARN, null, info, (Throwable) null);
        }
    }

    @Override
    public void error(String info) {
        if (isErrorEnabled()) {
            logger.logIfEnabled(FQCN, Level.ERROR, null, info, (Throwable) null);
        }
    }
}

Then user the exetended logger to print log:
public class WrapperLog {

    public void testLog() {
       ExtLogWithLine logger = ExtLogWithLine.create("xxx");
       
       // log will print out with correct line number
       logger.info("see the line number");
    }
}
Melanimelania answered 25/12, 2021 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.