Ever felt lost in your own code?

Wondering why your Java app's acting weird?

Tired of System.out.println everywhere?

Java logging's got your back.

It helps in understanding the behavior of the application, tracing errors, optimizing performance, and tracking the application flow.

In Java, logging is managed through various components each playing a crucial role in the logging process. The key logging components are Loggers, Handlers, Log Levels, Formatters, and Logging methods.

Think of it as your app's diary:

  • Every key play gets recorded
  • When something goes wrong, you've got the replay
  • No more guessing what happened during runtime

Java provides a built-in logging library java.util.logging. Additionally, other frameworks can be used to perform logging and follow best practices for effective logging.

To quickly address the key points about Java logging, here's a quick reference guide.

Java Log Quick Reference

Java logging is a crucial feature for monitoring, debugging, and maintaining Java applications.

Built-in Logging

Java provides a built-in logging framework called java.util.logging (JUL).

Basic Usage

import java.util.logging.Logger;

Logger logger = Logger.getLogger("MyClassName");
logger.info("This is an informational message");
logger.warning("This is a warning message");

Log Levels

From highest to lowest severity - SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST.

  • Log4j 2
  • SLF4J
  • Logback

Key Components

  • Loggers: Create and manage log messages
  • Handlers: Determine where log messages are sent (console, file, etc.)
  • Formatters: Define how log messages are formatted

Best Practices

  • Use appropriate log levels
  • Include relevant context in log messages
  • Configure log rotation for file logging
  • Avoid logging sensitive information

Configuration

Can be done programmatically or via configuration files (e.g., logging.properties for JUL, XML files for external frameworks).

Advanced Logging

For more advanced logging needs, consider using external frameworks which offer better performance, more features, and easier configuration.

This quick reference provides an overview of Java logging essentials. For more detailed information, continue reading the comprehensive guide below.

Java Logging Components

Loggers

Applications create logs with the help of Logger objects also known as Loggers. These Loggers are obtained from LoggerFactory using the getLogger() method.

Logger names are usually arbitrary strings but they contain the package names and class names of the logged component separated by the dot(.). Users can also create anonymous Logger objects that don’t appear in the shared namespace.

Logger objects keep track of their parent loggers. If the log level or any other attributes are not present then handlers go up in the hierarchy of the given logger to get the information.

A strong reference to the logger object must be stored to avoid it being collected by a garbage collector.

Here is an example of requesting a Logger object from the Logger factory:

package com.example.mypackage;
import java.util.logging.Logger;
public class GetLoggerExample {

    public static void main(String[] args) {
        // Get a logger using the class name
        Logger logger = Logger.getLogger(GetLoggerExample.class.getName());
        logger.fine("This is a fine log message");

    }
}

Output:

FINE: This is a fine log message

Log Levels

Every Log message has a LogLevel object. It is usually an Enumeration of 5 to 7 elements indicating the importance and extremity of the given log message SEVERE being the most important and FINE being the least important.

The application is also set to a log level. So based on the current log level of the application, it only cares about the log messages that are marked with equal or severe log levels. Loggers perform a cheap check to see if the message is effective.

After the test Loggers allocate a LogRecord describing the logging message, and then the filter is called to further check if the log record should be published or not. If the LogRecord passes the filter’s test then it is sent to the Handlers.

Log LevelMeaning
SEVERESevere is the most critical log message which tells the developer that the application needs immediate attention.
WARNINGWarning message highlights potential issues which could create severe problems in the future if not addressed.
INFOIf you just want to inform that some event is performed Info level can be used.
CONFIGIt is responsible for logging configuration details.
FINEFine log level is used for debugging, it provides complete details of the application behavior.
FINEREven more, minute debug information is written in finer logs, usually used for complex logging needs.
FINESTMessages with extreme log details are written at this level.

There are two additional levels OFF and ALL:

ALLThis level prints all the logging messages irrespective of the logging level.
OFFThis level turns of all the logs.

