☘ ANONI Chat - 모니터링 도구 적용(Elastic APM) + Slack알림
시간이 지남에 따라 변화하는 데이터를 의미한다.
메모리 사용률, CPU 사용률, 스레드 사용률 등 시간에 따른 추이를 추적할 가치가 있는 데이터이다.
어떤 서비스, 앱이냐에 따라 해석이 달라질 수는 있지만, 보편적으로 대시보드를 볼때 특정 수치들을 그래프로 보여주는 일종의 시각화로 이해하기도 한다.
| 기능 구분 | 설명 |
|---|---|
| 분산 추적 (Distributed Tracing) | 마이크로서비스 환경에서 서비스 간 호출을 추적. 전체 요청이 여러 서비스에 걸쳐 어떻게 전파되는지 시각화. |
| 자동 계측 (Auto Instrumentation) | 많은 언어/프레임워크에서 자동으로 HTTP, DB 쿼리, 외부 호출 등의 성능 데이터를 수집. |
| 에러 추적 (Error Capturing) | 예외 발생 시 스택 트레이스, 메시지, 환경정보 등을 자동 수집. 오류의 빈도, 위치, 영향을 받은 사용자 정보까지 확인 가능. |
| 서비스 맵 (Service Map) | 서비스 간의 호출 구조를 시각적으로 표현. 병목 구간이나 호출 관계를 쉽게 파악 가능. |
| 실시간 메트릭 수집 (Real-Time Metrics) | CPU 사용량, 메모리 사용량, GC 활동 등 JVM/Node.js 등의 런타임 수준의 성능지표 수집 가능. |
| Kibana 대시보드 통합 | Kibana UI를 통해 수집된 APM 데이터를 시각화. 사용자 정의 대시보드도 쉽게 생성 가능. |
| 지원 언어 및 프레임워크 | Java, Node.js, Python, Ruby, .NET, Go 등 다양한 언어 지원. Spring Boot, Django, Express 등 주요 프레임워크도 대응. |
| OpenTelemetry 호환 | OpenTelemetry로 수집한 trace 데이터도 수용 가능, 벤더 종속성 낮춤. |
| 환경 정보 수집 | 요청 URL, 사용자 에이전트, 사용자 IP, 컨텍스트 등 다양한 환경 정보 자동 수집. |
| 항목 | Elastic APM | Datadog | New Relic | Prometheus + Jaeger |
|---|---|---|---|---|
| 가격 | 오픈소스 기반 (자체 호스팅 가능) | 유료 | 유료 | 오픈소스 |
| 통합성 | ELK와 완벽 연동 | 자체 플랫폼 | 자체 플랫폼 | 연동 필요 |
| 시각화 | Kibana | 자체 UI | 자체 UI | Grafana 필요 |
| 자동계측 범위 | 주요 프레임워크 지원 | 더 광범위함 | 매우 광범위 | 제한적 |
| 커스터마이징 | 매우 유연 | 제한적 | 제한적 | 유연 |
[클라이언트 요청]
↓
[NGINX Reverse Proxy]
↓
[Spring Boot 컨테이너]
├── Agent가 DispatcherServlet, Service, Repository 등 Hook
├── Transaction 및 Span 객체 생성
└── 비동기로 APM Server로 전송
↓
[APM Server]
└── JSON 수신 후 Elasticsearch로 전송
↓
[Elasticsearch]
└── 문서화된 데이터 저장
↓
[Kibana]
└── 트랜잭션 시각화 및 분석
APM Agent는 애플리케이션 내부에 직접 탑재되어 실행 중인 코드를 자동 또는 수동 계측해 성능 데이터를 수집하는 라이브러리 또는 패키지다.
Java에선 Elastic APM Java Agent라는 JVM 에이전트를 등록하여 사용.
APM Server는 다양한 APM Agent가 수집한 trace/metric/error 데이터를 받아 Elasticsearch로 전달해주는 브로커 역할을 한다.
즉, APM agent → APM server → Elasticsearch → Kibana 순으로 연결된다.
| 역할 | 설명 |
|---|---|
| 데이터 수신 | 각 언어의 APM Agent가 전송하는 JSON 기반 트레이스 데이터 수신 |
| 검증 및 필터링 | 데이터 포맷 및 필드 검증, 불필요한 필드 제거 |
| 변환 및 전처리 | OpenTelemetry, Jaeger 등의 trace 포맷을 Elastic 포맷으로 변환 |
| Elasticsearch 전송 | 처리된 데이터를 Elasticsearch 인덱스로 전송 (apm-* 패턴) |
| 보안 설정 | API Key, secret token, TLS 등으로 인증 관리 |
# APM Server 추가
apm-server:
image: docker.elastic.co/apm/apm-server:7.11.1
container_name: apm-server
environment:
- output.elasticsearch.hosts=["elasticsearch:9200"]
- apm-server.host=0.0.0.0:8200
- apm-server.frontend.enabled=true
- apm-server.frontend.rate_limit=100000
- apm-server.read_timeout=1m
- apm-server.shutdown_timeout=2m
- apm-server.write_timeout=1m
- logging.level=info
ports:
- "8200:8200"
networks:
- elk
depends_on:
- elasticsearch
# Spring app에 필요한 매개변수 추가
spring:
image: ghcr.io/anonichat/app/anonichat
expose:
- "8080"
environment:
- ELASTICSEARCH_HOST=elasticsearch:9200
...생략
ELASTIC_APM_SERVICE_NAME=anonichat-app # APM에서 표시될 서비스명
ELASTIC_APM_SERVER_URLS=http://apm-server:8200 # APM 서버 주소
ELASTIC_APM_APPLICATION_PACKAGES=com.anonichat # 모니터링할 패키지
ELASTIC_APM_ENVIRONMENT=docker # 환경 구분
ELASTIC_APM_LOG_LEVEL=INFO # 로그 레벨
ELASTIC_APM_ENABLE_LOG_CORRELATION=true # 로그 연관성 활성화
depends_on:
- elasticsearch
- mysql
networks:
- elk
- data
# Kibana APM 활성화에 필요한 매개변수 추가
kibana:
image: docker.elastic.co/kibana/kibana:7.11.1
ports:
- "5601:5601"
networks:
- elk
environment:
... 생략
- xpack.apm.ui.enabled=true # Kibana에서 APM 메뉴 활성화
depends_on:
- elasticsearch
# apm agent jar 추가
RUN curl -o /app/elastic-apm-agent.jar \
https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.28.4/elastic-apm-agent-1.28.4.jar
# apm agent 실행 명령어 추가
ENTRYPOINT ["java", "-javaagent:/app/elastic-apm-agent.jar", "-jar", "/app/AnoniChatApp.jar"]
# dependency 추가
// Elastic APM Agent (프로그래밍 방식 연결용)
implementation 'co.elastic.apm:apm-agent-attach:1.50.0'
// APM Spring Boot Starter (자동 설정 - Spring Boot 3.x 호환)
implementation 'co.elastic.apm:elastic-apm-spring-boot-starter:1.50.0'
// APM과 로그 연동 (ECS 로그 형식)
implementation 'co.elastic.logging:logback-ecs-encoder:1.6.0'
# APM 설정
elastic.apm.service-name=${ELASTIC_APM_SERVICE_NAME:anonichat-app}
elastic.apm.server-urls=${ELASTIC_APM_SERVER_URLS:http://localhost:8200}
elastic.apm.application-packages=${ELASTIC_APM_APPLICATION_PACKAGES:Anoni}
elastic.apm.environment=${ELASTIC_APM_ENVIRONMENT:local}
elastic.apm.enabled=${ELASTIC_APM_ENABLED:true}
elastic.apm.transaction-sample-rate=${ELASTIC_APM_SAMPLE_RATE:1.0}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
... 생략
<!-- ECS 형식 appender (Elastic Stack 최적화, APM 완벽 연동) -->
<appender name="ECS_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5000</destination> <!-- 기존 포트 5000 사용 -->
<encoder class="co.elastic.logging.logback.EcsEncoder">
<!-- APM 서비스명 설정 -->
<serviceName>${elastic.apm.service-name:-anonichat-app}</serviceName>
<!-- APM 트레이스 정보 자동 포함 -->
<includeMarkers>true</includeMarkers>
<includeMdc>true</includeMdc>
</encoder>
<keepAliveDuration>5 minutes</keepAliveDuration>
</appender>
... 생략
<!-- APM 관련 로그 레벨 설정 -->
<logger name="co.elastic.apm" level="INFO" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
</configuration>
로그의 데이터 형식을 json 형식 → ecs 형식으로 변경
Elasticsearch에서 정의한 표준화된 로그 데이터 형식
# 일반 json 형식
{
"timestamp": "2025-06-15T14:30:15.123Z",
"level": "INFO",
"message": "사용자 로그인",
"userId": "user123",
"ip": "192.168.1.100",
"userAgent": "Chrome/91.0"
}
# ECS 형식
{
"@timestamp": "2025-06-15T14:30:15.123Z",
"log": {
"level": "INFO"
},
"message": "사용자 로그인",
"user": {
"id": "user123"
},
"client": {
"ip": "192.168.1.100"
},
"user_agent": {
"original": "Chrome/91.0"
},
"service": {
"name": "anonichat-app"
},
"trace": {
"id": "abc123"
},
"transaction": {
"id": "def456"
}
}
일반 json 형식보다 확장성, 호환성이 좋은 ECS로 로그 형식을 변경했다.
| 항목 | ElastAlert | Logstash Alerting | Kibana Watcher |
|---|---|---|---|
| 설명 | Elasticsearch 기반 조건 탐지 및 알림 전송 | Logstash 처리 파이프라인에서 조건 매칭 시 알림 전송 | Kibana 내 Watch 정의를 통한 모니터링 및 Alert |
| 설정방식 | YAML 설정 파일 |
Logstash 파이프라인 설정 파일 | Kibana GUI 또는 JSON DSL |
| 장점 | - 간단한 설정 - ES 쿼리 직접 사용 - 오픈소스 |
- 로그 수집과 알림을 동시에 처리 - 중간 저장 없이 실시간 처리 |
- UI 기반 설정 - ELK 통합 경험 우수 - 시각화 및 경보 연동 용이 |
| 단점 | - Python 기반 설치 필요 - 독립 실행 필요 - 대량 데이터 시 성능 저하 |
- 알림 기능이 제한적 - 조건 로직 복잡 시 관리 어려움 |
- X-Pack Gold 이상 필요 - 고급 조건 설정 시 DSL 복잡 - 외부 연동 제약 / 유료 |
| 기술요건 | Python 2.7/3.x, 독립 실행 | Logstash filter + output 설정 | Elasticsearch Watcher (X-Pack 포함) |
| 활용 예 | 보안 이벤트 탐지, 이상 로그 패턴 경보 | 수집 즉시 Slack 메시지 전송 | 특정 지표가 임계치 초과 시 Email 전송 |
Logstash는 현재 모두 구현되어 있는 상태이고 설정파일에 몇 줄만 추가하면 알림 설정을 마칠 수 있다.
설정 → 통합 → 앱 → 앱 추가 접속
incoming webhooks 설치

logstash.conf 수정input {
beats {
port => 5044
}
tcp {
port => 5000
codec => json_lines
type => "main_log"
}
}
filter {
if [type] == "main_log" {
if [log.level] in ["ERROR", "INFO", "FATAL"] { # INFO는 현재 기능 테스트를 위해 넣음
mutate {
add_field => { "alert_needed" => "true" }
}
}
}
}
output {
# 5000번 포트로 오는 모든 알림은 `main_log로 들어와 elastic에 저장`
if [type] == "main_log" {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "main_log"
}
}
######################## 추가 부분 ##########################
# ["ERROR", "INFO", "FATAL"] 에 해당 하는 알림은 슬렉으로 output 처리
if [alert_needed] == "true" { # error 레벨 발생 시 알려줌
http {
url => "https://hooks.slack.com/services/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" # output 플러그인 사용
http_method => "post" # Slack Webhook URL로 `POST` 요청 전송.
content_type => "application/json"
format => "json"
mapping => { # 메시지는 `%{}` 문법으로 필드 값을 문자열에 삽입:
"text" =>
"🚨 *AnoniChat 에러 발생!*```
레벨: %{[log.level]}
시간: %{[@timestamp]}
스레드: %{[process.thread.name]}
로거: %{[log.logger]}
메시지: %{message}
Trace ID: %{[trace.id]}
서비스: %{[service.name]}
```"
"channel" => "#anonichat-error-log"
}
}
}
##########################################################
}
docker-compose restart logstash
@GetMapping(GlobalURL.MAIN_URL)
public ModelAndView mainView() {
log.info("[MainController Log] mainView 접속 TEST");
List<TestEntity> list = testRepository.findAll();
log.info("[MainController Log] test_table data: {}", list);
return new ModelAndView("main");
}

이후 logstash.conf에서 INFO를 빼고 error 레벨만 알람이 울리도록 조정을 다시 해줬다.