Logging

Logs are texts for tracking problems during program development or operation or monitoring operational status. The easiest way for logging is to use the System.out.println(). A better way is to create a class dedicated to logging.

Log.java
package net.java_school.util;

import java.io.*;
import java.util.Date;

public class Log {
  String logFile = "C:/debug.log";
  FileWriter fw;
  static final String ENTER = System.getProperty("line.separator");
  
  public Log() {
    try {
      fw = new FileWriter(logFile, true);
    } catch (IOException e){}
  }

  public void close() {
    try {
      fw.close();
    } catch (IOException e){}
  }

  public void debug(String msg) {
    try {
      fw.write(new Date()+ " : ");
      fw.write(msg + ENTER);
      fw.flush();
    } catch (IOException e) {
      System.err.println("IOException!");
    }
  }
}

Create the following file for testing.

LogTest1.java
package net.java_school.logtest;

import net.java_school.util.Log;

public class LogTest1 {

  public void someMethod() {
    Log log = new Log(); //Create an output stream.
    log.debug("Log Test!"); //Logging
    log.close(); //Closes the output stream.
  }
  
  public static void main(String[] args) {
    LogTest1 test = new LogTest1();
    test.someMethod();
  }
}

Log4j 2

There are frameworks dedicated to logging. Frameworks result from efforts to automate common tasks and allow developers to develop applications quickly.

Download the Log4j binary file from the address below:
http://logging.apache.org/log4j/2.x/download.html

Copy log4j-api-2.x.x.jar and log4j-core-2.x.x.jar and paste them into the path that the class loader can find.

Create the following property file in the path that the class loader can find.

log42.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <File name="A1" fileName="A1.log" append="true">
      <PatternLayout pattern="%t %-5p %c{2} - %m%n" />
    </File>
    <Console name="STDOUT" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
    </Console>
  </Appenders>
  <Loggers>
    <Logger name="net.java_school" level="debug">
      <AppenderRef ref="A1" />
    </Logger>
    <Root level="debug">
      <AppenderRef ref="STDOUT" />
    </Root>
  </Loggers>
</Configuration>

Testing Log4j 2 in Eclipse

Follow these steps.

  1. Create a new project from Java Perspective.
  2. Add the Log4j 2 library to the project's Build Path.
    1. In the Package Explorer view, select the project and right-click.
    2. Select Build Path and then select Configure Build Path.
    3. Select the Libraires tab and then select Add External JARs.
    4. Add log4j-api-2.x.x.jar and log4j-core-2.x.x.jar and click the OK button.
  3. Create the file LogTest2.java as shown below.
LogTest2.java
package net.java_school.logtest;

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

public class LogTest2 {
  /*
  //Logger instance named "LogTest2". Only the ROOT Logger works.
  private Logger log = LogManager.getLogger(LogTest2.class); 
  */
  private Logger log = LogManager.getLogger("net.java_school");

  public void xxx() {
    if (log.isDebugEnabled()) {
      log.debug("debug message");
    }
  }

  public void yyy() {
    if (log.isInfoEnabled()) {
      log.info("info message");
    }
  }

  public static void main(String[] args) {
    LogTest2 test = new LogTest2();
    test.xxx();
    test.yyy();
  }
}

After running, check the log messages in the console view and A1.log file. Notice that no code closes the output stream.

A description of the main elements of the Log4j2 configuration file

Log4j 2 consists of three main components: Logger, Appender, and Layout, which work together to log.

  1. Logger: Pass the logging message to Appender.
  2. Appender: Specifies the output destination of the logging messages. see Appenders
  3. Layout: Determine the log format. see Layouts

The Root Logger is the top logger. It always exists and is at the top of all loggers.

For more information, see http://logging.apache.org/log4j/2.x/manual/configuration.html.

Log Level

The log levels are hierarchical as follows.
TRACE > DEBUG > INFO > WARN > ERROR > FATAL
If set to INFO, INFO, WARN, ERROR, and FATAL are recorded.

  1. FATAL: The FATAL level designates severe error events that presumably lead the application to abort.
  2. ERROR: The ERROR level designates error events that might still allow the application to continue running.
  3. WARN: The WARN level designates potentially harmful situations.
  4. INFO: The INFO level designates informational messages that highlight the application's progress at a coarse-grained level.
  5. DEBUG: The DEBUG Level designates fine-grained informational events that are most useful to debug an application.
  6. TRACE: The TRACE Level designates finer-grained informational events than the DEBUG

Commons Logging with Log4j 2

Apache's commons-logging is a framework built to provide developers with a standard logging API that prevents applications from depending on a particular logging framework. Currently, many third-party logging frameworks are implemented based on commons-logging.

Download the commons-logging binary from:
http://apache.mirror.cdnetworks.com/commons/logging/binaries/

After unpacking, copy the commons-logging-1.2.jar file to the classpath.