Let us look at an example for logging messages in different logging levels in the Java Standard Logging Library.

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

public class LogLevelExample {

    public static void main(String[] args) {
        // Get a logger for this class
        Logger logger = Logger.getLogger(LogLevelExample.class.getName());

        // Set the log level to FINE (shows all messages from FINE and above)
        logger.setLevel(Level.FINE);

        // Add a console handler to print logs to the console
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.FINE); // Set handler level to match logger level
        logger.addHandler(handler);

        // Log messages at all levels
        logger.severe("This is a SEVERE error message");
        logger.warning("This is a WARNING message");
        logger.info("This is an INFO message");
        logger.config("This is a CONFIG message");
        logger.fine("This is a FINE debug message");
        logger.finer("This is a FINER debug message (more detailed)");
        logger.finest("This is a FINEST debug message (most detailed)");
    }
}

Output:

SEVERE: This is a SEVERE error message
WARNING: This is a WARNING message
INFO: This is an INFO message
CONFIG: This is a CONFIG message
FINE: This is a FINE debug message

The above code only logs the messages that are either FINE or above it as the log level is set to FINE.

Handlers

Loggers allocate objects to a Handler object called LogRecord. This Handler objects Filter and Format the given LogRecord.

Java SE Handler classes are:

  • StreamHandler - It allows us to write formatted records to the OutputStream.
  • FileHandler - The file handler as the name suggests is responsible for writing to one or more rotating log files.
  • ConsoleHandler - It writes to the System.err.
  • SocketHandler - Socket Handler writes log records to the remote TCP ports.
  • MemoryHandler - Memory Handler buffers log records inside memory.

Apart from these built-in classes, developers can also create a Handler class either from scratch or deriving from one of these classes to extend the functionality.

Let us look at an example of a File Handler and Console Handler:

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

public class HandlerLevelExample {

    public static void main(String[] args) {
        // Get a logger for this class
        Logger logger = Logger.getLogger(HandlerLevelExample.class.getName());

        // Set the logger level to INFO (shows INFO and above)
        logger.setLevel(Level.INFO);

        // Console handler set to show only WARNING and above
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.WARNING);
        logger.addHandler(consoleHandler);

        // File handler set to show all messages (from FINEST to SEVERE)
        FileHandler fileHandler = null;
        try {
            fileHandler = new FileHandler("log.txt");
            fileHandler.setLevel(Level.ALL);
        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.addHandler(fileHandler);

        // Log messages at all levels
        logger.severe("This is a SEVERE error message");
        logger.warning("This is a WARNING message");
        logger.info("This is an INFO message");
        logger.config("This is a CONFIG message");
        logger.fine("This is a FINE debug message");
        logger.finer("This is a FINER debug message (more detailed)");
        logger.finest("This is a FINEST debug message (most detailed)");
    }
}

Console:

WARNING: This is a WARNING message
SEVERE: This is a SEVERE error message

log.txt file

FINEST: This is a FINEST debug message (most detailed)
FINER: This is a FINER debug message (more detailed)
FINE: This is a FINE debug message
CONFIG: This is a CONFIG message
INFO: This is an INFO message
WARNING: This is a WARNING message
SEVERE: This is a SEVERE error message

Formatters

Formatters facilitate the formatting of the string given as log messages. The formatMessage() is suggested to be used for local format message fields. It takes a logRecord object as a parameter and returns the formatted logRecord.

The event represented by the logRecord is passed to the suitable formatter by the handler. The formatter then returns formatted output to the handler which can be written to the output devices.

Java SE has two standard Formatter classes:

  • SimpleFormatter - Simple Formatter writes brief summaries of the log records.
  • XMLFormatter - XML formatters write XML structured information.

Let us now try to format out logged messages using the formatter

In the Standard Logging library of Java, we are required to create a class that derives from the Formatter class to define a custom formatter. String Builder is used to format the message.

CustomFormatter.java

package com.example.logging;

