개발자로 후회없는 삶 살기

[최적화] 유저 트랜잭션 분석을 위한 로깅 전략 본문

[대외활동]/[네이버 BoostCamp]

[최적화] 유저 트랜잭션 분석을 위한 로깅 전략

몽이장쥰 2024. 4. 4. 01:05

서론

  • Log4j2 로깅 전략
  • 유저 모니터링으로 사용성 개선

로깅을 사용하는 목적은 다양하며, 로컬, 개발, 운영 레벨에서 로깅 전략에 차이점을 주어 목적에 맞는 데이터를 수집합니다. 저희 서비스에서 로깅을 사용한 목적을 말씀드리고, 이를 토대로 유저 모니터링하여 사용성을 개선한 경험을 기록합니다. 사용성 개선 부분 먼저 보고 싶으신 분은 Ctrl+F에 [유저 모니터링으로 사용성 개선]를 검색해주세요.

 

본론

- 로깅 도입 목적

로깅을 사용하는 목적은 다양하지만, 운영 레벨에서는 사용자의 서비스 활용도를 파악하기 위해 사용한다고 합니다. 저희 서비스 또한, 상품 구매 목적으로 활용하는 사용자가 대부분일 것이기에 사용자가 저희 서비스를 통해 원하는 목적을 달성할 수 있도록 유저 데이터를 수집하기로 했습니다.

 

-> 로깅 체크 리스트

유저 데이터 확보를 위한 로그 체크리스트를 작성했습니다.

 

-> 체크 리스트 목표

  1. 메뉴별로 유저 일/월/년 접근량을 파악하기
  2. 유저가 주로 사용하는 트랜잭션을 파악하여 정보량을 많이 요구하는 쿼리를 최적화
  3. 비정상적 접근(ddos, 또는 권한이 없는 자의 접근 등) 추이를 파악하고 보안 대응
  4. 잘 쓰이지 않는 트랜잭션이나 메뉴가 있을 경우 개편
  5. 서비스 내에서 사용자 이동패턴을 분석하고 추후 UI 수정 등에 반영

결론적으로 유저 모니터링에 사용하기 위한 로그 체크리스트를 만들었습니다. 저희의 경우 1번과 5번에서 이동패턴으로 공간이나 가구를 생성시키고 유저가 배치까지 잘하는지를 분석하는 데 의미가 있습니다. 실제로 UXR 결과 갤러리 버튼에 활용도가 낮아 추후 UI 수정에 반영했습니다.

 

유저가 실제로 배치하는 데까지 걸리는 시간과 배치를 하기 위해 기다린 시간, 배치를 위해 몇 개의 데이터를 업로드해주는지 등을 분석하여 실질적으로 서비스가 얼마나 사용되고 있는지를 분석하고자 할 수 있을 것입니다. 이 분석을 토대로 '유저가 이 만큼 활발하게 이용을 잘 한다'가 증명할 수 있다고 판단했습니다.

 

체크 리스트 중에서는 Log4j2를 활용하여 얻을 수 있는 것도 있지만, 위 사진처럼 추론 서버에서 일어나는 일 등, 다른 방법으로 얻은 로그 데이터도 있습니다. 이 글에서는 Log4j2를 활용한 방식만 기록하겠습니다.

 

- Logback vs Log4j2

-> Logback

  • Log4j 개발팀이 제작하여 기본적으로 Log4j와 비슷하지만 성능이 향상됨
  • spring-boot-starter-web의 기본 로깅 프레임워크

-> Log4j2

  • Logback, Log4j의 단점을 개선한 로깅 프레임워크
  • Logback보다 탁월한 성능 및 다양한 appender 제공

위와 같은 이유로 성능이 좋다는 점을 고려하여 Log4j2를 사용하기로 했습니다.

 

- 로깅 전략

로컬, 개발, 운영 레벨에서 사용하는 전략이 달라집니다.

 

