pwoogi
자신의 왜곡된 경험을 진실이라고 생각하지 말자

프로그래밍/Spring

AOP & Logging (slf4j)

pwoogi 2022. 8. 5. 21:55

 

 

 

AOP란?

 

 

AOPAspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다. 

 

예로들어 핵심적인 관점은 결국 우리가 적용하고자 하는 핵심 비즈니스 로직이 된다. 또한 부가적인 관점은 핵심 로직을 실행하기 위해서 행해지는 데이터베이스 연결, 로깅, 파일 입출력 등을 예로 들 수 있다.

 

AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다. 

 

 

AOP 특징

  • 프록시 패턴 기반의 AOP 구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서임
  • 스프링 빈에만 AOP를 적용 가능
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 ...)에 대한 해결책을 지원하는 것이 목적

 

 

Aspect

여러 클래스나 기능에 걸쳐서 있는 관심사, 그리고 그것들을 모듈화함

AOP 중 가장 많이 활용되는 부분은 @Transactional 기능

 

Advice

조언, AOP에서 실제로 적용하는 기능(로깅, 트랜잭션, 인증 등)을 뜻함

 

Join point

모듈화된 특정 기능이 실행될 수 있는 연결 포인트

 

Pointcut

Joint point 중에서 해당 Aspect를 적용할 대상을 뽑는 조건식

 

Target Object

Advice가 적용될 대상 오브젝트

 

AOP Proxy

대상 오브젝트에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업을 AOP Proxly라고 함

주로 CGLIB(Code Generation Library, 실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 함

 

Weaving

Adivce를 비즈니스 로직 코드에 삽입하는 것을 말함

 

 

위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지다.

 

 

AspectJ 지원

AspectJ는 AOP를 제대로 사용하기 위해 꼭 필요한 라이브러리

기본적으로 제공되는 Spring AOP로는 다양한 기법(Pointcut 등)의 AOP를 사용할 수 없음

 

Aspect의 생성

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component  // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {

}

 

 

Pointcut의 생성

  • 해당 Aspect의 Advice(실행할 액션)이 적용될 Join point를 찾기 위한 패턴 또는 조건 생성
  • 포인트 컷 표현식이라고 부름
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component  // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {

	@Pointcut("execution(* transfer(..))")
	private void anyOldTransfer() {}
}

 

Pointcut의 결합

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component  // Component를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록해서 사용하기 위함
public class UsefulAspect {

	@Pointcut("execution(public * *(..))")
	private void anyPublicOperation() {} //public 메서드 대상 포인트 컷

	@Pointcut("within(com.xyz.myapp.trading..*)")
	private void inTrading() {} // 특정 패키지 대상 포인트 컷
	
	@Pointcut("anyPublicOperation() && inTrading()")
	private void tradingOperation() {} // 위의 두 조건을 and(&&) 조건으로 결합한 포인트 컷
}

 

 

Advice 정의

포인트컷들을 활용하여 포인트컷의 전/후/주변에서 실행될 액션을 정의함

Before Advice

dataAccessOperation()이라는 미리 정의된 포인트 컷의 바로 전에 doAccessCheck가 실행

 

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}

 

After Returning Advice

dataAccessOperation()라는 미리 정의된 포인트컷에서 return이 발생된 후 실행

 

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}

 

 

Around Advice

businessService()라는 포인트컷의 전/후에 필요한 동작을 추가함

 

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
}

 

(현업에서 가장 자주 사용되는 것 같음

pointcut으로 잡아둔 함수의 동작시간을 확인하고 싶을 때도 사용하고

예 : 동일한 리소스를 여러사람이 사용하지 못하게할 때도 사용한다고 한다)

 

 

 


 

로그란( Log )?

 

로그는 간단하게 말해서 연속된 데이터의 기록.

일반적으로 처음 프로그래밍을 배울 때는 보통 System.out.print 같은 언어에서 제공해주는 메소드를 사용해보았을텐데 프로그램이 실행되면서 콘솔에 무엇인가가 출력되는데, 이런 것들이 로그가 될 수 있음.

쉽게말하면 계속 로그를 찍어보면서 프로그램의 진행상황을 확인해보는 것(테스트의 개념과는 다름)

 

 

Logging Framework가 없다면?

(System.out.println 을 사용해서 디버깅을 할 수 있지만, 만약 어플리케이션의 사이즈가 커지게 되면 굉장히 비효율적)

public void test() {
  System.out.println("test() 메소드 실행");
  try {
    someMethod();
  } catch (Exception e) {
    System.out.println(e.message);
    e.printStackTrace()
  }
}

 

 