import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomFormatter extends Formatter {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public String format(LogRecord record) {
        StringBuilder sb = new StringBuilder();
        sb.append(dateFormat.format(new Date(record.getMillis())))
          .append(" [").append(record.getThreadID()).append("] ")
          .append(record.getLevel()).append(" ")
          .append(record.getLoggerName()).append(" - ")
          .append(formatMessage(record)).append("\n");
        return sb.toString();
    }
}

MyApp.java

package com.example.logging;

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

public class MyApp {
    public static void main(String[] args) {
        // Create a logger object
        Logger logger = Logger.getLogger(MyApp.class.getName());

        // Remove default handlers
        logger.setUseParentHandlers(false);

        // Create and configure console handler
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new CustomFormatter());
        logger.addHandler(consoleHandler);

        // Set the logger level
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        // Use the logger
        logger.fine("This is a fine message");
        logger.info("This is an info message");
        logger.warning("This is a warning message");
        logger.severe("This is a severe message");
    }
}

Output:

2024-07-04 12:34:56 [1] INFO com.example.logging.MyApp - This is an info message
2024-07-04 12:34:56 [1] WARNING com.example.logging.MyApp - This is a warning message
2024-07-04 12:34:56 [1] SEVERE com.example.logging.MyApp - This is an error message

Logging Methods

There are ample logging methods for the developer's convenience to generate log messages. There is a classic logger.log(LogLevel, Log message) format as well as quick log.warning(Log message) methods.

While logging we can pass the class and the method name explicitly along with the log message. This is useful for debugging purposes and tracing the flow of the application. This helps in identifying the exact location where the log message was generated irrespective of how logging is implemented.

void warning(String sourceClass, String sourceMethod, String msg);

The other set of methods is comparatively simpler for developers and only takes the message as an input. These methods attempt to automatically determine its calling class and method using techniques like reflection.

But the problem with this method is modern Virtual machines perform optimizations in the form of just-in-time compiling and may remove complete stack frames which store the necessary information which implies that they may or may not be correct.

void warning(String msg);

Java Logging Control Flow

Java Logging Control Flow consists of the Java Logging Components. The Application requests the Logger, this logger object allocates a LogRecord object which is then passed to the Handler. Both Handler and Logger use the LogLevel to filter out or decide if the LogRecord is of interest or not.

Next, when it needs to publish the LogRecord it calls the Formatter object to localize and format the log message to file, console, streams, etc.

Logging Process in Java
Logging Process in Java

Logging Process in Java

The Logging APIs are designed in a way that they can perform cheap operational checks if some logging level is disabled. The operations like getting messages, formatting, etc. are costly operations and thus are performed only if the log level is the same or higher than the log level. In this way, the logger also tries to minimize the cost if the Logging level is enabled before sending the control to the LogRecord. Localizations and Formatting are expensive operations and thus, Handler avoids them until necessary.

The function would look something like this:

public void publish(LogRecord record) {
	if (!isLoggable(record)) {
		return;
	}
	// Only perform localization and formatting if loggable
	String message = getLocalizedMessage(record);
	String formattedMessage = formatMessage(record);
	// Now write the message to the output
	writeLog(formattedMessage);
}

So initially it performs a cheap check to avoid unnecessary operations.

Using the Standard Java Logging Framework

Why Use a Logging Framework?

Logging is necessary for all applications, and therefore Java came up with JUL(java.util.logging) since Java 1.4 (2002) bundled with JDK. It provides a logger and lets us configure the behavior using Handlers. But with the spanning of use of the application and its features, JUL has shortcomings in the past. JUL is slower, has fewer configuration options, very little documentation, etc.

