What are the benefits of using parameterized log messages?

When creating a program in Java, we are mostly, if not always, add logging mechanism in our program. This log can be use to add debug information to our program, which can help us when problem occurs in our program. We usually add log message to the start of our methods to show the value of input parameters, or at the end of the methods before exiting to show the process results. We also add log information in the catch block of the try-catch statement to log messages related to exception that occurs in our program.

Here’s an example to illustrate the difference:

Concatenation approach:

logger.info("User " + username + " logged in at " + loginTime);

Parameterized approach:

logger.info("User {} logged in at {}", username, loginTime);

In Java, using parameterized log messages instead of concatenation offers several benefits:

Performance

Parameterized log messages can improve performance compared to string concatenation. When you concatenate strings, the JVM creates a new string object each time, which can lead to unnecessary memory allocation and garbage collection overhead. Parameterized messages, on the other hand, only evaluate the placeholders when the log message is actually logged, which can reduce memory usage and improve performance.

Readability

Parameterized log messages often result in cleaner and more readable code. By separating the log message template from the actual values, it’s easier to understand the intent of the log message and identify the dynamic values being logged.

Internationalization (i18n) and Localization (l10n)

Parameterized log messages make it easier to support internationalization and localization efforts. Since the placeholders in the log message template remain the same regardless of the language, translators can focus solely on translating the template and not worry about the dynamic values.

Prevention of unnecessary string manipulation

When concatenating strings for log messages, you may inadvertently perform unnecessary string manipulation operations (e.g., converting non-string values to strings). With parameterized messages, these operations are only performed if the log message is actually logged, reducing unnecessary computation.

Avoidance of potential formatting issues

When concatenating strings, you may encounter formatting issues, especially if the dynamic values contain special characters or formatting codes. Parameterized messages handle formatting automatically, ensuring that the logged values are properly formatted according to their data types.

Overall, using parameterized log messages can lead to more efficient, readable, and maintainable code in Java logging practices.

Below is another ilustration of adding log messages in our program:

package org.kodejava.util.logging;

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

public class ExampleLogger {

    private static final Logger logger = Logger.getLogger(ExampleLogger.class.getName());

    public void performLogin(String username, String loginTime) {
        // Simulate login process
        boolean loginSuccessful = true; // Assume login is successful for demonstration

        if (loginSuccessful) {
            // Log successful login using parameterized message
            logger.log(Level.INFO, "User {0} logged in at {1}", new Object[]{username, loginTime});
        } else {
            // Log failed login using parameterized message
            logger.log(Level.WARNING, "Failed login attempt for user {0} at {1}", new Object[]{username, loginTime});
        }
    }

    public static void main(String[] args) {
        ExampleLogger exampleLogger = new ExampleLogger();
        exampleLogger.performLogin("john_doe", "2024-06-08 10:00:00");
    }
}

In this example:

  • We define a performLogin method that simulates a user login process. The method takes username and loginTime as parameters.
  • Inside the method, we set a boolean variable loginSuccessful to true for demonstration purposes (assuming the login is successful).
  • We then use the java.util.logging.Logger class to log the login event. We use parameterized log messages with placeholders {0} and {1} for username and loginTime, respectively.
  • Depending on whether the login is successful or not, we log the event at different levels (INFO for successful login and WARNING for failed login).
  • In the main method, we create an instance of ExampleLogger and call the performLogin method with sample values for username and loginTime.

This example demonstrates the usage of parameterized log messages in a complete method for logging login events in Java.

How do I create a rolling log files?

In this example we create a rolling or a sequenced of log files. Instead of just limiting the file size (see. How do I limit the size of log file) we can also make the log file to roll. This will prevent a lost to an important log message if we use a single log file.

When using more than one file the log file name will have a sequence number in it starting from 0 to N-1. If we set the count to 5 then we’ll have log files such as myapp.log.0, myapp.log.1 up to myapp.log.5.

If the first log file (myapp.log.0) is about to full, it will be renamed to (myapp.log.1) before the log is written to the first log file. The log is always written to the first file (myapp.log.0).

To read the log messages in sequence you need to start from the highest to the lowest sequence number. By running this program multiple times you’ll see the creation of the log file one by one.

package org.kodejava.util.logging;

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

