민서네집

log 내용을 DB에 저장하기 본문

Java

log 내용을 DB에 저장하기

브라이언7 2014. 12. 17. 17:48

log 내용을 DB의 테이블에 저장하기 위해서는 log4j에서 제공해주는 JDBCAppender 를 사용하면 되는데, 그대로 사용하면 안되고, JDBCAppender 클래스를 상속해서 (') 문자를 치환해줘야지 된다고 한다.


http://stackoverflow.com/questions/6734044/log4j-jdbcappender-to-log-stacktraces


< How to create logs in database using JDBCAppender in log4j >


http://howtodoinjava.com/2013/04/08/how-to-create-logs-in-database-using-jdbcappender-in-log4j/


아니면 non official 한 Log4j JDBCAppender 를 사용하라고 되어 있었다.


http://stackoverflow.com/questions/2042070/how-to-sanitize-log-messages-in-log4j-to-save-them-in-database


http://stackoverflow.com/questions/6734044/log4j-jdbcappender-to-log-stacktraces


< Log4j JDBCAppender 홈페이지 >


http://www.dankomannhaupt.de/projects/index.html


< JDBCAppender >


http://logging.apache.org/log4j/2.x/manual/appenders.html#JDBCAppender


< log4j 를 이용하여 DB에 로그 쌓기 >


http://gnujava.com:8000/board/article_view.jsp?&article_no=3612&menu_cd=18&board_no=5&table_cd=EPAR01&table_no=01


< log4j.properties 설정 >

log4j.rootLogger = WARN, DB, myapp ${optional.appender}

log4j.appender.stdout           = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout    = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d %5p {%t} [%C.%M()] (%F:%L) - %m%n
log4j.appender.stdout.Encoding = UTF-8

log4j.appender.DB=com.myapp.home.config.MyJdbcAppender
log4j.appender.DB.BufferSize = 1
log4j.appender.DB.URL=jdbc:mysql://localhost:3306/myapp
log4j.appender.DB.driver=com.mysql.jdbc.Driver
log4j.appender.DB.user=username
log4j.appender.DB.password=password
log4j.appender.DB.layout=org.apache.log4j.PatternLayout
log4j.appender.DB.sql=INSERT INTO ERROR_LOG(CONTEXT_PATH, ERROR_TIME, LEVEL, THREAD, CATEGORY, CLASS, FILE, MESSAGE) VALUES('/${context.root}','%d{ISO8601}','%p','%t','%c','%C.%M()','%F:%L', '%m%n')
log4j.appender.DB.Encoding=UTF-8
log4j.appender.DB.ignoreExceptions=false

log4j.appender.myapp = org.apache.log4j.DailyRollingFileAppender
log4j.appender.myapp.file = ${TOMCAT_LOG_PATH}/myapp_${context.root}.log
log4j.appender.myapp.datePattern = '.'yyyy-MM-dd
log4j.appender.myapp.layout = org.apache.log4j.PatternLayout
log4j.appender.myapp.layout.ConversionPattern=%d %5p {%t} [%C.%M()] (%F:%L) - %m%n
log4j.appender.myapp.Encoding=UTF-8

log4j.logger.org.thymeleaf=WARN
log4j.logger.org.thymeleaf.TemplateEngine.CONFIG=WARN
log4j.logger.org.thymeleaf.TemplateEngine.TIMER=WARN
log4j.logger.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=WARN
log4j.logger.org.thymeleaf.TemplateEngine.cache.FRAGMENT_CACHE=WARN
log4j.logger.org.thymeleaf.TemplateEngine.cache.MESSAGE_CACHE=WARN
log4j.logger.org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE=WARN
log4j.logger.thymeleafexamples=DEBUG

log4j.logger.jdbc.audit=OFF
log4j.logger.jdbc.sqlonly=OFF


com.myapp.home.config.MyJdbcAppender 클래스

package com.myapp.home.config;

import org.apache.log4j.LogManager;
import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.LoggingEvent;

public class MyJdbcAppender extends JDBCAppender {

	@Override
	protected String getLogStatement(LoggingEvent event) {

		Object eventMsgObj = event.getMessage();
		String eventMessage = "";

		if( eventMsgObj != null && eventMsgObj.toString() != null ) {
			// DB에 입력하기 위해서는 (') 를 ('')로 치환해야지 에러없이 제대로 입력된다.
			eventMessage = eventMsgObj.toString().replaceAll("'", "''").replaceAll("(?<!\r)\n", "\r\n");
		}

		if (null != event.getThrowableInformation() ) {

			// DB에 저장할 때는 "\r\n" 이 아닌 "\n"만 "\r\n"으로 바꾼다.
			// 부정형 후방탐색 정규표현식 이용 => replaceAll("(?<!\r)\n", "\r\n")
			Throwable throwable = event.getThrowableInformation().getThrowable();

			String message = "";
			if( throwable != null ) {
				message = throwable.getMessage();
				if( message != null ) {
					message = message.replaceAll("'", "''").replaceAll("(?<!\r)\n", "\r\n");
				}
			}

			Exception exception = new Exception(message, throwable);

			exception.setStackTrace(throwable.getStackTrace());

			LoggingEvent clone = new LoggingEvent(event.fqnOfCategoryClass,
					LogManager.getLogger(event.getLoggerName()), event.getLevel(), eventMessage, exception);

			return getLayout().format(clone);
		} else {
			LoggingEvent clone = new LoggingEvent(event.fqnOfCategoryClass,
					LogManager.getLogger(event.getLoggerName()), event.getLevel(), eventMessage, null);

			return getLayout().format(clone);
		}
	}
}



Stack trace를 DB에 넣기 위해 EnhancedPatternLayout 사용하기


[참조] http://stackoverflow.com/questions/6734044/log4j-jdbcappender-to-log-stacktraces


위 웹사이트의 내용과 달라서 주의할 점은 


log4j.properties 파일에서 conversionPattern 앞에 layout. 을 붙여야 하고, pom.xml 파일에서


<dependency>

<groupId>log4j</groupId>

<artifactId>apache-log4j-extras</artifactId>

<version>1.2.17</version>

</dependency>


를 넣어줄 필요가 없다는 것이다.


( 나는 log4j 1.2.17 버전을 사용했음. )


log4j.appender.DB=com.myapp.home.config.MyJdbcAppender
log4j.appender.DB.BufferSize = 1
log4j.appender.DB.URL=jdbc:mysql://localhost:3306/myapp
log4j.appender.DB.driver=com.mysql.jdbc.Driver
log4j.appender.DB.user=username
log4j.appender.DB.password=password
log4j.appender.DB.layout=org.apache.log4j.EnhancedPatternLayout
log4j.appender.DB.layout.conversionPattern=INSERT INTO ERROR_LOG(CONTEXT_PATH, ERROR_TIME, LEVEL, THREAD, CATEGORY, CLASS, FILE, MESSAGE) VALUES('/${context.root}','%d{ISO8601}','%p','%t','%c','%C.%M()','%F:%L', '%m %throwable %n')
log4j.appender.DB.Encoding=UTF-8
log4j.appender.DB.ignoreExceptions=false


그런데 이렇게 하면 HeidiSQL로 보면 Stack Trace가 줄바꿈이 안되서 보이기 때문에 그냥 예전처럼 PatternLayout 을 사용하고, MyJdbcAppender 클래스의 getLogStatement() 메서드에서 Stack Trace를 message String 에 추가해서 넣어서 해결하려고 함.


HeidiSQL 에서 줄바꿈이 안되서 보이는 원인을 알았다.

Java 에서는 줄바꿈 문자로 \n 를 사용하는데, log4j.properties 파일에서의 %n는 운영체제에 맞는 줄바꿈 문자이다.

Windows 에서는 줄바꿈 문자가 \r\n 따라서 2개가 섞여 있어서 제대로 줄바꿈이 안보였던 것이다.


log4j.properties 파일에서 %n 을 없애고, MyJdbcAppender 파일에서 \n 를 \r\n 으로 replace 해주는 코드를 없애버림.

이렇게 하면 줄바꿈 문자가 \n 로 통일되기 때문에 HeidiSQL에서 줄바꿈 되서 제대로 보인다.


나의 최종 log4j.properties 파일과 MyJdbcAppender 파일은 다음과 같다.


log4j.appender.DB=com.myapp.home.config.MyJdbcAppender log4j.appender.DB.BufferSize = 1 log4j.appender.DB.URL=jdbc:mysql://localhost:3306/myapp log4j.appender.DB.driver=com.mysql.jdbc.Driver log4j.appender.DB.user=username log4j.appender.DB.password=password log4j.appender.DB.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.DB.layout.conversionPattern=INSERT INTO ERROR_LOG(CONTEXT_PATH, ERROR_TIME, LEVEL, THREAD, CATEGORY, CLASS, FILE, MESSAGE, STACK_TRACE) VALUES(left('/${context.root}',20),left('%d{ISO8601}',30),left('%p',5),left('%t',50),left('%c',100),left('%C.%M()',100),left('%F:%L',50), left('%m',1000), left('%throwable',20000)) log4j.appender.DB.Encoding=UTF-8 log4j.appender.DB.ignoreExceptions=false



package com.myapp.home.config;

import java.lang.reflect.Field;

import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.LoggingEvent;

public class MyJdbcAppender extends JDBCAppender {

	@Override
	protected String getLogStatement(LoggingEvent event) {

		Object eventMsgObj = event.getMessage();

		if( eventMsgObj != null && eventMsgObj.toString() != null ) {
			if( eventMsgObj.toString().contains("'") ) {
				replace(event, "message");
				replace(event, "renderedMessage");
			}
		}

		if (null != event.getThrowableInformation() ) {

			Throwable throwable = event.getThrowableInformation().getThrowable();

			replace(new Throwable().getClass(), throwable, "detailMessage");

			replace(throwable, "message");

			// stack trace를 출력할 때는 rep 멤버변수의 문자열이 출력되서 rep도 '를 ''으로 치환해야 한다.
			replace(event.getThrowableInformation(), "rep");
		}

		// 이 문자열이 올바른 SQL문이 되는지 console로 출력해서 확인해 본다.
		String format = getLayout().format(event);

		//System.out.println("##############################");
		//System.out.println(format);

		return format;
	}

	private void replace(Object object, String field) {
		replace(object.getClass(), object, field);
	}

	// DB에 저장하기 위해서는 (') 를 ('')로 치환해야지 에러없이 제대로 입력된다.
	private void replace(Class<?> clazz, Object object, String field) {
		try {
			Field f = clazz.getDeclaredField(field);
			f.setAccessible(true);
			if( f.get(object) != null ) {
				if( f.get(object) instanceof String[] ) {
					String[] stringArr = (String[]) f.get(object);
					for (int i = 0; i < stringArr.length; i++) {
						stringArr[i] = stringArr[i].replaceAll("'", "''");
					}
				} else {
					String value =  f.get(object).toString();
					if( value.contains("'") ) {
						f.set(object, value.replaceAll("'", "''"));
					}
				}
			}
		} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
			//e.printStackTrace();
		}
	}
}


Comments