Benefits of using frameworks:

  • Flexibility - Frameworks like Logback, and Log4j 2, offer extensive configuration options, developers can define their own log levels, format messages along with timestamps, add context information, and log to console, files, network servers, etc.
  • Performance - For high throughput applications, frameworks are optimized for performance.
  • Features - Advanced features are offered by frameworks, such as filtering messages, easy integration, tools for management and analysis, rolling log files(creating new files automatically if the current one reaches the size limit), etc. We can also use cloud services like Signoz Cloud to store large log files.
  • Abstraction - Some Logging Frameworks provide an abstraction layer which makes it easier to integrate more than one library for different features and switch between these logging implementations without modifying application code.
  1. Logback - Logback is written after Log4j, it is a mature and solid logging library.
  2. Log4j 2 - It is also a successor to the Log4j library and is inspired by all the other existing libraries. It has better performance and better API.
  3. SLF4j - SLF4J stands for Simple Logging Facade for Java, which is similar to the ACL ‘bridging’ library, and it provides flexibility to switch between frameworks.
  4. Log4j - This was the first version and was released in 2001. It is still used in many old projects. It gives a fair amount of control but modern libraries have more advanced features.

Adding a Logging Framework to Your Project

Here we are going to discuss adding a logging framework to a Maven Project. Adding Logging frameworks includes downloading the .jar files and adding a dependency in the pom.xml file. Let us take the example of the Log4j 2 library.

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

Framework Configuration

To configure the libraries further we can configure logging frameworks by creating a separate file. For LOG4J we can create a log4j2.xml file in src/main/resources. Let us take an example of configuring Appenders. Appenders can be used to set the destination of the log. The destination can be a console, file, or server network. To set the destination as Console we can use the following code. We can also set the format of the message logged:

<Configuration status="debug" name="signoz" packages="">
  <Appenders>
    <Console name="stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{MM/dd/yyyy} %p %m%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="stdout" />
    </Root>
  </Loggers>
</Configuration>

Using the Standard Java Logging API

Standard Java Logging API or JUL is very easy to import and use in Java applications.

First, we will create a logger object using the getLogger() method.

Logger logger = Logger.getLogger("MyClassName");

To log messages we can use logger.log() or more standard methods such as logger.severe(), logger.fine(), logger.info() etc.

logger.severe("An error occurred!");
logger.info("Application started successfully.");
logger.fine("Processing data for item ID 123.");

Output:

SEVERE: An error occurred!
INFO: Application started successfully.
FINE: Processing data for item ID 123.

While JUL is easy to use, its configuration options are limited compared to dedicated logging frameworks. However, you can configure logging aspects like log levels and destinations using a configuration file (often in XML format). This offers some control over how your logs are handled.

Logging Frameworks in Action

Log4J

Log4J was one of the first logging frameworks, there are more advanced frameworks available today but it lays the foundation for logging.

  1. Dependency

    First of all, we need to add the Log4J library in our dependencies in the pom.xml file in the maven project as follows:

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    

    On the official websites for each of these frameworks, there are lot many versions available and each version contains ample .jar files. We are not required to add all of these to our project. <framework>-api-<x.x.version>.jar and <framework>-core-<x.x.version>.jar most. Rest can be added based on the nature of the project.

    The JAR file can be found on Apache websites and the same provides dependency to be added to properly integrate the framework into the project. Make sure to check the version and the artifactId. As of now, the latest stable version is suggested for use which is 1.2.17 in the above case.

    Other JAR files are used for the specific needs of the projects.

  2. Configuration

    Next, we can configure this framework to achieve various functionality. Let us take a simple example to configure our application.

    <!-- Root configuration element for logging settings -->
    <Configuration status="debug" name="MyApp">
    
      <!-- Define appenders, which specify where the log output will be sent -->
      <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
          <PatternLayout pattern="%d{dd-MMM-yyyy HH:mm:ss} %p %m%n" />
        </Console>
        <!-- File appender for logging to a file -->
        <File name="mylogfile.log" fileName="/path/to/logs/myapp_%d{yyyy-MM-dd_HH-mm-ss}.log" append="true">
          <!-- Pattern layout to define the format of the log messages -->
          <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n" />
        </File>
      </Appenders>
    
      <!-- Define loggers, which determine the logging level and which appenders to use -->
      <Loggers>
        <Root level="info">
          <!-- Reference to the console appender -->
          <AppenderRef ref="stdout" />
          <!-- Reference to the file appender -->
          <AppenderRef ref="file" />
        </Root>
      </Loggers>
    </Configuration>
    
    <!-- Define filters, which control the flow of log events based on specific criteria -->
    <Filters>
      <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY">
        <!-- Apply the filter to a specific logger by name -->
        <Logger name="com.myapp.package" />
      </ThresholdFilter>
    </Filters>
    
    

    In the example above we have configured how our Java application will handle logging messages. The Configuration has two tags status and name which sets the logging status and the name of the logging configuration.

    Next are appenders that let us decide the destination of the logs like console and file in this case.

    Loggers define how logs will be handled from different parts of the application. It defines the root log level, then configures the root logger to use the stdout appender.

    The filter will filter the log messages based on their log level.

  3. Usage

    Here is a simple usecase of the above logging system that we configured and its output:

    import org.apache.log4j.Logger;
    
    public class MyApp {
    
      private static final Logger logger = Logger.getLogger(MyApp.class);
    
      public static void main(String[] args) {
    
        logger.debug("This is a debug message (filtered out).");
    
        logger.info("This is an informational message.");
    
        logger.warn("This is a warning message.");
    
        logger.error("This is an error message.");
    
      }
    
    }
    

    Output:

    28-Jun-2024 16:32:17 INFO  com.myapp.MyApp [main] This is an informational message.
    28-Jun-2024 16:32:17 WARN  com.myapp.MyApp [main] This is a warning message.
    28-Jun-2024 16:32:17 ERROR com.myapp.MyApp [main] This is an error message.
    

