[Framework] TIL 072 - 23.11.01

유진·2023년 10월 31일
0

07_Framework

LOGGING

  • 로그(log)란? 기록하는 것

원래: System.out.println(userId);
== 효율이 좋은 편은 아님

  • log4j == 옛날에 사용 (문법적으로 쉬움)

  • slf4j == 요즘 사용

  • logback 위 두개를 서로 호환해줌



log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders : 끝에 붙이겠다는 의미 -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%p] %m%n" /> <!-- 로깅시간 및 로그 레벨, 메세지 나타내는 것 -->
		</layout>
	</appender>

	
	<!-- //날짜별 로그 파일 생성 하기 -->
	<appender name="dailyRollingFile" class="org.apache.log4j.DailyRollingFileAppender"> <!-- 날마다 로그 파일을 하나씩 만드는 것 -->
	    <param name="File" value="/logs/runtime.log" /> <!-- 파일 만들지 않아도 logs 파일 자동 생성해줌 -->
	    <param name="Append" value="true" />
	    <param name="encoding" value="UTF-8" />
	    <param name="DatePattern" value="'.'yyyy-MM-dd" />
	    <layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%p] %m%n" />
		</layout>
	</appender>
	
	
	<!-- log level 
		trace -> debug -> info -> warn -> error -> fatal
	
		- trace : 추적
		- debug : 어느 시점에 무슨 값이 들어있는지 찍기 위한 용도
		- info : ex.어떤 시점에 어떤 controller가 잘 돌고 있는지
		- warn : 경고: 추후에 문제 생길 수 있음
		- error : 정상 작동 X > 우리가 다룰 수 있는 범위
		- fatal : 치명적인, 심각한 ERROR, 프로그램적으로 할 수 없는 것
		
	-->
	
	
	<!-- Application Loggers -->
	<logger name="edu.kh.project">
		<level value="debug" />
		<appender-ref ref="dailyRollingFile"/>
	</logger>
	
	<!-- 3rdparty Loggers : 부가적인 log들 -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="debug" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

MemberServiceImpl.java

package edu.kh.project.member.model.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.kh.project.member.model.dao.MemberDAO;
import edu.kh.project.member.model.dto.Member;

@Service // 비즈니스 로직(데이터 가공, DAO 호출, 트랜잭션 제어)처리하는 클래스 명시
		// + Bean으로 등록하는 어노테이션
public class MemberServiceImpl implements MemberService{
	
	// org.slf4j.Logger : 로그를 작성할 수 있는 객체
	// org.slf4j.LoggerFactory                        // 현재 클래스명 넣어주어야 함! (현재 클래스에 logger 사용하겠다)
	private Logger logger = LoggerFactory.getLogger(MemberServiceImpl.class);
	
	// @Autowired : 작성된 필드와
	// Bean으로 등록된 객체 중 타입이 일치하는 Bean을
	// 해당 필드에 자동 주입(Injection) 하는 어노테이션.
	// == DI(Dependency Injection, 의존성 주입)
	// -> 객체를 직접 만들지 않고, Spring이 만든걸 주입함(Spring에 의존)
	
	@Autowired
	private MemberDAO dao;
	
	@Autowired // bean으로 등록된 객체 중 타입이 일치하는 객체를 DI(의존성 주입)
	private BCryptPasswordEncoder bcrypt;
	
	// 암호화가 필요한 곳? 로그인, 회원가입
	
