Logging

Logs are texts for tracking problems that occur during program development or operation, or for monitoring operational status. The easiest way to leave a log is to use the System.out.println(); A better way to do this is to create a class that records the log.

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 are the result of efforts to automate common tasks and to allow developers to develop applications quickly. First, I will explain Log4j 2. To use Log4j 2, 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 from the directory where you extracted the file and paste them into the path that the class loader can find. The following property file is created 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 in the Java Project 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 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 {
	//private Logger log = LogManager.getLogger(LogTest2.class); //Logger instance named "LogTest2". Only the ROOT Logger works.
	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 console 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. Appenders
  3. Layout : Determine the log format. Layouts

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

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

Log Level

The log levels are organized hierarchically 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 very severe error events that will 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 progress of the application at 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 common logging API that prevents applications from being dependent 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, you will find the commons-logging-1.2.jar file.
Copy this file to the classpath.
Commons logging is a 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. The log4j-jcl-2.8.jar file can be found 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(long 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.setKind(DEPOSIT);
		transaction.setBalance(balance);
		transactions.add(transaction);
		logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
			this.accountNo, amount, Account.DEPOSIT, this.kind);
	}
	
	//..Omit code..

}
NormalAccount.java
@Override
public synchronized void withdraw(long 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.setKind(Account.WITHDRAW);
	transaction.setBalance(balance);
	transactions.add(transaction);
	logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
		this.getAccountNo(), amount, Account.WITHDRAW, this.getKind());
}
MinusAccount.java
@Override
public synchronized void withdraw(long 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.setKind(Account.WITHDRAW);
	transaction.setBalance(balance);
	transactions.add(transaction);
	logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", 
		this.getAccountNo(), amount, Account.WITHDRAW, this.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

Logs are written to 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>

Run LogTest5.java again.
Confirm that logs are accumulated in the javabank.log in the project root directory.

References