1

I am trying to create a Custom Logger. My code is given below and the Properties file is also given below.

It works in the sense that I get log messages and details in the log file in the format I want. However, all logs are logged with same ClassName - MyLogger. I want the 'name' I am passing in the constructor to be logged as ClassName instead of the MyLogger ClassName. Appreciate any help in that regard.

Java Code:

    public class MyLogger {

    private static Logger log;
    
    public static MyLogger getLogger(String name) {
        return new MyLogger(name);
    }

    private MyLogger(String name) {
        log = Logger.getLogger(name);
        try {
            LogManager.getLogManager().readConfiguration(new FileInputStream("./logging.properties"));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException | SecurityException ex) {
            Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void log(Level LogLevel, String logMessage, Object param1) {
        log.log(LogLevel, logMessage, param1);
    }

    public void log(Level LogLevel, String logMessage, Throwable e) {
        log.log(LogLevel, logMessage, e);
    }

    public void log(Level LogLevel, String logMessage) {
        log.log(LogLevel, logMessage);
    }
}

logging.properties File content:

handlers = java.util.logging.FileHandler
java.util.logging.FileHandler.level     = ALL
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tF %1$tT %4$s %2$s %5$s%6$s%n
java.util.logging.FileHandler.limit     = 1048576
java.util.logging.FileHandler.count     = 5
java.util.logging.FileHandler.append    = true
java.util.logging.FileHandler.pattern   = ./logs/log-%u-%g.log

--

AKPuvvada
  • 47
  • 2
  • 10
  • I don't understand. You have a `MyLogger` but you show the properties for `FileHander`? Do you have the part where you configure your logger? EDIT: also where do you *use* this class? – markspace Aug 30 '21 at 01:49
  • 2
    Why is the `log` field `static`? Every time you create a new instance that field will be overridden for all previous instances. Also, this `MyLogger` class seems redundant; unless there are other convenience methods you aren't showing here, you're basically just recreating the `Logger` API for no apparent benefit. – Slaw Aug 30 '21 at 02:45
  • The Original Logger, does not load the logging properties file unless it is passed as -D option to JVM or the file is in Java Home. I implemented this class to Logger to load the config by default. That is the one of the benefits I am expecting from this custom logger. – AKPuvvada Aug 30 '21 at 12:27
  • 1
    try { LogManager.getLogManager().readConfiguration(new FileInputStream("./logging.properties")); } catch (FileNotFoundException ex) { Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException | SecurityException ex) { Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex); } This part is not part of the original logger and so does not create log files unless we pass it as JVM option. – AKPuvvada Aug 30 '21 at 12:28

2 Answers2

3

The documentation for SimpleFormatter.format explains that the source parameter (%2$s in the format string) is "a string representing the caller, if available; otherwise, the logger's name". This looks at the stack to determine the direct caller, which in your case will always be the MyLogger class.

To use the logger name instead, use %3$s in the java.util.logging.SimpleFormatter.format configuration rather than %2$s.

Tim Moore
  • 8,958
  • 2
  • 23
  • 34
1

Per API docs you can use the log precise methods:

There are a set of "logp" methods (for "log precise") that are like the "log" methods, but also take an explicit source class name and method name.

Combine that with the inferCaller method to make your bridge look transparent:

 public void log(Level LogLevel, String logMessage) {
    StackTraceElement stack = inferCaller();
    log.logp(stack.getClassName(), stack.getMethodName(), LogLevel, logMessage);
 }

private boolean isPrintImplFrame(String cname) {
    return MyLogger.class.getName().equals(cname);
}

private StackTraceElement inferCaller() {
    StackTraceElement stack[] = (new Throwable()).getStackTrace();
    int ix = 0;
    while (ix < stack.length) {
        StackTraceElement frame = stack[ix];
        String cname = frame.getClassName();
        if (isPrintImplFrame(cname)) {
            break;
        }
        ix++;
    }

    while (ix < stack.length) {
        StackTraceElement frame = stack[ix];
        String cname = frame.getClassName();
        if (!isPrintImplFrame(cname)) {
            return frame;
        }
        ix++;
    }

    return new StackTraceElement(MyLogger.class.getName(), "log",
            MyLogger.class.getName(), -1);
}

Using this method your code will find the stack frame that is calling your bridge logger that is not the bridge logger itself.

jmehrens
  • 10,580
  • 1
  • 38
  • 47