	@Override
	public Member login(Member inputMember) {
		
		// 로그 출력
		logger.info("MemberServiceImpl.login() 실행"); // 정보 출력
		logger.debug("memberEmail : " + inputMember.getMemberEmail() );
		logger.warn("이건 경고 용도");
		logger.error("이건 오류 발생 시");
		
		
		// 암호화가 다 다른지 test 용도
//		System.out.println("암호화 확인 1:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 2:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 3:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 4:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
//		System.out.println("암호화 확인 5:"  +  bcrypt.encode(  inputMember.getMemberPw()  )  );
		
		
		// dao 메서드 호출
		Member loginMember = dao.login(inputMember);
		
		// 입력받은 비밀번호(평문) --비교-- DB에서 조회한 비밀번호(암호화)
		
		if(loginMember != null) { // 아이디가 일치하는 회원이 조회된 경우
			
			// 입력한 pw, 암호화된 pw 같은지 확인
			
			// 같을 경우            // 로그인 시 입력             // DB에서 조회한 것
			if(bcrypt.matches(inputMember.getMemberPw(), loginMember.getMemberPw())) {
				
				// 비밀번호를 유지하지 않기 위해서 로그인 정보에서 제거
				loginMember.setMemberPw(null);
				
			} else { // 다를 경우(비밀번호 잘못쳤을 경우)
				loginMember = null;
			}
			
		} 
		
		return loginMember;
	}

	
	// 회원 가입 서비스
	@Transactional
	@Override
	public int signUp(Member inputMember) {
		
		// 비밀번호 암호화 (Bcrypt) 후 다시 inputMember 세팅
		String encPw = bcrypt.encode(inputMember.getMemberPw());
		inputMember.setMemberPw(encPw);
		
		return dao.signUp(inputMember);
	}
	
	
}


-> 바꾼 패턴대로 콘솔창에 출력됨


-> 서버 실행 후 로그인 시, 해당 내용 출력됨



-> 파일도 함께 생성됨


Spring AOP

pom.xml

servlet-context.xml

BeforeAspect.java

package edu.kh.project.common.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
			// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
	
	private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
	
		// execution([접근제한자] 리턴타입  클래스명  메소드명  ([파라미터]))
	@Before("execution(* edu.kh.project..*Impl*.*(..))")
	public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
		// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
		//		객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
		
		// 대상 객체의 간단한 클래스명(패키지명 제외)
		String className = jp.getTarget().getClass().getSimpleName();
		
		// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
		String methodName = jp.getSignature().getName();
		
		String str = "-----------------------------------------------\n";
		
		str += "[Start] : " + className + " - " + methodName + "()\n";
		// [Start] : MemberServiceImpl - login()
		
		// jp.getArgs() : 파라미터 묶음(배열)
		str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
		
		logger.info(str);
	}
	
}

== 위 메소드와 패턴이 일치할 경우, console 창에 찍힘


CF ) AOP 변수 선언도 가능!

CommonPointcut.java

package edu.kh.project.common.aop;

import org.aspectj.lang.annotation.Pointcut;

// Pointcut을 모아둘 클래스
public class CommonPointcut {

	@Pointcut("execution(* edu.kh.project..*Impl*.*(..))")
	public void serviceImplPointcut() {}
}

BeforeAspect.java

package edu.kh.project.common.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
			// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
	
	private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
	
		// execution([접근제한자] 리턴타입  클래스명  메소드명  ([파라미터]))
	//@Before("execution(* edu.kh.project..*Impl*.*(..))")
	@Before("CommonPointcut.serviceImplPointcut()")
	public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
		// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
		//		객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
		
		// 대상 객체의 간단한 클래스명(패키지명 제외)
		String className = jp.getTarget().getClass().getSimpleName();
		
		// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
		String methodName = jp.getSignature().getName();
		
		String str = "-----------------------------------------------\n";
		
		str += "[Start] : " + className + " - " + methodName + "()\n";
		// [Start] : MemberServiceImpl - login()
		
		// jp.getArgs() : 파라미터 묶음(배열)
		str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
		
		logger.info(str);
	}
	
}


== 동일하게 정상 작동됨


BeforeAspect.java

package edu.kh.project.common.aop;

import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import edu.kh.project.member.model.dto.Member;

@Component
@Aspect // 공통 관심사(코드에서 똑같이 수행해야 되는 기능)가 작성된 클래스임을 지정
			// Pointcut(타겟지정) + Advice(수행할 코드)
public class BeforeAspect {
	
	private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
	