Note: The log4j.xml file containing the configuration must be contained in the src/main/resources folder of the Java\Maven Project.

To learn more about logs collection refer to Log Monitoring 101 Detailed Guide

SLF4J

While Log4J2 and Logback are fully-fledged logging frameworks, slf4j or Simple Logging Facade for Java is an abstraction layer meaning that it is a universal plug to integrate logging functionality.

SLF4J acts as an abstraction layer above the Standard Java Logging Library, allowing us to write messages and decouple the underlying frameworks like Log4J or Logback.

SLF4J acts as an abstraction layer above the Standard Java Logging Library, allowing us to write messages and decouple the underlying frameworks like Log4J or Logback. It hides the underlying details of filtering, formatting, etc., and makes it easier to switch between frameworks.

SLF4J is an API, we can flexibly switch between different frameworks without any code change. Additionally, we can separate the implementation from the code making it less dependent on the logging framework. In the future, we can integrate new and advanced frameworks without the overhead of modifying the code.

Logback

The developer that created Log4J created Logback and is meant to be a better version of it. It has many new features that were absent in LOG4J.

Logback Features:

  • Asynchronous Loggers - Logback doesn’t wait for the main thread to log, it rather creates a separate thread and logs when it is free. Thus, it provides better performance.
  • Advanced Filtering - Rather than only filtering using log levels as in Log4j, Logback allows filtering using regex, markers, or custom conditions.
  • Automatic Configuration Reloading - Unlike Log4J it doesn’t require a restart when configuration is changed.
  • Support for multiple APIs - Apart from Log4Js API it can also be integrated with SLF4J API and makes it easier to migrate between different frameworks.

