By Pavel Sheida
Last Validated on May 31 2021 · Originally Published on May 31, 2021 · Viewed 1.5k times

Introduction

Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems.

Every log message has an associated log level. The log level helps you understand the severity and urgency of the message. Usually, each log level has an assigned integer representing the severity of the message.

Though Java provides an extendable logging system, third-party logging libraries can significantly simplify the logging process.

In this tutorial, we are going to use a Command Line Application for our examples, and cover 3 methods of logging in Java:


Prerequisites

To complete this tutorial you will need:

  • Ubuntu 20.04 distribution including the non-root user with sudo access.
  • Java installed.
  • IntelliJ IDEA installed.

How to Create a Project

To get started, you need to create a new project. To simplify the work, we are going to use a default IntelliJ  template for the Console Applications, the Command Line App template.

You can create a Command Line Application Let's break it down into simple steps, the first thing you need to do is to open the New Project windows. There are several ways how to do it in the IntelliJ IDEA.

The first one is to select Create a new project in the welcome window.

The second one, If the IntelliJ is already open, you can follow the path on the top menu bar File > New > Project.

In the New Project window click on Java, and select the Command Line App template.


How to Install Dependencies

Before starting to work on the application, you need to install some dependency packages. IntelliJ provides multiple ways to do it. However, in this tutorial we will use Maven —  a software project management and comprehension tool. Based on the concept of a project object model.

The dependency management in Java may seem complicated, but we are going to break it down into small steps.

So, the first thing you have to do is to open the Project Structure window. You can do it using File > Project Structure. Alternatively, you can press CTRL + SHIFT + ALT + S.

In the window that opens, select the Libraries tab, then press ALT + INSERT and select From Maven.

Now, you can enter a Maven package you'd like to install and press OK. The package will be available from any part of the project!


Option 1 — Logging With Built-in Tools

Java has a powerful built-in tool — the java.utils.logging package. It supports structured output, multiple logging targets, and everything a modern logging framework should support.

JUL is useful in the simplest small applications as well as in large and complex ones. Due to its rich configuration abilities, you can use it in all your projects.


Step 1 — Creating a Logger

Preparatory to creating a logger, we have to import the Logger package.

Main.java
package com.company;

import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) {

    }
}

Now, you are ready to create a logger. The process is pretty straightforward and can be done in one line of code.

Main.java
package com.company;

import java.util.logging.Logger;

public class Main {
    // create a static logger
    private static final Logger logger = Logger.getLogger(Main.class.getName());
    
    public static void main(String[] args) {

    }
}

The logger will use JUL's default log levels system. The system consists of the 7 levels:

  • Severe — used for logging serious problems occurred during execution of the program.
  • Warning — used for reporting non-critical unusual behavior.
  • Info — used for informative messages highlighting the progress of the application for sysadmin and end user.
  • Config — used for logging the configuration information.
  • Finest, Finer, Fine — used  for tracing the code.

Step 2 — Configuring the Logger

The JUL provides several ways to configure the logger, such as the properties file and programmatic configuration. The configuration consists of setting up handlers and loggers.

In the tutorial we are going to configure the logger programmatically.

Let's import the required packages:

Main.java
package com.company;

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

public class Main {
    // create a static logger
    private static final Logger logger = Logger.getLogger(Main.class.getName());
    
    public static void main(String[] args) {

    }
}

After that, you are ready to configure the logger. By default, the JUL logger has a console handler, so let's add an extra file handler for with warning and higher severity logs.

Main.java
package com.company;

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

public class Main {
    // create a static logger
    private static final Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) {
        // create a file handler
        Handler fileHandler;
        // the file handler constructor may throw an exception
        // so we have to put it into try-catch block
        try {
            fileHandler = new FileHandler("./important.log");
            // set the file handler's minimum level
            fileHandler.setLevel(Level.WARNING);
            // add the handler to the logger
            logger.addHandler(fileHandler);
        } catch (IOException e) {
            // log the exception
            logger.log(Level.SEVERE, "Error occur in FileHandler.", e);
        }
    }
}

By default, the file handlers are set up to log events structured in XML.


Step 3 — Logging

To demonstrate how the logger works, we will log some simple messages, at least one of each level.

Main.java
package com.company;

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

public class Main {
    // create a static logger
    private static final Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) {
        // create a file handler
        Handler fileHandler;
        // the file handler constructor may throw an exception
        // so we have to put it into try-catch block
        try {
            fileHandler = new FileHandler("./important.log");
            // set the file handler's minimum level
            fileHandler.setLevel(Level.WARNING);
            // add the handler to the logger
            logger.addHandler(fileHandler);
        } catch (IOException e) {
            // log the exception
            logger.log(Level.SEVERE, "Error occur in FileHandler.", e);
        }

        // logging
        logger.finest("finest message log");
        logger.finer("finer message log");
        logger.fine("fine message log");
        logger.config("config message log");
        logger.info("info message log");
        logger.warning("warn message");
        logger.severe("severe message log");
    }
}

Now, let's build and run the program. You can simply do this by pressing SHIFT + F10.

