ELK 기본

https://boolynn17.tistory.com/47

ELK로 ModuPly의 SSE·알림 로그 들여다보기

1. 왜 ModuPly에 ELK가 필요할까

ModuPly에서 SSE(Server-Sent Events)와 알림(Notification) 도메인은 특성상 로그만으로는 상태 파악이 까다롭다.

이럴 때 ELK 스택을 붙이면 SSE 연결 수 추이, heartbeat 실패율, 알림 타입별 발송 성공/실패 카운트를 대시보드 하나로 볼 수 있다.


2. 로그를 구조화하기 (애플리케이션 단)

grok으로 텍스트 로그를 파싱하는 것보다, 애플리케이션에서 처음부터 JSON으로 로그를 찍는 편이 Logstash 파이프라인을 훨씬 단순하게 만든다. 예를 들어 SSE emitter 생명주기 로그를 이렇게 구조화한다고 하자.

{"timestamp":"2026-07-20T10:15:32.123Z","level":"INFO","event":"sse.connect","userId":1024,"emitterId":"emt-9f3a"}
{"timestamp":"2026-07-20T10:15:42.130Z","level":"DEBUG","event":"sse.heartbeat","emitterId":"emt-9f3a","status":"success"}
{"timestamp":"2026-07-20T10:16:05.201Z","level":"WARN","event":"sse.heartbeat","emitterId":"emt-9f3a","status":"fail","reason":"IOException"}
{"timestamp":"2026-07-20T10:16:05.210Z","level":"INFO","event":"sse.disconnect","emitterId":"emt-9f3a","reason":"client_timeout"}
{"timestamp":"2026-07-20T10:16:10.300Z","level":"INFO","event":"notification.publish","notificationType":"NEW_REVIEW","targetUserId":1024,"status":"sent"}
{"timestamp":"2026-07-20T10:16:10.450Z","level":"ERROR","event":"notification.publish","notificationType":"PLAYLIST_UPDATE","targetUserId":2048,"status":"failed","reason":"emitter_not_found"}

Logback의 logstash-logback-encoder 같은 라이브러리를 쓰면 Spring Boot에서 이런 JSON 로그를 코드 변경 최소화로 뽑아낼 수 있다.


3. Logstash 파이프라인 구성

# modu-ply-logstash.conf
input {
  beats {
    port => 5044
  }
}

filter {
  json {
    source => "message"
  }

  date {
    match => ["timestamp", "ISO8601"]
    target => "@timestamp"
  }

  # event 필드 기준으로 분기 처리
  if [event] == "sse.heartbeat" and [status] == "fail" {
    mutate {
      add_tag => ["sse_heartbeat_failure"]
    }
  }

  if [event] == "notification.publish" and [status] == "failed" {
    mutate {
      add_tag => ["notification_failure"]
    }
  }

  mutate {
    remove_field => ["message", "timestamp"]
  }
}

output {
  if [event] =~ "^sse\." {
    elasticsearch {
      hosts => ["<http://localhost:9200>"]
      index => "modu-ply-sse-%{+YYYY.MM.dd}"
    }
  } else if [event] =~ "^notification\." {
    elasticsearch {
      hosts => ["<http://localhost:9200>"]
      index => "modu-ply-notification-%{+YYYY.MM.dd}"
    }
  } else {
    elasticsearch {
      hosts => ["<http://localhost:9200>"]
      index => "modu-ply-app-%{+YYYY.MM.dd}"
    }
  }
}

포인트는 두 가지다.

  1. JSON 로그 + json filter: grok 정규식 없이 바로 필드가 생긴다. 파이프라인이 훨씬 단순하고 견고하다.
  2. event 필드 기준 인덱스 분리: SSE 로그와 알림 로그를 서로 다른 인덱스에 저장하면, Kibana에서 도메인별로 Data View를 따로 만들어 조회 속도와 가독성을 둘 다 챙길 수 있다.