8 minutes read

Introduction to Java logging

Logs are records of a software application, which we choose to save to a file or display in a console. These records could describe anything: an event in the application, the value of a variable, an error or an exception in the application. Logs are mostly used for debugging purposes. Today, we'll learn about the java.util.logging package, which is responsible for giving the developers logging capabilities within Java's standard SDK. There are several components you need to learn when working with Java logging: the Logger class, the FileHandler class, the ConsoleHandler class, SimpleFormatter, XMLFormatter, Level, LogRecord, and LogManager. In this topic, we will discuss the first 6 of those and see how loggers, handlers, filters, and formatters work together.

Java logging component hierarchy: Logger class, FileHandler class, ConsureHandler class, SimpleFormatter, XMLFormatter, Level, LogRecord and LogManager

Logger class

The Logger class is the most important and fundamental component in the logging package. The standard practice is to create a logger instance for each class. The Logger class introduces several methods to print log messages: the log() method is one of them. Check out the following example:

import java.util.logging.Level
import java.util.logging.Logger

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val logger = Logger.getLogger(Main::class.java.name)
        logger.log(Level.WARNING, "Hello " + logger.name)
    }
}

It will output:

WARNING: Hello Main

First, we create a logger instance using the class name. Then we call the log() method to print the log message. The log() method takes two arguments: its first argument is a Level object, and the second argument is a message. Level.WARNING is a constant from the Level class in the logging package.

Every log message is related to a certain log level. In the above example, it is Warning. Java uses Info as its default log level. There are seven log levels in Java's logging package. The list below shows them from the highest to the lowest severity.

  • SEVERE

  • WARNING

  • INFO

  • CONFIG

  • FINE

  • FINER

  • FINEST

The following image shows the integer values of the log levels:

shows the integer values of the log levels

The Logger class contains methods, such as info() and config(), where you don’t need to provide a log level as an attribute.
Check out the following example:

import java.util.logging.Logger

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val logger = Logger.getLogger(Main::class.java.name)
        logger.severe("Severe Log")
        logger.warning("Warning Log")
        logger.info("Info Log")
    }
}

The output will be:

January 09, 2023 9:26:04 AM Main main
SEVERE: Severe Log
January 09, 2023 9:26:04 AM Main main
WARNING: Warning Log
January 09, 2023 9:26:04 AM Main main
INFO: Info Log

Handlers and formatters

The next important components in the logging package are handlers and formatters, which often work together. Handlers are responsible for sending actual logs to the outside world. There is an abstract class called Handler in the java.util.logging package. It is extended by five concrete classes. Two most important classes among them are ConsoleHandler and FileHandler. ConsoleHandler writes log messages to System.err, while FileHandler writes log messages to a file.

Usually, a handler uses a formatter to format the log message. There are two types of formatters in the logging package. Those are SimpleFormatter and XMLFormatter. Of course, both of them extend the Formatter abstract class in the logging package.
Check out the following example to better understand handlers and formatters:

import java.util.logging.FileHandler
import java.util.logging.Handler
import java.util.logging.Logger
import java.util.logging.XMLFormatter

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val logger = Logger.getLogger(Main::class.java.name)
        val fileHandler: Handler = FileHandler("default.log")
        logger.addHandler(fileHandler)
        fileHandler.formatter = XMLFormatter()
        logger.info("Info log message")
    }
}

It will create a log file called default.log, which will contain the following XML text:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2023-01-09T07:29:56.688157Z</date>
  <millis>1673249396688</millis>
  <nanos>157000</nanos>
  <sequence>0</sequence>
  <logger>Main</logger>
  <level>INFO</level>
  <class>Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>Info log message</message>
</record>
</log>

Filters

Let’s discuss filters now. When we are developing a software application, we write as many log messages as possible. But we don’t want all the log messages to be executed every time the application runs: it will waste resources and may result in unnecessarily long log files. That’s why we use filters. Let’s say you want to print only info messages. For that, first, you have to create a custom filter class by implementing the Filter interface in the logging package.

class FilterExample : Filter {
    override fun isLoggable(record: LogRecord): Boolean {
        return if (record.level !== Level.INFO) {
            false
        } else true
    }
}

Now, you create an object of the FilterExample class and then use the filter field of the Logger instance to set the filter.

import java.util.logging.Filter
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger


object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val logger = Logger.getLogger(Main::class.java.name)
        val filter: Filter = FilterExample()
        logger.filter = filter
        logger.severe("Severe Log")
        logger.info("Info Log")
        logger.warning("Warning Log")
    }
}

When this code is executed, only the Info log message will be printed:

January 09, 2023 9:36:42 AM Main main
INFO: Info Log

Conclusion

Let's summarize what we've learned in this topic. First, java.util.logging is part of the Java SDK, and it is responsible for giving logging capabilities to developers. We've discussed several components of the logging package. Logger instances are responsible for creating log messages. We usually create a logger instance for every class we are going to add logs to. Handlers are responsible for sending log message out of the application. If you want to print log messages to console, use ConsoleHandler. If you want to write log messages to a file, use FileHandler. Formatters format log messages. If you want to log messages in the XML format, use XMLFormatter. Finally, we discussed filters, which help you control which logs are to be executed when the application runs.

32 learners liked this piece of theory. 4 didn't like it. What about you?
Report a typo