After the execution, your console's output should look like:

Output
May 31, 2021 2:52:15 AM com.company.Main main
INFO: info message log
May 31, 2021 2:52:15 AM com.company.Main main
WARNING: warn message
May 31, 2021 2:52:15 AM com.company.Main main
SEVERE: severe message log

Now, let's check the logs written in the important.log file.

important.log
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2021-05-31T03:52:15.623883648Z</date>
  <millis>1622433135623</millis>
  <nanos>883648</nanos>
  <sequence>1</sequence>
  <logger>com.company.Main</logger>
  <level>WARNING</level>
  <class>com.company.Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>warn message</message>
</record>
<record>
  <date>2021-05-31T03:52:15.662811715Z</date>
  <millis>1622433135662</millis>
  <nanos>811715</nanos>
  <sequence>2</sequence>
  <logger>com.company.Main</logger>
  <level>SEVERE</level>
  <class>com.company.Main</class>
  <method>main</method>
  <thread>1</thread>
  <message>severe message log</message>
</record>
</log>

Option 2 — Logging with log4j

Log4j is a popular logging framework for Java with great documentation, a lot of related materials, and a big developer community. In the long list of the log4j features you can find:

  • Custom formatting layout
  • Support of structured logging, that allows them to be treated as data sets rather than text
  • Compatibility with asynchronous applications and systems
  • Multiple logging targets, such as files, console, console, email, and many other outputs.

Step 1 — Installing Dependencies

To start using the log4j package, you have to install it. We are going to install the latest version at the time of writing — org.apache.logging.log4j:log4j-1.2-api:2.11.2 via Maven.


Step 2 — Creating a Logger

Preparatory to creating a logger, we have to import 2 packages: log4j.Logger and log4j.LogManager.

Main.java
package com.company;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;

public class Main {
    public static void main(String[] args) {

    }
}

At the moment, you are ready to create a logger. For the application, we are going to use the simplest way to do it in log4j — the LogManager class. It provides the getLogger method to get a unique logger per each class. Unlike the globally configured logger, the logger per class helps you to easily capture the source of the log message.

Main.java
package com.company;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;

public class Main {
    // create a static logger
    private static final Logger logger = LogManager.getLogger(Main.class);
    
    public static void main(String[] args) {

    }
}

The logger will use the log4j's default log levels system. The system consists of the 6 levels:

  • Fatal — used for reporting about errors that are forcing shutdown of the application.
  • Error — used for logging serious problems occurred during execution of the program.
  • Warn  — used for reporting non-critical unusual behavior.
  • Info — used for informative messages highlighting the progress of the application for sysadmin and end user.
  • Debug — used for debugging messages with extended information about application processing.
  • Trace — used for tracing the code.

Step 3 — Creating a Config

Log4j provides 2 ways how to configure the logging system: programmatically or via a configuration file. You can get advanced information about each of them in the documentation.

In the tutorial we're going to configure the logger using log4j2.xml file.

The first thing to do is to create a log4j2.xml file in the root of your application, in the src folder. You can do it by pressing right-clicking the src tab in the sidebar and clicking the New option. The only thing left is to enter the log4j2.xml name in the opened window.

The log4j2.xml file should look like:

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders></Appenders>
    <Loggers>
        <Root></Root>
    </Loggers>
</Configuration>

Now, let's write a config. The config is written in the XML language. If you are not familiar with the XML language, you may dive in the great XML tutorial by w3school.

In our case the config consists of 2 parts: setting up the root logger and setting up the appenders.

We've decided to add 2 logging targets for the application: the console and a file. In the config file, each of them will be represented by a separate XML element. Let's write the appenders in the config file.

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <File name="file" fileName="logs/all.log">
            <PatternLayout>
                <pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n</pattern>
            </PatternLayout>
        </File>
    </Appenders>
    <Loggers>
        <Root></Root>
    </Loggers>
</Configuration>

The first appender will write all logs with debug and higher severity to the console, the second one — to the logs/all.log file.

Now, let's specify the minimum level for the root logger and appenders used by the logger.

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <File name="file" fileName="logs/all.log">
            <PatternLayout>
                <pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n</pattern>
            </PatternLayout>
        </File>
    </Appenders>
<Loggers>
        <Root level="debug">
            <AppenderRef ref="console"/>
            <AppenderRef ref="file"/>
        </Root>
    </Loggers>
</Configuration>

The appenders are referred by the name property, so make sure to make it unique!


Step 4 — Logging

To demonstrate how the logger works, we will log some simple messages, at least one of each level. It's a pretty straightforward task, because of the clear log4j API.

More specifically, log4j logger provides 6 methods for logging: trace,debug, info, warn, error, and fatal.

Main.java
package com.company;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;

public class Main {
    // create a static logger
    private static final Logger logger = LogManager.getLogger(Main.class);
    
    public static void main(String[] args) {
	// logging
        logger.trace("trace message log");
        logger.debug("debug message log");
        logger.info("info message log");
        logger.warn("warn message log");
        logger.error("error message log");
        logger.fatal("fatal message log");
    }
}

