끄적끄적

static logger 에 대한 지극히 개인적인 생각 본문

개발/방법론

static logger 에 대한 지극히 개인적인 생각

코리이 2023. 3. 10. 22:41

필자는 보통 Nestjs 로 개발하는데 대부분 아래와 같은 형식으로 로거를 주입시켜 개발한다. 

export class WithdrawCommandHandler {
    constructor(
        private readonly assetRepository: IAssetRepository,
        private readonly logger: ILogger,
    ) {}
}

그런데 최근에 사이드 프로젝트를 spring 으로 개발하고 있는데 대부분의 로거를 아래와 같인 Slfj 의 어노테이션을 활용한다. 그리고 컴파일하면 static logger 를 사용하도록 코드가 쓰여져 있음을 확인할 수 있다.

@Service
@Slf4j
public class WithdrawCommandHandler {
    private final AssetRepository assetRepository
}

// 컴파일 시
@Service
public class WithdrawCommandHandler {
    private static final Logger log = LoggerFactory.getLogger(WithdrawCommandHandler.class);
    private final AssetRepository assetRepository
}

즉 Nest 로 개발할 때는 의존성 주입을 통해 logger 를 동적으로 주입시키는 방법을 활용하고 spring 으로 개발할때는 static logger 를 통해서 logger 를 활용한다. 그래서 갑자기 왜 다를까 궁금해져서 여러 사람들의 블로그를 뒤져보다가 개인적인 생각을 적어보고자 한다.

static logger 의 문제는 뭘까

우선 한국에서 가장 활발하게 사용되고 있는 (java) static logger 에 대한 이야기를 하고자 한다. 결론부터 이야기하자면 static logger는 객체지향의 관점에서 보면 안티패턴 중에 하나라고 볼 수 있다. 하지만 특정 언어의 특수성이 있지 않을까 싶다. 필자의 경우 java 의 LogBack이나 Log4J 에 대한 큰 이해는 없는 상황이다. 그래서 아래 링크에서 실제로 사용하는 분의 의견을 빌려 이야기 해볼까 한다. 

Java Logging Framework는 Appender 라는 실제로 로깅을 행하는 객체를 설정을 통해 자유롭게 "Dependency Injection" 할 수 있게 이미 설계되어 있다.

 

실제로 spring 으로 개발할 때 대부분의 개발자들은  logback.xml  파일을 통해 자유롭게 환경에 따라 다르게 로깅을 한다. 개인적으로 static 로깅을 할 때 가장 귀찮았던 부분중에 하나가 테스트 코드 작성에는 로그를 사용하고 싶지 않은데 static logger 를 사용하면 의미 없는 로그가 계속 찍혀 필요한 부분을 확인하는데 너무 많은 정보가 나왔던 것이였다. 하지만 이런 부분들은 환경설정으로 충분히 커버가 되는 부분인 것 같다. 그 외에도 static logger 의 여러 단점을 찾아봐도 spring 을 사용하는 경우 특별히 문제될 것 같다는 생각이 들지 않았다.

다만 static logger 를 사용했을 때 가장 큰 문제점이 무엇일까 생각해보면 만약 내가 Log4j 를 사용하고 있는데 새로운 좋은 로거가 생겼다고 가정해보자. 그럼 새로운 로거로 갈아끼우기 위해 모든 비즈니스 로직을 갈아 엎어야 하는 상황이 발생할 것 같다. 비교해서 이야기 해보자면 최근 많은 개발자들이 DDD, 클린아키텍쳐니 하면서 도메인은 외부 인프라에 독립적이게 만들어야 한다고 이야기 한다. 그 때문에 외부 환경때문에 도메인 로직이 변경되지 않게 하기 위해 아래 그림처럼 Repository 를 추상화하거나 한다. 즉, 추상화를 해서 레파지토리를 인프라 레이어로 밀어내면 외부 환경에 휘둘리지 않는 코드를 작성할 수 있다고 한다. 그리고 "만약 내가 Mysql 에서 Mongodb 로 바꾸고 싶을때 쉽게 바꿀 수 있다" 등의 예시를 제시한다.

출처: https://softwareengineering.stackexchange.com/questions/263016/ddd-modularizing-the-application-and-domain-layers-without-breaking-the-dip

그렇다면 Logger 는 어떨까? Logger 의 경우에도 충분히 외부 툴이라고 생각해 볼 수 있다. 즉, 추상화 해야하는 객체가 될 수 있다는 점이다. 실제로 Nodejs 진영에서 많은 개발자들이 Winston 이라는 라이브러리를 쓰지만 최근에는 Pino 혹은 다른 로깅 라이브러리들이 많이 새롭게 출시되는 중이다. 필자의 경우에도 Nestjs 로 개발하면서 처음엔 Nestjs 에 내장된 로거를 쓰다가 Winston 으로 바꾸는 작업을 진행했는데 Logger 를 추상화 했기 때문에 쉽게 변경할 수 있었다. 추가로 Pino 로 바꾸고 싶다면 추상화한 Logger 구현체만 갈아 치워주면 쉽게 변경할 수 있게 된다.

 

