Get Started Quickly With Java Logging

You’ve already seen how to get started with C# logging as quickly as possible.  But what if you’re more of a Java guy or gal? Well, then we’ve got your back, too: today’s post will get you up to speed with logging using C#’s older cousin.

As in the previous post in this series, we’ll not only provide a quick guide but also go into more detail about logging, diving particularly into the what and why of logging.

The Simplest Possible Java Logging

For this simple demo, I’m going to use the free community version of IntelliJ IDEA. I’m also assuming that you have the Java JDK installed on your machine.

First, open IntelliJ IDEA and click on “Create New Project”:

IntelliJ IDEA Screenshot

On the next screen, select “Java” as the type of project, on the left panel. You’ll also need to point to the JDK’s path, in case you haven’t yet.

JDK Path Screenshot

Third step—mark the “Create project from template” option and then select “Command Line App”:

New Project Screenshot

I’ve called my project “JavaLoggingDemo,” as you can see from the image below.

JavaLoggingDemo Screenshot

After doing the steps above, you should see the Main class that was automatically created for you. It should look like this:

package com.company;

public class Main {

    public static void main(String[] args) {
    // write your code here
    }
}

Let’s get to work. Replace the “// write your code here” comment with the following line:

System.out.println("Hello there!");

Now you have an application that displays a friendly greeting.

Next, let’s run our app. Go to Run > Run 'Main' or use the shortcut Shift + F10 (Windows and Linux) or Control + R on Mac.

If everything went well, you should see something like this:

Program File Screenshot

Nice. But let’s say the requirements for our little app changed, as they always seem to do. Now we need our app not only to display a message but also to log it somewhere. How to go about that?

There are a lot of sophisticated options we could use, but let’s try and do the simplest thing we can. Edit the code in your main function so it looks like this:

public static void main(String[] args) throws IOException {
    String message = "Hello there!";
    System.out.println(message);
    Files.write(Paths.get("log.txt"), message.getBytes());
}

Notice the changes. We’ve added a new variable (message) to store the greeting’s text. Then there’s the printing. And finally, there’s a new line, which is meant to save the text to a file.

We also had to add a throw declaration to our method since the last line can throw an IOException. Additionally, we’ve added some new import declarations. The whole code now looks like this:

package com.company;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {

    public static void main(String[] args) throws IOException {
        String message = "Hello there!";
        System.out.println(message);
        Files.write(Paths.get("log.txt"), message.getBytes());
    }
}

When you run your application again, it should behave the same as before. But the last line added will create a log file for you. It’ll be located in your project folder:

Project File Screenshot

That’s pretty much it. You’ve just written your first Java logger! And with that first, small step, you’re ready to try larger ones.

What Is Application Logging?

First things first. Let’s briefly define what application logging is.

According to Wikipedia,

In computing, a log file is a file that records either events that occur in an operating system or other software runs, or messages between different users of a communication software. Logging is the act of keeping a log.

A little bit vague, right? I tend to prefer the definition we gave in our C# logging post:

Application logging involves recording information about your application’s runtime behavior to a more persistent medium.

With the what out of the way, let’s get to the why of logging.

What’s the Motivation for Logging?

I think the key part of the logging definition we’ve presented in the previous section was the word persistent. Why would we need to record information about the behavior of our app in a persistent medium?

Well, for the same reason we record anything to a persist medium: so we can get back to it later. The question then becomes “Why would you want to do that with events that happened in an application execution?”

It all boils down to the nature of software itself.

A piece of software in production is a very complicated thing, more often than not running in a totally non-controlled environment. How do you know it’s going to work? And when things go wrong, how can you know what exactly went off the rails?

You can always cross your fingers and hope, but as they say, hope is not a strategy. Logging is, though!

In a nutshell, you use logging so that you can after-the-fact debug your application. By reading the log entries, you can understand the events that happened with your application, or even retrace the steps taken by a user, in order to diagnose and fix issues.

Evolving Our Approach: What Should We Capture? And To Where?

Being able to do an after-the-fact investigation on your app sounds like a great benefit — and, indeed, it is. Our humble logger isn’t able to provide us this benefit, though, since all it can do is write some text to a file.

Our job now should be to evolve our logger, turning it into a richer source of information for its future readers. Now, how to go about that?

The key point to understand is the nature of a log entry. Some of this is familiar ground, but I’m going to recap here for completeness’ sake. Think of a log entry as an event. Something that is of interest to your application happened in a given instant in time. So the value of your log entry derives from the data you capture about the event.

The following list contains some examples of things that you’ll probably want to capture:

  • A timestamp. Exactly when did the event take place? (You’ll want to record times in UTC and using a standard format such as ISO-8601, in case you’re logging to a file.)
  • Context that makes it clear to a reader what the entry’s about. Just recording “Hello, user!” might prove confusing weeks or months later. A better message might say, “Recorded user greeting of ‘Hello, user!’”
  • Tags and/or categories for each entry, to enable later search and classification.
  • Log levels, such as “error,” “warning,” or “information,” that allow even more filtering and context.

After deciding what things you want to capture, the next step is defining where you want to log to. Even though I’ve used a file in our first example, there’s nothing stopping you from logging to other media, such as a database (relational or not), the windows event viewer, or even the console.

Enter the Logging Framework

Time to get back to our demo. Here’s the thing: it was designed to give you a quick start on the Java logging world. But now you’re equipped to look at it in a more critical way, so let’s do just that. As it turns out, our humble logger is inadequate in several important ways.

For one thing, the code always overwrites the log file. A log strategy that only allows you to see the last event isn’t terribly useful, isn’t it? Since we’re talking about overwriting the file, it probably wouldn’t hurt to think about file permissions, how to deal with concurrent access, and that sort of thing.