왜 Log를 사용해야할까?


 

  • 비즈니스로직에서 로그성 코드를 분리
  • 상황에 따라 유연하게 대처할 수 있도록 로그를 레벨로 분리하여 노출 및 관리
  • 특정한 로그는 정해진 파일에 저장

 

 

Slf4j

 

 다양한 자바 로깅 시스템을 사용할 수 있도록 해주는 파사드 패턴의 인터페이스

 

slf4j를 사용하면 logback, log4j, log4j2와 같은 구현체를 어플리케이션에 영향 없이 손쉽게 교체할 수 있다

https://deeplify.dev/back-end/spring/logging

 

 

Slf4j 사용방법

 

1. 의존성 추가

 

spring-boot-starter-web에는 기본적으로 logback 로깅 모듈이 포함되어 있지만, 성능 상 log4j2가 logback 보다 우수하기 때문에 이를 제외하고 log4j2를 추가해주는 작업을 해줄 것

// ... 생략

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
✅+   implementation 'org.springframework.boot:spring-boot-starter-log4j2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

✅
+ configurations {
+     all {
+       exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
+     }
+ }

// ... 생략

log4j2 의존성을 추가해주고, spring-boot-starter-logging을 제외함

(사실 스프링에서 기본 제공하는 logback을 많이 쓴다고 한다)

 

 

2. applicaiton.properties

//전체 로그 레벨 설정(기본 info)
logging.level.root=info 

//hello.springmvc 패키지와 그 하위 로그 레벨 설정 
logging.level.hello.springmvc=debug

 

 

 

3. 롬복을 이용하여 SLF4J 사용 가능

private Logger log = LoggerFactory.getLogger(getClass()); 
private static final Logger log = LoggerFactory.getLogger(Xxx.class)

 

 

4.로그 호출 방법

 

  • log.info("hello")
  • System.out.println("hello")

 

LogTestController

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LogTestController {

    // @Slf4j가 있으면 해당 역할을 대신 한다.
    // private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest() {
        String name = "Spring";

        log.trace("trace log = {}", name);
        log.debug("debug log = {}", name);
        log.info("info log = {}", name);
        log.warn("warn log = {}", name);
        log.error("error log = {}", name);

        return "ok";
    }
}

 

 

LogTestController 매핑 정보

 

@RestController

  • @Controller는 반환 값이 String이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.
  • @RestController는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다. 따라서 실행 결과로 ok 메세지를 받을 수 있다.

 

로그 옵션

 

  1. 로그가 출력되는 포맷
  • 시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지
  1. 로그 레벨 설정을 변경해서 출력 결과를 다르게 할 수 있다.
  • LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
  • 개발 서버는 debug 출력
  • 운영 서버는 info 출력

 

로그 사용시 장점

 

시스템 콘솔로 직접 출력하는 것 보다 로그를 사용하면 다음과 같은 장점이 있다. 실무에서는 항상 로그를 사용한다고 한다.

 

1. 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
2. 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그 내용을 상황에 맞게 조절할 수 있다.
3. 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다. 특히, 파일로 남길 때는 날짜나 특정 용량을 기준으로 로그를 분할하는 것도 가능하다.
4. 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용한다고 한다.


 

올바른 로그 사용법

 

log.debug("data="+data)와 같은 방식은 사용하지 않는다

  • 로그 출력 레벨을 info로 설정해도, 실제로는 해당 코드에 있는 "data="+data가 실행된다. 결과적으로 문자 연산이 발생한다. 의도치 않게 불필요한 연산 작업을 수행시키는 꼴이다. 로그가 많다면, 문제가 발생할 여지가 있다.

log.debug("data={}", data)와 같은 방식을 사용한다

  • 로그 출력 레벨을 info로 설정하면 아무일도 발생하지 않는다. 따라서, 의미없는 연산이 발생하지 않는다. 더 효율적으로 리소스를 사용할 수 있다.

 

 

 

References : 

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

https://logging.apache.org/log4j/2.x/manual/

 

Log4j – Overview

Copyright © 1999-2022 The Apache Software Foundation. All Rights Reserved. Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the Apache Logging project logo are trademarks of The Apache Software Foundation.

logging.apache.org

https://velog.io/@woply/

'프로그래밍 > Spring' 카테고리의 다른 글

Spring Boot Validation  (0) 2022.08.08
ORM, Hibernate, JPA  (0) 2022.08.07
[SPRING BOOT]JWT, Thymeleaf, form (2/2)  (0) 2022.08.04
[SPRING BOOT]JWT, Thymleaf, form (1/2)  (2) 2022.08.04
[DI, IoC, Bean] 개념 박살내기  (0) 2022.08.01