public class RollingLogFile {
    // Set a small log file size to demonstrate the rolling log files.
    public static final int FILE_SIZE = 1024;

    public static void main(String[] args) {
        Logger logger = Logger.getLogger(RollingLogFile.class.getName());

        try {
            // Creating an instance of FileHandler with 5 logging files
            // sequences.
            FileHandler handler = new FileHandler("myapp.log", FILE_SIZE, 5, true);
            handler.setFormatter(new SimpleFormatter());
            logger.addHandler(handler);
            logger.setUseParentHandlers(false);
        } catch (IOException e) {
            logger.warning("Failed to initialize logger handler.");
        }

        logger.info("Logging information message.");
        logger.warning("Logging warning message.");
    }
}

How do I limit the size of log file?

In this example you learn how to limit the size of a log file when using a FileHandler handler. Limiting the log file will prevent the log file to grow wildly without limit.

package org.kodejava.util.logging;

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

public class LogFileLimit {
    // The log file size is set to 1 MB.
    public static final int FILE_SIZE = 1024 * 1024;

    public static void main(String[] args) {
        Logger logger = Logger.getLogger(LogFileLimit.class.getName());

        try {
            // Create a FileHandler with 1 MB file size and a single log file.
            // We also tell the handler to append the log message.
            FileHandler handler = new FileHandler("myapp.log", FILE_SIZE, 1, true);
            logger.addHandler(handler);
        } catch (IOException e) {
            logger.warning("Failed to initialize logger handler.");
        }

        logger.info("Test info");
        logger.warning("Test warning");
        logger.severe("Test severe");
    }
}

How do I create a custom logger Formatter?

To create a custom Formatter we need to extend the java.util.logging.Formatter abstract class and implements the format(LogRecord) method. In the method then we can format the log message stored in the LogRecord to match our need.

The java.util.logging.Formatter class also have the getHead(Handler) and getTail(Handler) which can be overridden to add a head and a tail to our log message.

package org.kodejava.util.logging;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.*;

public class LogCustomFormatter {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger(LogCustomFormatter.class.getName());
        logger.setUseParentHandlers(false);

        MyFormatter formatter = new MyFormatter();
        ConsoleHandler handler = new ConsoleHandler();
        handler.setFormatter(formatter);

        logger.addHandler(handler);
        logger.info("Example of creating custom formatter.");
        logger.warning("A warning message.");
        logger.severe("A severe message.");
    }
}

class MyFormatter extends Formatter {
    // Create a DateFormat to format the logger timestamp.
    private static final DateFormat df = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss.SSS");

    public String format(LogRecord record) {
        StringBuilder builder = new StringBuilder(1000);
        builder.append(df.format(new Date(record.getMillis()))).append(" - ");
        builder.append("[").append(record.getSourceClassName()).append(".");
        builder.append(record.getSourceMethodName()).append("] - ");
        builder.append("[").append(record.getLevel()).append("] - ");
        builder.append(formatMessage(record));
        builder.append("\n");
        return builder.toString();
    }

    public String getHead(Handler h) {
        return super.getHead(h);
    }

    public String getTail(Handler h) {
        return super.getTail(h);
    }
}

Below is an output produced by the custom formatter above.

08/10/2021 07:55:55.153 - [org.kodejava.util.logging.LogCustomFormatter.main] - [INFO] - Example of creating custom formatter.
08/10/2021 07:55:55.164 - [org.kodejava.util.logging.LogCustomFormatter.main] - [WARNING] - A warning message.
08/10/2021 07:55:55.164 - [org.kodejava.util.logging.LogCustomFormatter.main] - [SEVERE] - A severe message.

How do I prevent the logger send log messages to its parent logger?

To prevent log records being forwarded to the logger’s parent handlers we can set false the useParentHandlers field using the Logger.setUserParentHandlers(boolean useParentHandlers) method.

package org.kodejava.util.logging;

import java.util.logging.Logger;
import java.util.logging.ConsoleHandler;

public class NoParentLogger {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger(NoParentLogger.class.getName());

        // Do not forward any log messages the logger parent handlers.
        logger.setUseParentHandlers(false);

        // Specify a ConsoleHandler for this logger instance.
        logger.addHandler(new ConsoleHandler());
        logger.info("Logging an information message.");
    }
}