Good Examples Using Java.Util.Logging

Java Logger usage

If you want different Loggers, you need to supply different names for each. Hence this line of your code (in SpotifyLogger constructor) always returns the same Logger.

Logger myLogger = Logger.getLogger("");

This actually returns the java.util.logging.LogManager.RootLogger which has a single Handler which is an instance of ConsoleLogger. You subsequently remove that Handler in the first invocation of SpotifyLogger constructor, hence in every subsequent invocation, method getHandlers returns an empty array.

Since you only ever add Handlers to the global Logger, another FileHandler is added to the global logger every time SpotifyLogger constructor is called. I have not verified but I believe that a Logger will use the first, appropriate Handler in the array returned by method getHandlers, hence the behavior you are seeing whereby only the first log file is being written to, i.e. the file that you passed to the first invocation of SpotifyLogger constructor.

Note that you have not provided a reproducible example so I cannot verify any of the above with regard to your context. I only tested the code in your question in order to arrive at the above.

Consider the following rewrite of class SpotifyLogger – including a main method for testing purposes only.

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class SpotifyLogger {
private static final String[] NAMES = {"First", "Second"};
private static int count;

private int index;

public SpotifyLogger(String loggerFilePath) throws IOException {
index = count;
Logger myLogger = Logger.getLogger(NAMES[count++]);
myLogger.setUseParentHandlers(false);
// set level
myLogger.setLevel(Level.SEVERE);
// create a txt handler
FileHandler textFileHandler = new FileHandler(loggerFilePath);
SimpleFormatter simpleFormatter = new SimpleFormatter();
textFileHandler.setFormatter(simpleFormatter);
myLogger.addHandler(textFileHandler);
}

public void log(String user, Exception e) {
Logger myLogger = Logger.getLogger(NAMES[index]);
myLogger.log(Level.SEVERE, user, e);
}

public static void main(String[] args) {
try {
SpotifyLogger x = new SpotifyLogger("spotifyx.log");
SpotifyLogger y = new SpotifyLogger("spotifyy.log");
x.log("George", new Exception());
y.log("Martha", new RuntimeException());
}
catch (IOException x) {
x.printStackTrace();
}
}
}

Note that you are correct regarding parent Handlers, hence the following line in the above code:

myLogger.setUseParentHandlers(false);

After running the above code, the contents of file spotifyx.log is:

Feb 12, 2022 2:12:28 PM javalogp.SpotifyLogger log
SEVERE: George
java.lang.Exception
at javalogp/javalogp.SpotifyLogger.main(SpotifyLogger.java:38)

And the contents of file spotifyy.log is:

Feb 12, 2022 2:12:28 PM javalogp.SpotifyLogger log
SEVERE: Martha
java.lang.RuntimeException
at javalogp/javalogp.SpotifyLogger.main(SpotifyLogger.java:39)

And no log messages are written to the console.

Why not use java.util.logging?

Disclaimer: I am the founder of log4j, SLF4J and logback projects.

There are objective reasons for preferring SLF4J. For one, SLF4J allows the end-user the liberty to choose the underlying logging framework. In addition, savvier users tend to prefer logback which offers capabilities beyond log4j, with j.u.l falling way behind. Feature-wise j.u.l may be sufficient for some users but for many others it just isn't. In a nutshell, if logging is important to you, you would want to use SLF4J with logback as the underlying implementation. If logging is unimportant, j.u.l is fine.

However, as an oss developer, you need to take into account the preferences of your users and not just your own. It follows that you should adopt SLF4J not because you are convinced that SLF4J is better than j.u.l but because most Java developers currently (July 2012) prefer SLF4J as their logging API. If ultimately you decide not to care about popular opinion, consider the following facts:

  1. those who prefer j.u.l do so out of convenience because j.u.l is bundled with the JDK. To my knowledge there are no other objective arguments in favor of j.u.l.
  2. your own preference for j.u.l is just that, a preference.

Thus, holding "hard facts" above public opinion, while seemingly brave, is a logical fallacy in this case.

If still not convinced, JB Nizet makes an additional and potent argument:

Except the end user could have already done this customization for his
own code, or another library that uses log4j or logback. j.u.l is
extensible, but having to extend logback, j.u.l, log4j and God only
knows which other logging framework because he uses four libraries that
use four different logging frameworks is cumbersome. By using SLF4J, you
allow him to configure the logging frameworks he wants, not the one
you have chosen. Remember that a typical project uses myriads of
libraries, and not just yours
.

If for whatever reason you hate the SLF4J API and using it will snuff the fun out of your work, then by all means go for j.u.l. After all, there are means to redirect j.u.l to SLF4J.

By the way, j.u.l parametrization is at least 10 times slower than SLF4J's which ends up making a noticeable difference.

How to write logs in text file when using java.util.logging.Logger

Try this sample. It works for me.

