제목, 태그, 카테고리로 검색

모든 글
약 4분 분량 프로젝트/오락가락

[트러블슈팅] Loki에서 스택트레이스 파싱 실패

목차

한 줄 요약

Exception 로그가 여러 줄로 분리되어 Grafana에서 스택트레이스 검색이 안 됐어요. Log4j2를 JSON 포맷으로 바꾸고 Promtail JSON 파이프라인을 설정해서 해결했어요.


증상

Grafana에서 NullPointerException을 검색하면 에러 메시지 한 줄만 나오고, 실제 스택트레이스는 보이지 않았어요. 스택트레이스의 각 줄이 별도의 로그 엔트리로 저장되고 있었거든요.

예를 들어 이런 로그가 있으면:

2024-01-01 10:00:00.123 ERROR [main] c.e.Service - 처리 실패
java.lang.NullPointerException: null
at com.example.Service.method(Service.java:10)
at com.example.Controller.handle(Controller.java:20)

Promtail이 줄 단위로 파싱해서 첫 줄, 둘째 줄, 셋째 줄이 각각 별개의 로그 엔트리가 돼요. “NullPointerException”을 검색하면 둘째 줄만 나오는데, 그 로그에 대한 컨텍스트(어떤 서비스에서, 어떤 요청에 의해)가 전혀 없어요.

환경

  • Grafana Loki + Promtail
  • Spring Boot + Log4j2
  • Docker Compose 단일 서버 구성

해결: Log4j2 JSON 포맷 + Promtail 파이프라인

핵심은 스택트레이스를 한 줄로 만드는 거였어요. JSON 포맷으로 바꾸면 스택트레이스가 message 필드 안에 이스케이프된 문자열로 들어가니, Promtail 입장에서는 한 줄이 돼요.

1. Log4j2 JSON Layout 적용

Log4j2의 JsonLayout을 사용해서 로그를 JSON으로 출력하게 변경했어요.

결과 JSON:

2. Promtail JSON 파이프라인 설정

Promtail이 JSON을 파싱해서 level, logger 등을 Loki 레이블로 추출하도록 설정했어요.

3. 환경별 로그 레벨 분리

운영 환경에서는 Kafka, Redis 내부 로그를 WARN 이상만 남기도록 설정했어요. 이런 라이브러리 로그가 Loki 용량을 불필요하게 차지하는 걸 방지하기 위해서예요.

4. 비동기 로깅

로그 출력이 애플리케이션 스레드를 블로킹하지 않도록 AsyncLogger를 적용했어요.

5. ERROR 로그 별도 파일 관리


Grafana 검색 비교

Before (텍스트 로그)

{job="orakgaraki"} |= "NullPointerException"
→ 스택트레이스 없이 에러 메시지만 표시

After (JSON 로그)

{job="orakgaraki"} | json | level="ERROR" | line_format "{{.message}}"
→ 전체 스택트레이스 포함, 구조화 쿼리 가능

traceId 기반 요청 추적도 가능해졌어요:

{job="orakgaraki"} | json | traceId="3fa414eac33375e9"

결과

지표개선 전개선 후
스택트레이스 검색불가능가능
에러 분석 시간서버 SSH 접속 필요Grafana에서 즉시
로그 필터링텍스트 매칭만구조화 쿼리
요청 추적수동traceId로 자동 추적

참고 자료

Summary

Resolved stacktrace search failures in Grafana caused by multi-line log splitting. Switching Log4j2 to JSON format and configuring Promtail JSON pipeline fixed the issue.


Symptoms

Searching for NullPointerException in Grafana returned only the error message line, not the actual stacktrace. Each stacktrace line was stored as a separate log entry.

For example, this log:

2024-01-01 10:00:00.123 ERROR [main] c.e.Service - Processing failed
java.lang.NullPointerException: null
at com.example.Service.method(Service.java:10)
at com.example.Controller.handle(Controller.java:20)

Promtail parsed line-by-line, making each line a separate log entry. Searching “NullPointerException” returned only the second line with no context about which service or request caused it.

Environment

  • Grafana Loki + Promtail
  • Spring Boot + Log4j2
  • Docker Compose single-server setup

Solution: Log4j2 JSON Format + Promtail Pipeline

The key was making stacktraces single-line. JSON format embeds stacktraces as escaped strings within the message field, appearing as one line to Promtail.

1. Log4j2 JSON Layout

Switched to Log4j2’s JsonLayout for JSON-formatted log output.

Result JSON:

2. Promtail JSON Pipeline

Configured Promtail to parse JSON and extract level, logger, etc. as Loki labels.

3. Per-Environment Log Level Separation

Production limits Kafka and Redis internal logs to WARN+, preventing library logs from consuming Loki storage.

4. Async Logging

AsyncLogger prevents log output from blocking application threads.

5. Separate ERROR Log Files


Grafana Search Comparison

Before (text logs)

{job="orakgaraki"} |= "NullPointerException"
→ Error message only, no stacktrace

After (JSON logs)

{job="orakgaraki"} | json | level="ERROR" | line_format "{{.message}}"
→ Full stacktrace included, structured queries possible

traceId-based request tracing became possible:

{job="orakgaraki"} | json | traceId="3fa414eac33375e9"

Results

MetricBeforeAfter
Stacktrace searchImpossiblePossible
Error analysis timeServer SSH requiredInstant in Grafana
Log filteringText matching onlyStructured queries
Request tracingManualAutomatic via traceId

References

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글