Other features include custom log levels, custom logging format, etc.

  1. Dependency

    Add the Logback dependency to your project's pom.xml file:

    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.5</version>
    </dependency>
    
  2. Configuration

    Create a file named logback.xml in your project's src/main/resources directory with the following content:

    <!-- Logback configuration file for MyApp with logging level set to DEBUG -->
    <configuration status="debug" name="MyApp">
    
      <!-- Console Appender: Logs output to the console -->
      <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          <!-- Pattern for log messages: date, priority, logger name, and message -->
          <pattern>%d{dd-MMM-yyyy HH:mm:ss} %p %logger{36} - %msg%n</pattern>
        </encoder>
      </appender>
    
      <!-- Rolling File Appender: Logs output to a file with rolling policy -->
      <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The log file location and naming pattern -->
        <file>/path/to/logs/myapp_%d{yyyy-MM-dd}.log</file>
        <!-- Rolling policy: rotates log files based on date -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>/path/to/logs/archived/myapp_%d{yyyy-MM-dd}.log.gz</fileNamePattern>
        </rollingPolicy>
        <encoder>
          <pattern>%d{dd-MMM-yyyy HH:mm:ss} %p %logger{36} - %msg%n</pattern>
        </encoder>
      </appender>
    
      <!-- Logger for a specific package with DEBUG level -->
      <logger name="com.myapp.package" level="debug">
        <!-- Do not log messages to parent loggers -->
        <additivity>false</additivity>
        <!-- Reference to async appender -->
        <appender-ref ref="async" />
      </logger>
    
      <logger name="root" level="info">
        <appender-ref ref="stdout" />
        <!-- Reference to rolling file appender --
        <appender-ref ref="rollingFile" />
      </logger>
    
      <!-- Asynchronous appender to improve performance -->
      <async name="async" bufferSize="512">
        <!-- Maximum size of the logging queue -->
        <queueSize>1024</queueSize>
        <!-- Whether to discard logs on overflow -->
        <discardingOnOverflow>false</discardingOnOverflow>
        <!-- Reference to rolling file appender -->
        <appender-ref ref="rollingFile" />
      </async>
    
    </configuration>
    

    In the configuration file, we have used Appenders similar to the previous example to append to the console and file. We are using the rollingFile appender, what it does is rotate logs daily and compress them with Gzip.

    In the loggers section the level of the root logger is set to info and a new logger is introduced by the name com.myapp.package which follows the Java naming convention.

    Additionally, we have added an AsyncAppender. This will log the messages asynchronously which improves the performance. Rather than appending a log at the time it is encountered affecting the performance of the main thread, asynchronous logging adds the logs to the queue and a separate thread is used to execute the logs.

  3. Usage

    The example of the Logback is the same as Log4J but the code will differ in imports:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class MyApp {
    
      private static final Logger logger = LoggerFactory.getLogger(MyApp.class);
    
      public static void main(String[] args) {
    
        logger.debug("This is a debug message.");
    
        logger.info("This is an informational message.");
    
        logger.warn("This is a warning message.");
    
        logger.error("This is an error message.");
    
      }
    }
    

Output:

DEBUG: This is a debug message.
INFO: This is an informational message.
WARN: This is a warning message.
ERROR: This is an error message.

Note: Logback requires SLF4J.

Log4J 2

  1. Dependency

    Dependency for the Log4J can be added as below. Here rather than complement implementation we are taking API and log4j-core. This library provides the core functionalities used by Log4j2 for logging operations such as appenders, filters, loggers, layouts, etc.

    <dependency>
    	<groupId>org.apache.logging.log4j</groupId>
    	<artifactId>log4j-api</artifactId>
    	<version>2.x.x</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.logging.log4j</groupId>
    	<artifactId>log4j-core</artifactId>
    	<version>2.x.x</version>
    </dependency>
    

A similar configuration can also be written for the Log4J2 to get the desired results.

Comparison of Java Logging Frameworks

FeatureJUL (java.util.logging)SLF4J (Simple Logging Facade for Java)LogbackLog4j 2
TypeBuilt-in JDK libraryFacade, not a frameworkThird-party frameworkThird-party framework
FunctionalityBasic loggingAbstraction layer for other frameworksFeature-rich, built on top of SLF4JFeature-rich, successor to Log4j
ConfigurationLimited (properties files)None (uses underlying framework)Flexible (XML, JSON, YAML)Flexible (XML, JSON, YAML)
PerformanceModerateN/A (depends on underlying framework)FastFastest
FlexibilityLimitedHigh (works with various frameworks)HighHigh
Community SupportGood (part of JDK)StrongStrongActive
Learning CurveEasiestEasy (uses familiar concepts)ModerateModerate