다시 돌아와서 그럼 Spring 에서는 어떨까? 지극히 주관적인 생각으로는 굳이 추상화하려고 들지 말고 "그냥 써라" 라고 생각한다. 왜냐하면 첫째로 Nestjs(nodejs) 와 비교해서 Spring(Java) 의 경우 이미 성숙한 프레임워크라고 생각해서 로그에 필요한 많은 것들이 이미 제공되고 있을 것이고, 많은 좋은 레퍼런스들도 많기 때문이다. 그렇기 때문에 Spring(Java) 에서는 로그에 관한 것들을 변경할 확률이 매우 적을 것이다. 그런 상황에서는 오히려 남들이 하지 않는 추상화를 굳이 써서 버그라도 생기면 골치아파질 것 같다. 둘째로 실제로 도메인 로직이 로깅때문에 문제가 생겼던 적은 개인적인 경험으로써는 매우 적다. (이전에 딱 한번 있었다.) 도메인을 해치는 일일수는 있지만 static logger 가 주는 편리함을 생각하면 어느정도의 트레이드 오프는 충분히 가치있는 일이라고 생각한다. 

그럼 의존성 주입은 ??

개인적으로 Spring(Java) 처럼 이미 잘 사용하고 있는 않는 로거라면 의존성 주입을 활용해서 사용하는 것이 더 좋은 선택이라고 생각한다.  이유는 로거도 사실상 외부 인프라에 종속되어 있다고 생각하기 때문이다. 하지만 nodejs 를 예전부터 써오신 분들이라면 DI 를 사용하는 것보다 static 한 함수를 만들어 직접적으로 사용하는 것을 더 선호하는 사람들이 많다. 앞에서 이야기한 Winston 에 대해서 이야기 해보자. 실제로 이 로거를 사용하는 대부분의 Nodejs 개발자들은 아래와 같은 방식으로 사용할 것이다.

import winston from 'winston';
const logger = winston.createLogger({ /* 설정값 */ })
export { logger };

// 사용시
import { logger } from '../winston-logger';

const withdraw = (balance) => {
    logger.info(`출금하기 ${balance}`)
}

즉, logger 를 미리 생성해두고 static 하게 사용한다는 것이다. 그리고 대부분의 경우 문제없이 잘 사용하고 있다. 특히나 로거 뿐만 아니라 일반적인 함수 조차도 굳이 클래스 형식으로 만드는 것을 선호하지 않는다. 왜냐하면 태생 자체가 클래스 기반도 아니였고 오히려 클래스 기반으로 뭔가를 하게 된다면 코딩에서 많은 부분들이 꼬이기 시작하기 때문이다. 그래서 Nodejs 개발을 많이 해본 개발자들은 대부분의 로직들을 함수형으로 만들고자 노력한다. 여담으로 아래와 같이 이야기 하시는 분들도 있다.

dependency injection is "really just a pretentious way to say 'taking an argument'"
(DI 는 argument 를 전달하는 허세적인 방법이다)
- Rúnar Bjarnason 

 

근데 맞는 말이라 생각한다. 결국 DI 라는 건 "new Class(a, b)" 와 같은 형태를 만들어주는 것 따위에 불과하니까 말이다. 즉, 다시 생각해 봐야 할 점이 있다면 Nodejs 에서 의존성 주입 자체가 필요할까? 하는 생각이다. 이번 포스팅에서 이 점까지 이야기 한다면 너무 나아가는 것 같다는 생각이 들어 결론만 이야기하자면 Case by Case 라는 점이다. 필자처럼 이미 객체지향으로 익숙한 사람이라면 그렇게 만들면 되고 개발실력이 더 좋으신 분들은 함수형으로 만들면 될 일이다. 각자만의 장점이 있다고 생각한다.

결론

참고 했던 블로그들이나 페이스북 글들이 대부분 5년 전의 것들이다보니 현재는 어떻게 다들 받아들이고 있는지는 잘 모르겠다. 개인적으로 프로그래밍을 하면서 "무조건 해야한다" 라는 건 없다고 생각하기 때문에 이것도 괜찮고 저것도 괜찮다 라고 말하고 싶어 조금 두서없이 이야기 한 것 같기도 하다. 결국에는 회사에 맞는 방향으로 개발하는 것이 정답이 아닐까 싶다. 

참고

https://justhackem.wordpress.com/2017/07/07/no-more-static-logger/

https://www.facebook.com/kwon37xi/posts/pfbid02HzWtsX6anLUM66ukAE8U9fLtUxN34ugoJKaYLZ2Q5QU6J5fLYJ9A2VBMQFk4k8fKl

https://www.facebook.com/gyuwon.yi/posts/pfbid0ZsA9PEQrNVGF2JZVh4EQbdcvXSYheDEzPg8gCPq9gek2GQDgMrcrYBSU1qEqk9aXl

https://kjkjjang.wordpress.com/2019/05/06/spring-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-log%EB%8A%94-static-instance-variables/

https://kjkjjang.wordpress.com/2019/05/06/spring-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-log%EB%8A%94-static-instance-variables/

https://stackoverflow.com/questions/6653520/why-do-we-declare-loggers-static-final