public static void main(String[] args) {  

Logger logger = Logger.getLogger("MyLog");
FileHandler fh;

try {

// This block configure the logger with handler and formatter
fh = new FileHandler("C:/temp/test/MyLogFile.log");
logger.addHandler(fh);
SimpleFormatter formatter = new SimpleFormatter();
fh.setFormatter(formatter);

// the following statement is used to log any messages
logger.info("My first log");

} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

logger.info("Hi How r u?");

}

Produces the output at MyLogFile.log

Apr 2, 2013 9:57:08 AM testing.MyLogger main  
INFO: My first log
Apr 2, 2013 9:57:08 AM testing.MyLogger main
INFO: Hi How r u?

Edit:

To remove the console handler, use

logger.setUseParentHandlers(false);

since the ConsoleHandler is registered with the parent logger from which all the loggers derive.

Do you find java.util.logging sufficient?

Logging Dependencies with Third Party Libraries

Java JDK logging in most cases is not insufficient by itself. However, if you have a large project that uses multiple open-source third party libraries, you will quickly discover that many of them have disparate logging dependencies.

It is in these cases where the need to abstract your logging API from your logging implementation become important. I recommend using slf4j or logback (uses the slf4j API) as your API and if you want to stick with Java JDK logging, you still can! Slf4j can output to many different logger implementations with no problems.

A concrete example of its usefulness happened during a recent project: we needed to use third-party libs that needed log4j, but we did not want to run two logging frameworks side by side, so we used the slf4j log4j api wrapper libs and the problem was solved.

In summary, Java JDK logging is fine, but a standardized API that is used in my third party libraries will save you time in the long run. Just try to imagine refactoring every logging statement!

Using java.util.logging to log on the console

Very simple, a logger can have several handlers, with each a different level.

handler.setLevel(Level.ALL);

Java logging opening too many logfiles

@Joop nailed it (in a comment, which can't be marked as the answer). It turns out that setting a handler is an additive process, it does not simply overwrite the previous settings. That seems really unintuitive to me, but that's java.util.logging for ya... Leaving all the other properties in place, but removing the handler assignment, was the way to go.

I'm also using the reset() call suggested by @jschoen because that just seems to be a smart thing to do!

Many thanks to all of you. I'm not sure how to mark this all as "closed"... time to fiddle with the site some.

Using the java.util.logging API to log different levels to separate files

You use custom Handlers to write the Log Records.

Here is a simple, but complete example you can build upon.

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

public class LevelBasedFileHandler extends FileHandler
{
public LevelBasedFileHandler(final Level level) throws IOException, SecurityException
{
super();
super.setLevel(level);
}

public LevelBasedFileHandler(final String s, final Level level) throws IOException, SecurityException
{
super(s);
super.setLevel(level);
}

public LevelBasedFileHandler(final String s, final boolean b, final Level level) throws IOException, SecurityException
{
super(s, b);
super.setLevel(level);
}

public LevelBasedFileHandler(final String s, final int i, final int i1, final Level level) throws IOException, SecurityException
{
super(s, i, i1);
super.setLevel(level);
}

public LevelBasedFileHandler(final String s, final int i, final int i1, final boolean b, final Level level) throws IOException, SecurityException
{
super(s, i, i1, b);
super.setLevel(level);
}

@Override
public void setLevel() { throw new UnsupportedOperationException("Can't change after construction!"); }

// This is the important part that makes it work
// it also breaks the contract in the JavaDoc for FileHandler.setLevel()
@Override
public void publish(final LogRecord logRecord)
{
if (logRecord.getLevel().equals(super.getLevel())
{
super.publish(logRecord);
}
}
}

and here is how to use it

try
{
// I use the Anonymous logger here, but any named logger will work as well
final Logger l = Logger.getAnonymousLogger();
l.addHandler(new LevelBasedFileHandler("/tmp/info.log", Level.INFO));
l.addHandler(new LevelBasedFileHandler("/tmp/warn.log", Level.WARNING));
l.addHandler(new LevelBasedFileHandler("/tmp/server.log", Level.SEVERE));

l.info("This is an INFO message");
l.warning("This is a WARNING message");
l.severe("This is a SEVERE message");
}
catch (final IOException e)
{
// ignore this for this example, you should never do this in real code
}

you will get three files in /tmp each with only the messages for each particular log level in them.

Note, I like the Dependency Injection style approach of requiring the Level in the constructor so you can't "forget" to call .setLevel() when using this sub-class. I also disabled .setLevel() because calling it and changing would break the semantics of the subclass"

Just for completeness you can use a java.util.logging.Filter to acomplish the same thing. It isn't as encapsulated but it is an alternative. It is more code and more verbose, thus more to not get right.

final FileHandler infoFileHandler = new FileHandler("/tmp/info.log");
infoFileHandler.setFilter(new Filter()
{
public boolean isLoggable(final LogRecord logRecord)
{
return logRecord.getLevel().equals(Level.INFO);
}
});

Personally I still like the sub-class approach better, it is less error prone and more self documenting as of its purpose and intent.



Related Topics



Leave a reply



Submit