Best practices for Logging

  1. Utilize a Dedicated Logging Framework:

Though the Java Standard library provides basic logging capabilities (JUL), dedicated frameworks offer significant advantages.

  • Enhanced Configurability: This lets us customize the message format, set the destination for the logs, define custom logging levels, etc.
  • Improved Performance: It provides optimization and efficiency.
  • Advanced Features: Offers advanced logging features to ease the logging process such as filtering, integration, etc.
  1. Log Messages must be Clear and Concise: Logs must contain necessary information but at the same time must be short and to the point. Considering the length of the logs in an application it should be easy to locate and analyze.

  2. Leverage Logging Levels:

    Different Logging levels offer different numbers of logging levels, the common ones are debug, info, error, and warning. Utilize these strategically, to categorize information and improve manageability.

  3. Prioritize Security

    Log files should contain necessary information but should not leak important or sensitive data. Consider masking sensitive data or employing secure logging configurations.

  4. Monitor Logs

    Examination of the logs can help detect errors in the early stages. Logs can also serve to track down issues if something suspicious is detected. Logs can be monitored using open-source external tools available like Signoz.

By adhering to these best practices, proper monitoring systems for Java applications can be made robust and enhance overall maintainability.

The actual process of logging consists of collecting, analyzing, and managing logs generated by computers, applications, and networks. This can be done with dedicated paid or open-source software like SigNoz. Refer to the article Collecting Application Logs Using OTEL Java Agent to collect application logs.

Conclusion

  • Jogging is essential for any application development, debugging, and maintenance. It lets developers have insight into application behavior, flow, performance, and issues.
  • Java provides a standard logging library also known as JUL which offers basic logging functionalities.
  • For logging, we are required to create a logger object using the loggerFactory. This logger then can be used to log messages with the log level. If the log level is higher or equal to the message’s level, then it creates a logRecord object.
  • This logRecord object is then passed to Handler which filters and formats the message and sends it to the respective destination like console, file, or network.
  • Though JUL provides various functionalities, for advanced needs, dedicated logging frameworks like Log4j 2, SLF4J, Logback, etc are used due to extensive configureability, performance optimizations, and other helpful features.

FAQs:

What is a log for Java?

A Java log is like a personal diary for your program. It keeps track of important events, errors, and general activity, helping you understand how it's functioning.

Where can I find the Java log?

By default, Java logs might go to the console (your terminal window) or a file depending on the program's setup. Some Java applications use separate log files which can be located inside the programs directory.

How to create a log in Java?

Java provides a standard logging library also known as JUL which can be used to log. We can import java.util.log and create a logger object. This logger object can be used to log messages. We can also use external libraries that provide additional functionalities.

How to print logs in Java?

We can either use the log() method or more specific methods like info(), debug(), and error() present in the standard Java Log library to write different types of messages to the log.

How to enable Java logs?

To enable logging in Java, we can set the log level to INFO, DEBUG, or WARNING. The logs can be found in the console or a log file in the projects folder.

How to keep logs in Java?

We can keep logs by saving them in a file. Logging libraries provide methods for adding file handlers and setting formatters to the logger object.

How to check the log level in Java?

The logger.getLevel() method can be used to get the current log level. It returns a Level object.

How to format logs in Java?

You can customize how log messages appear, including timestamps, severity levels, and even adding context-like variable values.

How to log a value in Java?

To log values of variables in Java, we can pass an object array or use String.format() with the log message as follows:

logger.log(Level.WARNING, "logging: {0} {1} {2}", new Object[] { "p1", "p2", "p3" });
logger.log(Level.WARNING, String.format("logging: %s %s %s %s", "p1", "p2", "p3", "additional string"));

What is a log level in Java?

Log levels are classifications used to determine the severity of the log message. It helps developers and administrators to filter the logs and quickly respond to the urgent ones.

What is an access log in Java?

Access logs track web server activity, not directly related to Java application code. They might reside in the web server's configuration directory.