1. 로컬 : file로 저장되거나 아카이브화 하여 보관할 필요가 없다고 판단하여 Appender를 Console로 설정했습니다.
2. 개발 : 배포하기 전 API를 맞춰봐야 하기 때문에 File로 남겨두는 것이 좋다고 판단하여 File_Appender로 설정했습니다.
3. 운영 : 로그 파일을 보관하는 것이 좋다고 판단해 RollingFile_Appender로 설정했습니다.

 

+ 로그 레벨

로컬과 개발 환경에서는 DEBUG, 배포 환경에서는 INFO로 로그 레벨을 설정했습니다. Spring Application 관련 불필요한 정보들이 너무 많이 출력되는 것을 고려하여 환경에 알맞게 세팅하는 것이 올바른 선택입니다. 무조건 많은 정보를 띄운다고 좋은 것이 아니며, 용량과 가독성을 고려하기도 해야 합니다.

 

1. 로컬 환경 Log4j2.yml

Configutation:
  name: Default
  status: info

  Properties:
    Property:
      name: log-path
      value: "logs"

  Appenders:
    Console:
      name: Console_Appender
      target: SYSTEM_OUT
      PatternLayout:
        pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
            ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} %style{[%t]}{yellow}- %m%n"

  Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: Console_Appender
    Logger:
      - name: com.boostcamp.zzimkong
        additivity: false
        level: trace
        AppenderRef:
          - ref: Console_Appender

 

2. 개발 환경 Log4j2-develop.yml

Configutation:
  name: Default
  status: info

  Properties:
    Property:
      name: log-path
      value: "logs"

  Appenders:
    File:
      name: File_Appender
      fileName: ${log-path}/logfile.log
      append: false
      PatternLayout:
        pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
            ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} %style{[%t]}{yellow}- %m%n"

  Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: File_Appender
    Logger:
      - name: com.boostcamp.zzimkong
        additivity: false
        level: debug
        AppenderRef:
          - ref: File_Appender

 

3. 운영 환경 Log4j2-deploy.yml

Configutation:
  name: Default
  status: info
 
  Properties:
    Property:
      name: log-path
      value: "logs"
 
  Appenders:
    RollingFile:
      - name: RollingFile_Appender
        fileName: ${log-path}/rollingfile.log
        filePattern: "${log-path}/archive/rollingfile.log_%d{yyyy-MM-dd}-%i.gz"
        PatternLayout:
          pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
            ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} [%C] %style{[%t]}{yellow}- %m%n"
        Policies:
          TimeBasedTriggeringPolicy:
            Interval: 1
            modulate: true
          SizeBasedTriggeringPolicy:
            size: "10 MB"
        DefaultRollOverStrategy:
          max: 10
          Delete:
            basePath: "${log-path}/archive"
            maxDepth: "1"
            IfLastModified:
              age: "P14D"
            IfAccumulatedFileCount:
              exceeds: 140
  Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: RollingFile_Appender
    Logger:
      - name: com.boostcamp.zzimkong
        additivity: false
        level: info
        AppenderRef:
          - ref: RollingFile_Appender

 

- Appenders

Appenders:
Console:
  name: Console_Appender
  target: SYSTEM_OUT
  PatternLayout:
    pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
        ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} [%C] %style{[%t]}{yellow}- %m%n"

로깅 전략과 출력 패턴을 명시하는 부분입니다.

 

target : 표준 입출력임을 명시
pattern : 로그 스타일 지정, 가독성을 위한 색깔 지정

 

패턴과 색깔을 적용했을 때 모습입니다.

 

-> File_Appender

Appenders:
File:
  name: File_Appender
  fileName: ${log-path}/logfile.log
  append: false
  PatternLayout:
    pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
        ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} %style{[%t]}{yellow}- %m%n"

 

 

파일의 경우 저장 경로를 지정하면 로그 파일이 컴퓨터에 저장됩니다.

 

-> RollingFile_Appender