Commons logging is standardized logging.
Logging using commons-logging requires a separate logging implementation.

Let's use Log4j 2 as the logging implementation.

Create the following commons-logging properties file in the classpath.

commons-logging.properties
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Log4JLogger

Create the following test file.

LogTest3.java
package net.java_school.logtest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogTest3 {
  private Log log = LogFactory.getLog(LogTest3.class);
  //private Log log = LogFactory.getLog(this.getClass());
	
  public void xxx() {
    if (log.isDebugEnabled()) {
      log.debug("debug message");
    }
  }
	
  public void yyy() {
    if (log.isInfoEnabled()) {
      log.info("info message");
    }
  }
  
  public static void main(String[] args) {
    LogTest3 test = new LogTest3();
    test.xxx();
    test.yyy();
  }
}

Add commons-logging-1.2.jar and log4j-jcl-2.8.jar to your classpath. --You can find the log4j-jcl-2.8.jar file in the Log4j 2 directory--

slf4j

Download the latest distribution from the following address:
http://www.slf4j.org/download.html

After unpacking, add the slf4j-api-1.7.25.jar and slf4j-simple-1.7.25.jar files to the classpath.

Create and test the following example.

package net.java_school.bank;

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

public class LogTest5 {
	
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(LogTest5.class);
    int amount = 1000;
    int balance = 10000;
    logger.info("amount: {}, balance: {}", amount, balance);
  }
}

The default level for slf4j is INFO.
To change the logging level to DEBUG, make the simplelogger.properties file in the classpath as follows:

simplelogger.properties
org.slf4j.simpleLogger.defaultLogLevel=DEBUG

Applying slf4j to a Javabank example

Add the slf4j-api-1.7.25.jar and slf4j-simple-1.7.25.jar files to the Javabank classpath.
Apply logging to the deposit and withdrawal methods of the Account, NormalAccount, and MinusAccount classes.

Account.java
package net.java_school.bank;

//..Omit code..

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

public abstract class Account implements Serializable {
  Logger logger = LoggerFactory.getLogger(Account.class);
	
  //..Omit code..
	
  public synchronized void deposit(double amount) {
    balance = balance + amount;
    Transaction transaction = new Transaction();
    Calendar cal = Calendar.getInstance();
    Date date = cal.getTime();
    transaction.setTransactionDate(Account.DATE_FORMAT.format(date));
    transaction.setTransactionTime(Account.TIME_FORMAT.format(date));
    transaction.setAmount(amount);
    transaction.setBalance(balance);
    transaction.setKind(DEPOSIT);
    transactions.add(transaction);
    logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
      accountNo, amount, DEPOSIT, getKind());
  }
	
  //..Omit code..
}
NormalAccount.java
@Override
public synchronized void withdraw(double amount) {
  if (amount > balance) {
    throw new InsufficientBalanceException("There is not enough balance.");
  }
  balance = balance + amount;
  Transaction transaction = new Transaction();
  Calendar cal = Calendar.getInstance();
  Date date = cal.getTime();
  transaction.setTransactionDate(Account.DATE_FORMAT.format(date));
  transaction.setTransactionTime(Account.TIME_FORMAT.format(date));
  transaction.setAmount(amount);
  transaction.setBalance(balance);
  transaction.setKind(Account.WITHDRAW);
  transactions.add(transaction);
  logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
    getAccountNo(), amount, Account.WITHDRAW, getKind());
}
MinusAccount.java
@Override
public synchronized void withdraw(double amount) {
  balance = balance - amount;
  Transaction transaction = new Transaction();
  Calendar cal = Calendar.getInstance();
	
  Date date = cal.getTime();
  transaction.setTransactionDate(Account.DATE_FORMAT.format(date));
  transaction.setTransactionTime(Account.TIME_FORMAT.format(date));
  transaction.setAmount(amount);
  transaction.setBalance(balance);
  transaction.setKind(Account.WITHDRAW);
  transactions.add(transaction);
  logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
    getAccountNo(), amount, Account.WITHDRAW, getKind());
}

If you want to output log messages to a file rather than the console, add the following setting to simplelogger.properties:

simplelogger.properties
org.slf4j.simpleLogger.logFile=C:/java/Bank/javabank.log
org.slf4j.simpleLogger.defaultLogLevel=DEBUG

You can see the log messages in the javabank.log file. However, SimpleLogger does not accumulate log messages in the log file.

logback

Download the latest version from the address below:
http://logback.qos.ch/download.html

Add logback-core-x.x.x.jar, logback-classic-x.x.x.jar, and logback-access-x.x.x.jar to the classpath.
Remove slf4-simple-x.x.x.jar from the classpath.

Create a logback.xml file in the classpath.

logback.xml
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>javabank.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
      ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

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

Rerun LogTest5.
Check if the log has accumulated in the javabank.log in the project root directory.

References