		// execution([접근제한자] 리턴타입  클래스명  메소드명  ([파라미터]))
	//@Before("execution(* edu.kh.project..*Impl*.*(..))")
	
	@Order(1) // 순서, 하나의 타겟에 대한 여러 advice 수행 시 순서 지정
	@Before("CommonPointcut.serviceImplPointcut()")
	public void beforeLog(JoinPoint jp) { // Advice (수행할 코드)
		// 매개변수 JoinPoint : AOP의 부가 기능이 적용된 대상의
		//		객체, 메서드, 파라미터 정보를 얻을 수 있게 해주는 객체
		
		// 대상 객체의 간단한 클래스명(패키지명 제외)
		String className = jp.getTarget().getClass().getSimpleName();
		
		// 메서드 선언부(== 메서드 시그니처)에서 메서드명만 얻어옴
		String methodName = jp.getSignature().getName();
		
		String str = "-----------------------------------------------\n";
		
		str += "[Start] : " + className + " - " + methodName + "()\n";
		// [Start] : MemberServiceImpl - login()
		
		// jp.getArgs() : 파라미터 묶음(배열)
		str += "[Parameter] : " + Arrays.toString(jp.getArgs()) + "\n";
		
		
		try {
			// 접속자 IP 얻어오기
			HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
			Member loginMember = (Member)req.getSession().getAttribute("loginMember");
			
			str += "[ip]" + getRemoteAddr(req);
			if(loginMember != null) {
				str += "(email:" + loginMember.getMemberEmail() + ")";
			}
		}catch (Exception e) {
			str += "[스프링 스케쥴러]";
		}

		
		
		
		logger.info(str);
	}
	
	
	
	
	public static String getRemoteAddr(HttpServletRequest request) {

        String ip = null;

        ip = request.getHeader("X-Forwarded-For");

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("Proxy-Client-IP"); 
        } 

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("WL-Proxy-Client-IP"); 
        } 

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("HTTP_CLIENT_IP"); 
        } 

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("X-Real-IP"); 
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("X-RealIP"); 
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("REMOTE_ADDR");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getRemoteAddr(); 
        }

		return ip;
	}

	
}




== ip 콘솔창에 잘 찍힘

AfterAspect.java

package edu.kh.project.common.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AfterAspect {
	
	private Logger logger = LoggerFactory.getLogger(AfterAspect.class);
	
	@Order(3)
	@After("CommonPointcut.serviceImplPointcut()")
	public void afterLog() {
		logger.info("----------------------------------------------\n\n");
	}

}
** 실행 순서 **

Before (1)
Around (2) = Before + After
After(3)

AroundAspect.java

== 실행시간 측정 시 많이 사용

package edu.kh.project.common.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AroundAspect {
	
	private Logger logger = LoggerFactory.getLogger(AroundAspect.class);
	
	// 전처리, 후처리를 모두 해결하고자 할 때 사용 하는 어드바이스
	// proceed() 메소드 호출 전  : @Before advice 작성
	// proceed() 메소드 호출 후  : @After advice 작성
	// 메소드 마지막에 proceed()의 반환값을 리턴해야함. 
	@Order(2) 
	@Around("CommonPointcut.serviceImplPointcut()")
	public Object aroundServiceLogs(ProceedingJoinPoint pp) throws Throwable {
		// @Around advice는 JoinPoint Interface가 아닌
		//  하위 타입인 ProceedingJoinPoint를 사용해야 함.
		
		
		long startMs = System.currentTimeMillis(); // 서비스 시작 시의 ms 값
		
		Object obj = pp.proceed(); // 여기가 기준
		
		long endMs = System.currentTimeMillis(); // 서비스 종료 시의 ms 값
		
		String str = "Running Time : " + (endMs- startMs) + "ms";	

		logger.info(str);
		
		return obj; // Around는 꼭 Object로 반환! 반환되는 Object에는 proceed가 꼭 담겨져 있어야 함! -> 그렇지 않으면 오류 발생
		
	}


}

0개의 댓글