이 경우 파일을 아카이빙 할 기간, 용량 관리 등을 위해 설정이 복잡해집니다.

 

Appenders:
    RollingFile:
      - name: RollingFile_Appender
        fileName: ${log-path}/rollingfile.log
        filePattern: "${log-path}/archive/rollingfile.log_%d{yyyy-MM-dd}-%i.gz"
        PatternLayout:
          pattern: "%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red,
            ERROR=red, INFO=green, DEBUG=blue, TRACE=bg_yellow} [%C] %style{[%t]}{yellow}- %m%n"
        Policies:
          TimeBasedTriggeringPolicy:
            Interval: 1
            modulate: true
          SizeBasedTriggeringPolicy:
            size: "10 MB"
        DefaultRollOverStrategy:
          max: 10
          Delete:
            basePath: "${log-path}/archive"
            maxDepth: "1"
            IfLastModified:
              age: "P14D"
            IfAccumulatedFileCount:
              exceeds: 140

 

Policies : 파일 보관 기준을 지정
OnStartupTriggeringPolicy : jvm start 시 trigger
TimeBasedTriggeringPolicy + Interval : 시간에 따라 trigger, 위 예시는 하루 단위로 압축 보관
SizeBasedTriggeringPolicy + size : file size에 따른 trigger
CronTriggeringPolicy : Cron 정책에 따름

DefaultRollOverStrategy : 파일 삭제 기준 지정
max : 동시간대에 최대 10개까지 파일이 생성 가능
IfLastModified - age : 수정된 지 14일이 지난 파일은 삭제
IfAccumulatedFileCount :RollingUp된 압축파일을 저장하는 디렉토리에 존재할 수 있는 파일 개수. 파일 개수를 넘으면 오래된 파일부터 삭제(총 140개 초과시 오래된 파일부터 삭제)

운영 레벨에서 유저 데이터는 다음과 같은 전략으로 수집했습니다.

 

-> 유저 데이터 수집 예시

Log 폴더에 모든 로그가 Append 됩니다. 색깔이 로그에 적용되어 가독성이 좋은 효과를 주며, 현재 스프링 AOP를 사용하여 메서드 호출 스택에 따른 깊이를 '-->'로 표시하고 메서드 호출 시간도 표시했습니다.

 

-> 유저 선호도 파악 예시

유저 선호도를 파악하기 위한 방법으로 가구나 공간 단건 조회 횟수를 카운팅 하면 된다고 판단했습니다. 특정 메서드 호출 시에 로깅되어야 하는 데 가장 편한 방법은 해당 메서드마다 log.info() 메서드를 붙이는 것입니다.

 

하지만 이렇게 되면 로깅이 필요한 메서드가 추가될 때마다 코드 수정이 필요하고 log 메서드를 각 메서드마다 붙여야 한다는 단점이 있습니다.

 

따라서, 클라이언트 코드 수정 없이 기능을 추가하기 위해 스프링 AOP를 적용하여 단건 조회 메서드 호출 시에만 로깅되도록 했습니다. 테스트 결과 다른 로그는 사라지고 단건 조회 시에만 로그가 남으며, 이를 따로 저장하면 사용자 선호도 조사가 가능합니다.

 

- 유저 모니터링으로 사용성 개선

지금부터 UXR 유저 모니터링으로 서비스를 고도화한 과정을 기록합니다.

 

🚨 사용자1 : 서비스의 전체적인 정체성을 모르겠어요. 인테리어인지? 3D 재구성인지?

정체성이 불분명하다는 의견이 나온 이유는 헤더 네이밍 때문이었습니다. Space, Furniture만 명시해 놓으니 유저 입장에서는 무엇을 의미하는지 한 번 더 고민해봐야 했고

 

1) 인테리어 툴이 있는지도 몰랐다. 
2) 갤러리 버튼이 있는지도 몰랐다.

