2

I use standard Java logging api, that is java.util.logging.

Tutorials on this that I've found use extends Formatter, however in java 8 Formatter is final.

What I want is to create my own class that determines format of messages sent in console. This is my unfinished code to initialize logging:

 // Init logger
 {
    // get the global logger to configure it
    Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    // Remove all old/default loggers
    Logger rootLogger = Logger.getLogger("");
    java.util.logging.Handler[] handlers = rootLogger.getHandlers();
    for(java.util.logging.Handler h: handlers)
        rootLogger.removeHandler(h);

    logger.addHandler(new ConsoleLoggerBase());
 }

This is my class for logging in stdout:

package cz.autoclient.logging;

import java.util.logging.Handler;
import java.util.logging.LogRecord;

public class ConsoleLoggerBase extends Handler {

    @Override
    public void publish(LogRecord record) {
        System.out.println(record.getMessage());
    }

    @Override
    public void flush() {
        System.out.flush();
    }
    // No need to close system.out
    @Override
    public void close() throws SecurityException {}
}

But the program log is empty... I am quite surprised to find no concise information about this on the internet. Can anyone explain how am I supposed to work with this API?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • You’re mistaken. [java.util.Formatter](https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html) is final, but [java.util.logging.Formatter](https://docs.oracle.com/javase/8/docs/api/java/util/logging/Formatter.html) is not. – VGR May 22 '17 at 03:40

1 Answers1

1

You must always hold a strong non-local reference to loggers that you are modifying. Otherwise your logger can be garbage collected along with your newly attached handler.

As explained in the Java Logging Overview:

Loggers keep track of their parent loggers in the logging namespace. A logger's parent is its nearest extant ancestor in the logging namespace. The root Logger (named "") has no parent.

You are attaching a handler to the GLOBAL_LOGGER_NAME. The parent of the global logger is the root logger. Normally, the global logger has no children therefore, you won't see any output unless you are directly writing to the global logger. My advice would be to forget about using the global logger. Instead focus on modifying the output format of the root logger and have your application code create named loggers.

You don't have to create a custom handler. Instead create a custom formatter and install it on the ConsoleHandler attached to the root logger.

Here is an example program:

import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class ModifyRoot {

    private static final Logger[] PINNED_LOGGERS;

    static {
        //Assuming the default config file.
        PINNED_LOGGERS = new Logger[]{
            Logger.getLogger(""),
            Logger.getLogger(ModifyRoot.class.getName())
        };

        for (Logger l : PINNED_LOGGERS) {
            for (Handler h : l.getHandlers()) {
                if (h instanceof ConsoleHandler) {
                    h.setFormatter(new DynamicFormatter());
                    h.setLevel(Level.ALL);
                }
            }
        }

        //Safe because it is already pinned.
        Logger.getLogger(ModifyRoot.class.getName()).setLevel(Level.ALL);
    }

    private static class DynamicFormatter extends java.util.logging.Formatter {

        @Override
        public String format(LogRecord record) {
            if (isJumpSite(record)) {
                return record.getSourceClassName() + '.'
                        + record.getSourceMethodName() + ' '
                        + formatMessage(record) + System.lineSeparator();
            } else {
                return formatMessage(record) + System.lineSeparator();
            }
        }

        private boolean isJumpSite(LogRecord record) {
            //Check the raw message not the formatted message.
            final String msg = String.valueOf(record.getMessage());
            return msg.startsWith("ENTRY") || msg.startsWith("RETURN")
                    || msg.startsWith("THROW");
        }
    }

    //========
    private static final String CLASS_NAME = ModifyRoot.class.getName();
    private static final Logger logger = Logger.getLogger(CLASS_NAME);

    public static void main(String[] args) {
        logger.entering(CLASS_NAME, "main");
        logger.info("Hello World!");
        logger.exiting(CLASS_NAME, "main");
    }
}

Which outputs:

ModifyRoot.main ENTRY
Hello World!
ModifyRoot.main RETURN

You should really consider configuring your loggers using the java.util.logging.config.file system property and creating your own logging.properties file.

jmehrens
  • 10,580
  • 1
  • 38
  • 47