Finally, it sounds like an awful lot of work having to write boilerplate code in each logging call to get log levels, timestamps, and all that jazz I mentioned in the previous section.

I could go on and on, but you’ve got the picture: coming up with a sound Java logging approach requires a lot of thought. I have good news, though. As it turns out, people have already solved these problems. Not only that, they provide their solutions for you, ready to use and often for free, in the form of a logging framework.

A logging framework is a package you install in your application that provides easy and configurable logging for you. With a little bit of configuration, you can make one-line calls as simple as the one in our toy example. But this time, they’ll have consistent and nicely formatted log entries, and they’ll have answers to all those questions from the previous section.

Installing a Logging Framework

The first thing we’re going to do is go back to our codebase and delete that line that writes to the file. Then we can proceed to install a logging framework called log4j. This is only one of the options that are out there, and I do encourage you to try alternatives later. But since this is a quick guide, it makes sense to go with log4j since it’s  a widely used framework.

On the Project tool windows, right-click on the “JavaLoggingDemo” module and then on “Add Framework Support…”:

Add Framework Screenshot

Now, select “Maven” and click OK:

Maven Screenshot

After you’ve done this, the IDE will create a pom.xml file for you and open it for editing:

Pom XML Screenshot

Now, copy and paste the following XML text into your file:

<dependencies>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.10.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.10.0</version>
      </dependency>
    </dependencies>

The whole file should look like this by now:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>JavaLoggingDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.10.0</version>
        </dependency>
    </dependencies>     
</project>

After adding the dependencies to the file, you should see a small pop-up in the bottom-right corner of your screen. Click on “Import Changes” to finish the installation.

Import Changes Screenshot

Configuring the Logger

Now it’s time to configure log4j. What I’m going show you is one way of doing this; it’s not the only one, and it’s probably not even the best one, whatever “best” may mean. But it’s definitely shorter than a lot of the tutorials you see out there.

Pretty much all you have to do is to create a configuration file and then write some lines of code. Don’t believe me? Well, let me just show you, then.

First, go to the “Project” tool window on IntelliJ. Expand the folders and locate the “resources” folder, under JavaLoggingDemo > src > main, just like the image below:

Java Logging Demo Screenshot

Now, right-click on the resources folder, and then select New > File:

New File Screenshot

When you’re prompted for a name, enter “log4j2.xml.” After the file is created, paste the following text into it:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <File name="FileAppender" fileName="proper.log" immediateFlush="false" append="true">
            <PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="ALL">
            <AppenderRef ref="FileAppender"/>
        </Root>
    </Loggers>
</Configuration>

Editing the Code

First, at the top of the file, add the following two import declarations:

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

Then, add the following line to the top of the Main class, which declares a private field to hold the instance of the logger:

private static final Logger logger = LogManager.getLogger(Main.class);

Finally, add the following line of code to where you earlier had the call to Files.write(Paths.get("log.txt"), message.getBytes()):

logger.info(message);

The whole class should now look like this:

package com.company;

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

public class Main {

    private static final Logger logger = LogManager.getLogger(Main.class);

    public static void main(String[] args) {
        String message = "Hello there!";
        System.out.println(message);
        logger.info(message);
    }
}

And we’re done! Now all you have to do is run the application.

Checking the Results

Now navigate back to the application folder and notice a new file there, called “proper.log.” If you open it using a text editor, you should see the following:

2017-12-31 15:39:29.739 [main] INFO  com.company.Main - Hello there!

Let’s break this line into its components.

2017-12-31 15:39:29.739

First, we have the timestamp, in the ISO-8601-compliant format.

[main]

This refers to the name of the thread from which the event originated.

INFO

Here we have the logging level.

com.company.Main

Then, the name of the class.

Hello there!

Last, but not least, the message itself.

The Power of a  Logging Framework

I think you’ll agree that our logger just got a lot more useful with the update we’ve made. Just by creating a config file and writing a few lines of code, we were able to configure a realistic example of a logger. This isn’t all there is to it, of course, but it’s already enough to be useful in a real application. What’s more, it gives you a pretty good idea of the power a logging framework can put into your hands.

The XML configuration file is one the places this power really manifests itself.  It offers you a lot of options to configure each entry that you log, such as the name of the log file or if the logger should append to it or overwrite it, just to name a few.

A lot of the flexibility log4j provides is due to something called an appender.  The appender is the component that effectively writes the message to some medium, and there are many of them available. This means it’s possible to direct your logs somewhere else entirely, just by adding a new appender to the XML file, without even touching your application code.

Separation of concerns at its finest, if you ask me.

Happy Learning!

What we’ve seen today is just the tip of the iceberg. Make no mistake: you now have a lot of learning ahead of you. But you have the advantage of already having a live, functional, and realistic setup to work with. Start by playing with and tweaking it. Here are some tips on what you can do:

And to learn more about Java logging and logging strategies in general, you probably won’t find a better place then Scalyr’s blog. Scalyr offers a log aggregation tool, which means that once you have lots of log files and data, they’ll help you organize, search, and make sense of all these data. So stay tuned for more!

Learning something from zero can be an overwhelming task. Fortunately for you, you don’t have to do that. We’ve given you the fundamentals upon which you can build a solid and long-lasting body of knowledge.

Now it’s up to you.

This post was written by Carlos Schults, a software developer with experience in both desktop and web development, now trying his hand at mobile.  Carlos has a passion for writing clean and concise code, and he’s also very interested in practices that help you improve your app’s health.

Leave a Reply

Your email address will not be published. Required fields are marked *