Now, let's build and run the program. You can simply do this by pressing SHIFT + F10.

After the execution, your console's output should look like:

Output
02:46:11.353 [main] DEBUG com.company.Main - debug message log
02:46:11.359 [main] INFO  com.company.Main - info message log
02:46:11.359 [main] WARN  com.company.Main - warn message log
02:46:11.360 [main] ERROR com.company.Main - error message log
02:46:11.361 [main] FATAL com.company.Main - fatal message log

Now, let's check the logs written in the file. The all.log is located in the ./logs directory.

all.log
[DEBUG] 2021-05-29 02:46:11.353 [main] Main - debug message log
[INFO ] 2021-05-29 02:46:11.359 [main] Main - info message log
[WARN ] 2021-05-29 02:46:11.359 [main] Main - warn message log
[ERROR] 2021-05-29 02:46:11.360 [main] Main - error message log
[FATAL] 2021-05-29 02:46:11.361 [main] Main - fatal message log

Option 3 — Logging with Logback

Logback is one of the most popular logging frameworks in Java's community. It's a successor of the log4j framework, which provides more configuration options.


Step 1 — Installing Dependencies

To start using the Logback package, you have to install it. We are going to install the latest stable version at the time of writing — ch.qos.logback:logback-classic:jar:1.2.3 via Maven.


Step 2 — Creating a Logger

Preparatory to creating a logger, we have to import 2 packages: org.slf4j.Logger and org.slf4j.LoggerFactory.

Main.java
package com.company;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {

    }
}

We don't use the Logback package directly, because it's commonly accessed via the SLF4J API.

At the moment, you are ready to create a logger. We are going to use the simplest way to do it  — the LoggerFactory class. It provides the getLogger method to get a unique logger per each class.

Main.java
package com.company;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;

public class Main {
    // create a static logger
    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    
    public static void main(String[] args) {

    }
}

The logger will use the Logback's default log levels system. The system consists of the 5 levels:

  • Error — used for logging serious problems occurred during execution of the program.
  • Warn  — used for reporting non-critical unusual behavior.
  • Info — used for informative messages highlighting the progress of the application for sysadmin and end user.
  • Debug — used for debugging messages with extended information about application processing.
  • Trace — used for tracing the code.

Step 3 — Configuring the KLogger

Logback provides 2 ways how to configure the logging system: programmatically or via a configuration file. You can get advanced information about each of them in the documentation.

In the tutorial we're going to configure the logger using logback.xml file.

The first thing to do is to create a logback.xml file in the root of your application, in the src folder. You can do it by pressing right-clicking the src tab in the sidebar and clicking the New option. The only thing left is to enter the logback.xml name in the opened window.

The logback.xml file should look like:

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <root></root>
</configuration>

Now, let's write a config. The config is written in the XML language. If you are not familiar with the XML language, you may dive in the great XML tutorial by w3school.

In our case the config consists of 2 parts: setting up the root logger and setting up the appenders.

We've decided to add 2 logging targets for the application: the console and a file. In the config file, each of them will be represented by a separate XML element. Let's write the appenders in the config file.

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/important.log</file>
        <append>true</append>
        <!-- set immediateFlush to false for much higher logging throughput -->
        <immediateFlush>true</immediateFlush>
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <root></root>
</configuration>

The first appender will write all logs with debug and higher severity to the console, the second one — to the logs/all.log file.

Now, let's specify the minimum level for the root logger and appenders used by the logger.

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/important.log</file>
        <append>true</append>
        <!-- set immediateFlush to false for much higher logging throughput -->
        <immediateFlush>true</immediateFlush>
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

The appenders are referred by the name property, so make sure to make it unique!


Step 4 — Logging

To demonstrate how the logger works, we will log some simple messages, at least one of each level. It's a pretty straightforward task, because of the clear log4j API.

More specifically, Logback logger provides 5 methods for logging: trace,debug, info, warn, and error.

Main.java
package com.company;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    // create a static logger
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        // logging
        logger.trace("trace message log");
        logger.debug("debug message log");
        logger.info("info message log");
        logger.warn("warn message log");
        logger.error("error message log");
    }
}

Now, let's build and run the program. You can simply do this by pressing SHIFT + F10.

After the execution, your console's output should look like:

Output
07:18:26.923 DEBUG com.company.Main - debug message log
07:18:26.955 INFO  com.company.Main - info message log
07:18:26.956 WARN  com.company.Main - warn message log
07:18:26.957 ERROR com.company.Main - error message log

Now, let's check the logs written in the important.log file.

important.log
2154 [main] DEBUG com.company.Main - debug message log
2186 [main] INFO  com.company.Main - info message log
2187 [main] WARN  com.company.Main - warn message log
2188 [main] ERROR com.company.Main - error message log

Conclusion

Proper logging can greatly assist in the support and development of your application. This may seem like a daunting task, but Java has good built-in tools and many fast and configurable logging libraries.

In the tutorial, you have configured your logging system in 3 different ways:

They all have a lot in common, but each has a specific configuration and use.

Now developing and maintaining your Java applications will be much easier!