이로 인해 위와 같은 피드백도 많았고 로깅 집계 결과, 가구 배치와 인테리어 툴 클릭률이 현저히 낮았습니다.

 

✅ 해결 방법

헤더를 서비스 이해에 필요한 요소(서비스 안내, 가이드, 갤러리)들로 교체하고 회원가입 전에도 서비스 탐색을 할 수 있는 데모 체험 기능을 추가했으며, 주된 서비스 기능인 갤러리와 나의 3D 재구성 버튼을 홈페이지 상단에, 한눈에 볼 수 있도록 재배치했습니다.

 

🚨 사용자 2 : 영상 촬영 가이드가 너무 어렵고 한눈에 들어오지 않아요

과거 영상 촬영 가이드에 대한 지적이 있었고, 이를 해결하기 위한 방법을 모색했습니다.

 

1) 가이드에 사진과 설명을 Step By Step

탐색 중, 이케아에서 시행하고 있는 영상 촬영 사이드를 참고하여 단계별 가이드로 명확한 지침을 주는 방법을 선택했습니다.

 

2) 전체 공간보다는 원하는 공간만 촬영하도록 변경

전체 공간을 촬영했을 경우 가이드를 이행하기 어렵다는 피드백이 있었고, 공간에 따라 최대 10분까지 촬영을 해야 함에 따라 사용자들이 서비스를 이용하기 어려웠고, 이를 해결해야 하기 위해 무수한 고민을 했습니다.

 

✅ 가구 배치를 원하는 면만 촬영하게 하자!

 

저희는 가구 배치가 목적이라면 원하는 면만 촬영해도 원하는 분위기인지 확인할 수 있을 거라는 생각이 들었고 영상 길이 제한을 6분에서 2분으로 줄인 결과 인테리어 툴 클릭률과 사용률이 70% 증가했습니다.

 

🚨 연구원 1 : 추론 서버에서 에러가 발생 시 확인할 수 있는 관리자 페이지가 필요

저희는 총 6대의 분산 환경에서 추론 서버를 운영하였고, 이 때문에 추론 서버에서 에러 발생시 어떠한 서버에서 어떠한 에러가 발생했는지 확인할 수 있는 방법이 필요했습니다. 이를 해결하지 않으면 유저의 요청이 유실되는 문제까지 발생했습니다.

 

1) 어떠한 유저가 어떠한 요청을 보냈는지 확인하는 기능

이 기능은 어떤 유저의 요청에서 문제가 발생했는지 확인하기 위한 기능으로, 위 사진은 재구성을 요청한 유저의 닉네임을 의미합니다.

 

2) 어떤 유저의 요청이 어떤 추론 서버로 전달됐는지 확인

1번 기능으로 유저의 요청을 리스트업 했다면, 해당 유저가 요청을 보냈을 때 어떠한 파일명의 요청이 어떠한 서버에서 추론되는지 로깅했습니다. 만약 문제가 발생하면 연구원이 해당 서버로 접속하여 문제를 해결할 수 있습니다.

 

3) 추론 중 어느 부분에서 문제가 발생했는지 확인하는 기능

마지막으로, 추론 중 발생한 문제를 해결하기 위해 디버깅 용도의 로깅 기능을 추가했습니다. 각 라인은 추론 과정에서 DB에 저장되고, 문제 발생 시 발생 원인을 로깅합니다.

 

결론

이렇게 개발, 운영 레벨에서 로깅을 활용하는 방법을 배우고 실제 서비스에 적용해 보았습니다. 서비스를 운영하면 발생할 수 있는 다양한 오류를 직면했고 해결하며 사이드 프로젝트와 실제 서비스를 운영하는 레벨은 하늘과 땅 차이임을 한 수 배웠습니다. 끝으로 유저 선호도 패턴 로깅으로 알아낸, 유저가 선호하는 가구에 대해 모델 학습을 강화한다면 더욱 고객 만족도가 올라갈 거라고 기대합니다.

Comments