
이 글에서는 ogger container → Filebeat → Elasticsearch → Grafana 흐름으로 로그를 수집하고 시각화하는 과정을 정리한다. 로그 생성기는 파일에 JSON 로그를 기록하고, Filebeat는 해당 파일을 감시해 Elasticsearch로 전송한다. Grafana는 Elasticsearch를 데이터소스로 등록해 로그를 조회하고 시각화한다. 목적은 파일 기반 로그 수집 구조의 기본 동작을 이해하는 데 있다.

아래 구성은 Elasticsearch, Filebeat, 로그 생성기, Grafana를 함께 실행한다.
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
filebeat:
image: docker.elastic.co/beats/filebeat:8.13.4
container_name: filebeat
user: root
depends_on:
- elasticsearch
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- applogs:/var/log/app:ro
command: ["--strict.perms=false"]
loggen:
image: alpine:3.20
container_name: loggen
volumes:
- applogs:/var/log/app
command: >
sh -c '
mkdir -p /var/log/app;
i=0;
while true; do
i=$$((i+1));
level="INFO";
if [ $$((i % 10)) -eq 0 ]; then level="ERROR"; fi;
ts=$$(date -u +"%Y-%m-%dT%H:%M:%SZ");
echo "{\"@timestamp\":\"$$ts\",\"service\":\"app\",\"level\":\"$$level\",\"message\":\"hello $$i\",\"status\":$$((200 + (i % 5))) }" >> /var/log/app/app.log;
sleep 1;
done
'
grafana:
image: grafana/grafana-oss:11.1.5
container_name: grafana
ports:
- "3000:3000"
depends_on:
- elasticsearch
volumes:
- grafana-storage:/var/lib/grafana
volumes:
esdata:
applogs:
grafana-storage:
loggen 컨테이너는 1초마다 JSON 로그를 생성한다. filebeat와 loggen은 applogs 볼륨을 공유하며, Filebeat는 해당 경로의 로그 파일을 감시한다.
filebeat.inputs:
- type: filestream
enabled: true
paths:
- /var/log/app/*.log
processors:
- decode_json_fields:
fields: ["message"]
target: "json"
overwrite_keys: true
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
filestream은 파일 변경을 감지해 신규 라인을 읽는다. decode_json_fields는 로그 한 줄을 JSON으로 파싱해 json.* 필드로 저장한다. 이후 Elasticsearch로 전송된다.
loggen이 /var/log/app/app.log에 JSON 로그를 기록한다.각 단계는 실제 명령어로 확인할 수 있다.
인덱스 생성 여부 확인
curl -s "http://localhost:9200/_cat/indices?v".ds-filebeat-* 인덱스가 존재하고 docs.count 값이 증가하고 있다면 전송은 정상이다.
문서 색인 확인
curl -s "http://localhost:9200/filebeat-*/_search?size=1&sort=@timestamp:desc"응답의 _source에 json.level, json.message, json.status 필드가 포함되어 있다면 JSON 파싱도 정상이다.
로그 파일 생성 확인
docker exec -it loggen sh ls -l /var/log/app파일 크기가 증가하고 있다면 로그 생성기는 정상 동작 중이다.
Elasticsearch는 Lucene 검색 문법을 기반으로 데이터를 조회한다. 기본 형태는 필드:값이며, 특정 필드에 원하는 값을 지정하여 문서를 필터링한다.
* `json.level:ERROR`
* `json.status:500 AND json.service:payment`
* `json.message:timeout`
Lucene은 AND, OR, NOT과 같은 논리 연산자뿐 아니라 범위 검색 문법도 지원한다. 예를 들어 json.status:[400 TO 499]는 400번대 상태 코드를 가지는 로그를 조회한다. 이러한 문법을 조합하면 단순한 로그 조회를 넘어, 조건에 따른 세밀한 필터링이 가능하다.
쿼리를 작성할 때는 실제 색인된 필드명을 정확히 확인해야 한다. Filebeat의 decode_json_fields 설정에 따라 json.level, json.message와 같이 prefix가 붙을 수 있으므로, Grafana의 Fields 목록을 기준으로 필드 구조를 확인한 뒤 작성해야 한다.
Grafana에서 Elasticsearch를 데이터소스로 등록한다.
http://elasticsearch:9200.ds-filebeat-* 또는 filebeat-*@timestamp연결이 정상이라면 Explore 화면에서 로그를 조회할 수 있다.
