마지막 업데이트: 2026-01-25
이 가이드는 observability-core 모듈을 기존 Spring Boot 프로젝트에 적용하는 방법을 설명합니다. OpenTelemetry Java Agent와 AOP 기반 자동 추적을 중심으로 작성되었습니다.
- 개요 및 버전 정보
- 프로젝트에 적용하기
- Java Agent 환경변수 상세 설명
- AOP 기반 자동 추적 설정
- JDBC 쿼리 추적
- 커스텀 메트릭
- 커스텀 트레이스
- 로그-트레이스 연동
- Grafana 활용
- 환경별 설정
- 프로덕션 고려사항
- 트러블슈팅
- 참고 자료
이 프로젝트는 OpenTelemetry Java Agent 방식의 자동 계측을 사용합니다. 기존의 라이브러리 방식과 달리, 애플리케이션 코드 변경 없이 JVM 수준에서 자동으로 HTTP, JDBC, 메시지 큐 등을 추적합니다.
추가로 Spring AOP를 이용한 내부 메서드 자동 추적(TracingAspect)으로, Service/Repository 계층의 모든 public 메서드를 자동으로 span으로 생성합니다.
| 컴포넌트 | 버전 |
|---|---|
| Spring Boot | 3.4.1 |
| Kotlin | 1.9.22 |
| OpenTelemetry Java Agent | 2.11.0 |
| OpenTelemetry Instrumentation Annotations | 2.12.0 |
| Micrometer | 1.14.2 |
| Micrometer Tracing | 1.4.1 |
| Loki4j | 1.5.2 |
| Grafana | 11.4.0 |
| Prometheus | 2.55.1 |
| Loki | 3.3.2 |
| Tempo | 2.6.1 |
| PostgreSQL | 16 |
프로젝트의 build.gradle.kts에 observability-core 의존성을 추가합니다.
dependencies {
// 옵션 A: 로컬 모듈 (개발 중)
implementation(project(":observability-core"))
// 옵션 B: Maven Central 배포 라이브러리 (향후)
// implementation("com.example:observability-core:0.0.1-SNAPSHOT")
}또는 libs.versions.toml에서 정의된 번들을 사용할 수 있습니다:
dependencies {
implementation(libs.bundles.observabilityAgent)
}주의: opentelemetry-exporter-otlp 의존성은 포함하지 않습니다. Java Agent가 자체 exporter를 제공하므로 포함하면 중복 전송이 발생합니다.
Docker 이미지에 OpenTelemetry Java Agent를 다운로드하고 실행 시 로드합니다.
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# 헬스 체크용 curl 설치
RUN apk add --no-cache curl
# OpenTelemetry Java Agent 다운로드
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.11.0/opentelemetry-javaagent.jar /app/opentelemetry-javaagent.jar
# 빌드된 JAR 파일 복사
COPY your-app/build/libs/your-app-*.jar app.jar
# Java 옵션 설정
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 포트 노출
EXPOSE 8080
# 헬스 체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# Java Agent와 함께 실행
ENTRYPOINT ["sh", "-c", "java -javaagent:/app/opentelemetry-javaagent.jar $JAVA_OPTS -jar app.jar"]핵심 포인트:
-javaagent:/app/opentelemetry-javaagent.jar플래그로 Java Agent 활성화- JAR 파일 경로:
/app/opentelemetry-javaagent.jar - 환경변수를 통해 Agent 동작 제어
docker-compose.yml에서 애플리케이션 서비스에 OpenTelemetry 환경변수를 설정합니다.
services:
your-app:
build:
context: ../
dockerfile: your-app/Dockerfile
container_name: your-app
ports:
- "8080:8080"
environment:
# 기본 Spring Boot 설정
- SPRING_PROFILES_ACTIVE=docker
- SPRING_APPLICATION_NAME=your-app
# OpenTelemetry Java Agent 설정
- OTEL_SERVICE_NAME=your-app
- OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4318
- OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- OTEL_TRACES_EXPORTER=otlp
- OTEL_METRICS_EXPORTER=none # Prometheus가 메트릭 담당
- OTEL_LOGS_EXPORTER=none # Loki가 로그 담당
# 자동 계측 활성화
- OTEL_INSTRUMENTATION_SPRING_WEBMVC_ENABLED=true
- OTEL_INSTRUMENTATION_JDBC_ENABLED=true
- OTEL_INSTRUMENTATION_LOGBACK_MDC_ADD_BAGGAGE=true
- OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED=true
# Loki 설정
- LOKI_URL=http://loki:3100
- APP_NAME=your-app
- ENV=docker
# 데이터베이스 설정 (PostgreSQL 예시)
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/mydb
- SPRING_DATASOURCE_USERNAME=app
- SPRING_DATASOURCE_PASSWORD=app
depends_on:
tempo:
condition: service_healthy
loki:
condition: service_healthy
prometheus:
condition: service_healthy프로젝트의 src/main/resources/application.yml에 기본 설정을 추가합니다.
spring:
application:
name: your-app
jpa:
hibernate:
ddl-auto: validate
show-sql: false
# AOP 기반 내부 메서드 자동 추적 활성화
observability:
tracing:
aop:
enabled: true
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
# 메트릭 태그 설정
metrics:
tags:
application: ${spring.application.name}
# 로깅 설정
logging:
level:
root: INFO
com.example: DEBUG
io.opentelemetry: INFO
pattern:
level: "%5p [${spring.application.name:},%X{trace_id:-},%X{span_id:-}]"docker 프로필용 설정 (application-docker.yml):
spring:
application:
name: your-app
jpa:
hibernate:
ddl-auto: create-drop # Docker 환경에서는 자동 생성
# AOP 기반 추적 활성화
observability:
tracing:
aop:
enabled: true
logging:
level:
root: INFO
com.example: DEBUG로깅 설정 파일을 생성하거나 업데이트합니다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- observability-core의 로깅 설정 포함 -->
<include resource="logback-spring-observability.xml"/>
<!-- 환경 변수 설정 -->
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="your-app"/>
<springProperty scope="context" name="ENV" source="spring.profiles.active" defaultValue="local"/>
<springProperty scope="context" name="LOKI_URL" source="loki.url" defaultValue="http://localhost:3100"/>
<!-- 로컬 프로파일: 콘솔만 사용 -->
<springProfile name="!docker">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- Docker 프로파일: 콘솔 + Loki -->
<springProfile name="docker">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="LOKI"/>
</root>
</springProfile>
</configuration>주의: logback-spring-observability.xml은 observability-core 모듈에 포함되어 있으며, Java Agent가 MDC에 trace_id와 span_id를 snake_case 형식으로 자동 추가합니다.
OpenTelemetry Java Agent는 환경변수를 통해 동작을 제어합니다. 각 변수의 역할을 설명합니다.
- 설명: OpenTelemetry에서 인식하는 애플리케이션 이름
- 값:
your-app - 영향: Tempo에서 service 필터, Grafana 대시보드에 표시
- 예시:
OTEL_SERVICE_NAME=payment-service
- 설명: Trace 데이터를 보낼 OTLP 수신자 주소
- 값:
http://tempo:4318(Docker) 또는http://localhost:4318(로컬) - 형식:
http://{host}:{port}(TLS는 https 사용) - 예시:
# 로컬 개발 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # Docker Compose OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4318 # Kubernetes OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo.observability.svc.cluster.local:4318
- 설명: OTLP 프로토콜 선택
- 값:
http/protobuf(권장) 또는grpc - 추천:
http/protobuf는 HTTP/1.1 기반으로 더 안정적 - 예시:
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- 설명: Trace 데이터 내보내기 활성화
- 값:
otlp - 예시:
OTEL_TRACES_EXPORTER=otlp
- 설명: Metric 데이터 내보내기 제어
- 값:
none(Prometheus가 대신 처리) - 이유: Micrometer를 통해 Prometheus가 메트릭 수집
- 예시:
OTEL_METRICS_EXPORTER=none
- 설명: Log 데이터 내보내기 제어
- 값:
none(Loki4j가 대신 처리) - 이유: Logback Appender (Loki4j)가 로그 전송
- 예시:
OTEL_LOGS_EXPORTER=none
- 설명: Spring Web MVC 자동 계측 활성화
- 값:
true(기본값) - 효과: HTTP 요청/응답 자동 추적
- 예시:
OTEL_INSTRUMENTATION_SPRING_WEBMVC_ENABLED=true
- 설명: JDBC 쿼리 자동 계측 활성화
- 값:
true(기본값) - 효과: SQL 쿼리 span 자동 생성
- 예시:
OTEL_INSTRUMENTATION_JDBC_ENABLED=true
- 설명: Logback MDC에 trace_id, span_id 자동 추가
- 값:
true - 효과: 모든 로그에
trace_id,span_id포함 - 주의:
true로 설정하지 않으면 로그-트레이스 연동 불가 - 예시:
OTEL_INSTRUMENTATION_LOGBACK_MDC_ADD_BAGGAGE=true
- 설명: Java Agent 글로벌 자동 설정 활성화
- 값:
true(권장) - 효과: 환경변수 기반 자동 설정 적용
- 예시:
OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED=true
- 설명: OTLP 내보내기 타임아웃
- 값:
10s(기본값) - 권장:
- 로컬/내부 네트워크:
10s - 외부 네트워크:
30s - 매우 빠른 응답 필요:
5s
- 로컬/내부 네트워크:
- 예시:
OTEL_EXPORTER_OTLP_TIMEOUT=30s
- 설명: OTLP 요청에 추가할 HTTP 헤더
- 값:
key1=value1,key2=value2형식 - 사용 사례: 인증 토큰, API 키
- 예시:
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer+token123,X-API-Key=secret
- 설명: 샘플링 전략
- 값:
always_on,always_off,traceidratio,parentbased_* - 기본값:
parentbased_always_on - 예시:
# 1% 샘플링 OTEL_TRACES_SAMPLER=traceidratio OTEL_TRACES_SAMPLER_ARG=0.01
- 설명: 리소스 속성 (모든 span에 추가)
- 값:
key1=value1,key2=value2형식 - 예시:
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.2.3
Spring AOP를 이용한 자동 추적은 Service, Repository 등의 모든 public 메서드를 자동으로 span으로 변환합니다.
요청
↓
[Java Agent] HTTP 요청 자동 추적 (루트 span 생성)
↓
[TracingAspect] Service.getUser() 호출 감지
↓
span 자동 생성: "ServiceName.methodName"
↓
[TracingAspect] Service.processData() 호출 감지
↓
span 자동 생성: "ServiceName.methodName" (자식 span)
↓
[Java Agent] JDBC 쿼리 자동 추적
↓
[결과] Tempo에 전체 span 트리 전송
application.yml에서 observability.tracing.aop.enabled=true로 설정합니다.
observability:
tracing:
aop:
enabled: true프로파일별 설정:
# application-dev.yml
observability:
tracing:
aop:
enabled: true
# application-prod.yml
observability:
tracing:
aop:
enabled: true # 프로덕션에서도 활성화하되, 샘플링으로 부하 제어현재 TracingAspect는 다음을 자동 추적합니다:
com.example패키지 하위의 모든 public 메서드@Service,@Repository,@Component등 모든 Spring Bean
제외할 패키지:
com.example.observability(무한 루프 방지)
커스터마이징 예시 (TracingAspect.kt 수정):
// 특정 패키지만 추적하려면
@Pointcut("within(com.example.service..*) || within(com.example.repository..*)")
fun applicationPackage() {}
// 특정 어노테이션만 추적하려면
@Pointcut("@within(org.springframework.stereotype.Service)")
fun serviceLayer() {}특정 환경에서 AOP 추적을 끄려면:
observability:
tracing:
aop:
enabled: false또는 환경변수:
OBSERVABILITY_TRACING_AOP_ENABLED=falseAOP 추적이 활성화되면 Tempo에서 다음과 같은 span 구조를 볼 수 있습니다:
GET /api/users 450ms (Agent: HTTP)
├─ UserService.getAllUsers 120ms (AOP)
│ ├─ UserRepository.findAll 80ms (AOP)
│ │ └─ SELECT * FROM users 75ms (Agent: JDBC)
│ └─ UserService.enrichUsers 35ms (AOP)
└─ UserService.formatResponse 15ms (AOP)
OpenTelemetry Java Agent는 JDBC 쿼리를 자동으로 추적하고, 각 쿼리를 별도의 span으로 생성합니다.
docker-compose.yml에서 다음을 설정합니다:
environment:
- OTEL_INSTRUMENTATION_JDBC_ENABLED=true- Driver: JDBC 호환 모든 드라이버 (PostgreSQL, MySQL, Oracle 등)
- 작업: SELECT, INSERT, UPDATE, DELETE, CREATE TABLE 등
- 정보: 쿼리 문자열, 실행 시간, 성공/실패 상태
Tempo에서 쿼리 span은 다음과 같이 표시됩니다:
Database Query 75ms
├─ db.system: postgresql
├─ db.name: mydb
├─ db.user: app
├─ db.statement: SELECT * FROM users WHERE id = ?
└─ duration: 75ms
# docker-compose.yml
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: mydb
sample-app:
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/mydb
- SPRING_DATASOURCE_USERNAME=app
- SPRING_DATASOURCE_PASSWORD=app
- OTEL_INSTRUMENTATION_JDBC_ENABLED=trueTempo에서 트레이스를 열면 각 JDBC span에서 쿼리를 확인할 수 있습니다:
Details 탭 → db.statement 필드 → 전체 SQL 확인
Micrometer를 통해 비즈니스 메트릭을 수집하고 Prometheus에 내보냅니다.
이벤트 발생 횟수를 기록합니다.
import io.micrometer.core.instrument.MeterRegistry
import org.springframework.stereotype.Service
@Service
class OrderService(private val meterRegistry: MeterRegistry) {
fun placeOrder(order: Order) {
// ... 비즈니스 로직 ...
// 카운터 증가
meterRegistry.counter(
"orders.placed",
"status", order.status,
"region", order.region
).increment()
}
fun cancelOrder(orderId: String) {
meterRegistry.counter(
"orders.cancelled",
"reason", "user_request"
).increment()
}
}Prometheus 쿼리:
rate(orders_placed_total[5m])
Grafana 대시보드: 시계열 그래프로 시간대별 주문 수 표시
현재 값을 측정합니다 (예: 큐 크기, 활성 연결).
@Service
class QueueService(private val meterRegistry: MeterRegistry) {
private val queue = ConcurrentLinkedQueue<Task>()
@PostConstruct
fun init() {
// 큐 크기를 게이지로 등록
meterRegistry.gauge(
"queue.size",
queue,
{ it.size }
)
}
fun addTask(task: Task) {
queue.add(task)
// 게이지가 자동으로 업데이트됨
}
}Prometheus 쿼리:
queue_size
작업 실행 시간을 측정합니다.
@Service
class ReportService(private val meterRegistry: MeterRegistry) {
fun generateReport(type: String): Report {
return meterRegistry.timer(
"report.generation.duration",
"type", type
).recordCallable {
// 비즈니스 로직
val result = computeReport(type)
result
}
}
}Prometheus 쿼리 (P95 지연시간):
histogram_quantile(0.95, rate(report_generation_duration_seconds_bucket[5m]))
메서드 수준에서 자동으로 메트릭을 수집합니다.
import io.micrometer.core.annotation.Timed
import org.springframework.stereotype.Service
@Service
class UserService {
@Timed(value = "user.fetch", description = "사용자 조회 시간")
fun fetchUser(userId: String): User {
// 자동으로 메트릭 수집
return userRepository.findById(userId)
}
}span을 수동으로 생성하거나, @Observed 어노테이션으로 자동 생성할 수 있습니다.
가장 간단한 방법으로, 자동으로 span을 생성하고 속성을 추가합니다.
import io.micrometer.observation.annotation.Observed
import org.springframework.stereotype.Service
@Service
class OrderProcessingService {
@Observed(
name = "order.processing",
contextualName = "process-order"
)
fun processOrder(order: Order): ProcessResult {
// 자동으로 span 생성
// 실행 시간 자동 기록
// 로그에 traceId 자동 추가
val validation = validateOrder(order)
val payment = capturePayment(order)
return ProcessResult(validation, payment)
}
@Observed(contextualName = "validate-order")
private fun validateOrder(order: Order): ValidationResult {
// 자식 span 자동 생성
return ValidationResult(...)
}
@Observed(contextualName = "capture-payment")
private fun capturePayment(order: Order): PaymentResult {
// 또 다른 자식 span
return PaymentResult(...)
}
}Tempo에서 결과:
process-order (300ms)
├─ validate-order (50ms)
└─ capture-payment (200ms)
더 세밀한 제어가 필요한 경우, Tracer를 직접 사용합니다.
import io.micrometer.tracing.Tracer
import org.springframework.stereotype.Service
@Service
class DataProcessingService(private val tracer: Tracer) {
fun processLargeDataset() {
// 현재 span에 태그 추가
tracer.currentSpan()?.tag("dataset.size", "1000000")
for ((batchIndex, batch) in batches.withIndex()) {
// 새로운 span 생성
val span = tracer.nextSpan().name("process-batch-$batchIndex")
try {
span.start().use {
processBatch(batch)
}
} finally {
span.end()
}
}
}
}span에 메타데이터를 추가합니다.
import io.micrometer.observation.Observation
import org.springframework.stereotype.Service
@Service
class TransactionService(private val tracer: Tracer) {
fun executeTransaction(transId: String, amount: BigDecimal) {
val observation = Observation.createNotStarted(
"transaction.execution",
Observation.Context()
)
observation.observe {
val span = tracer.currentSpan()
// 태그 추가 (검색 가능)
span?.tag("transaction.id", transId)
span?.tag("transaction.amount", amount.toString())
span?.tag("transaction.currency", "USD")
try {
val result = processTransaction(transId, amount)
span?.tag("transaction.status", "success")
} catch (e: Exception) {
span?.tag("transaction.status", "failed")
span?.tag("transaction.error", e.message)
throw e
}
}
}
}Tempo에서 검색:
transaction.status=successtransaction.amount=100.00
OpenTelemetry Java Agent는 자동으로 trace_id와 span_id를 Logback의 MDC에 추가하므로, 모든 로그에 traceId가 포함됩니다.
docker-compose.yml에서 다음을 설정하면:
environment:
- OTEL_INSTRUMENTATION_LOGBACK_MDC_ADD_BAGGAGE=true그러면 모든 로그에 자동으로 trace_id와 span_id가 추가됩니다.
logback-spring-observability.xml에서:
<!-- Java Agent는 snake_case 사용: trace_id, span_id -->
<property name="TRACE_PATTERN" value="%X{trace_id:-},%X{span_id:-}"/>
<property name="LOG_PATTERN" value="%d{ISO8601} [%thread] %-5level %logger{36} - [${TRACE_PATTERN}] %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>2024-01-25 10:30:45.123 [http-nio-8080-exec-1] INFO com.example.service.OrderService - [b6c89963909788a33fddf391627d1808,6e1110d5294cbcd7] 주문 처리 시작
2024-01-25 10:30:45.156 [http-nio-8080-exec-1] DEBUG com.example.repository.OrderRepository - [b6c89963909788a33fddf391627d1808,3f2e8c1a9d7f5b2c] 데이터베이스 쿼리 실행
Loki 쿼리로 특정 traceId의 모든 로그를 조회합니다:
# 특정 traceId의 모든 로그
{app="my-service"} | json | traceId="b6c89963909788a33fddf391627d1808"
# ERROR 로그만 필터
{level="ERROR"} | json | traceId="b6c89963909788a33fddf391627d1808"
# 특정 서비스들의 로그
{app=~"order-service|payment-service"} | json | traceId=~"b6c89963.*"
| 서비스 | URL | 계정 |
|---|---|---|
| Grafana | http://localhost:3000 | admin / admin |
| Prometheus | http://localhost:9090 | - |
| Tempo | http://localhost:3200 | - |
| Loki | http://localhost:3100 | - |
주문 처리 시간 (P95)
- Grafana 접속 → Dashboard → New Panel
- Data Source: Prometheus
- 쿼리:
histogram_quantile(0.95, rate(order_processing_duration_seconds_bucket[5m])) - Panel Type: Time series
- Unit: Seconds
애플리케이션 에러 로그
- Data Source: Loki
- 쿼리:
{app="my-service",level="ERROR"} | json - Panel Type: Logs
특정 트레이스 상세 보기
- Tempo UI 접속 (http://localhost:3200)
- Search → Service 선택 → 추적 조건 입력
- 원하는 트레이스 클릭 → 전체 스팬 구조 확인
Loki 로그에서 traceId를 클릭하면 자동으로 Tempo의 해당 트레이스로 이동합니다.
설정: docker/grafana/provisioning/datasources/datasources.yml 참조
# application.yml
observability:
tracing:
aop:
enabled: true
management:
tracing:
sampling:
probability: 1.0 # 100% 추적
logging:
level:
root: INFO
com.example: DEBUG# application-staging.yml
observability:
tracing:
aop:
enabled: true
management:
tracing:
sampling:
probability: 0.1 # 10% 샘플링
logging:
level:
root: WARN
com.example: INFO# application-prod.yml
observability:
tracing:
aop:
enabled: true # 활성화하되 샘플링으로 제어
management:
tracing:
sampling:
probability: 0.01 # 1% 샘플링
logging:
level:
root: WARN
com.example: WARN
io.micrometer: WARN# 명령어
./gradlew bootRun --args='--spring.profiles.active=staging'
# 환경변수
export SPRING_PROFILES_ACTIVE=prod
./gradlew bootRun
# Docker
docker run -e SPRING_PROFILES_ACTIVE=prod your-app:latest프로덕션에서는 100% 추적이 성능을 저하시키고 비용을 증가시킵니다.
management:
tracing:
sampling:
probability: 0.01 # 1%에서 시작권장 샘플링 비율:
- 개발: 1.0 (100%)
- 스테이징: 0.1 (10%)
- 프로덕션: 0.01 ~ 0.001 (1% ~ 0.1%)
logging:
level:
root: WARN # 경고 이상만
com.example: INFO # 애플리케이션 INFO만
org.springframework: WARN
io.micrometer: WARN<!-- logback-spring.xml -->
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
<http>
<url>${LOKI_URL}/loki/api/v1/push</url>
<batchMaxItems>500</batchMaxItems> <!-- 500개 로그마다 전송 -->
<batchTimeoutMs>5000</batchTimeoutMs> <!-- 또는 5초마다 -->
</http>
</appender>docker-compose.yml에서:
prometheus:
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
loki:
deploy:
resources:
limits:
memory: 512M
tempo:
deploy:
resources:
limits:
memory: 2G# docker-compose.yml - prometheus
prometheus:
command:
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=50GB'# grafana.ini
[security]
admin_password = ${GRAFANA_PASSWORD} # 강력한 비밀번호
allow_sign_up = false
[auth.anonymous]
enabled = false증상: 요청을 보냈지만 Tempo에서 trace를 찾을 수 없음
해결 방법:
-
Java Agent 활성화 확인:
docker logs your-app | grep -i "opentelemetry"
-
환경변수 확인:
docker exec your-app env | grep OTEL
예상 출력:
OTEL_SERVICE_NAME=your-app OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4318 OTEL_TRACES_EXPORTER=otlp -
Tempo 연결 확인:
docker exec your-app curl -v http://tempo:4318/v1/traces -
샘플링 비율 확인 (매우 낮으면 trace가 드물 수 있음):
management: tracing: sampling: probability: 1.0 # 테스트 중에는 100%로 설정
증상: 로그에 trace_id와 span_id가 표시되지 않음
해결 방법:
-
환경변수 확인:
OTEL_INSTRUMENTATION_LOGBACK_MDC_ADD_BAGGAGE=true
-
Logback 패턴 확인 (snake_case 사용):
<!-- logback-spring-observability.xml --> <pattern>... %X{trace_id} %X{span_id} ...</pattern>
주의: Micrometer는 camelCase (
traceId)를 사용하지만, Agent는 snake_case (trace_id)를 사용합니다. -
콘솔 출력으로 확인:
curl http://localhost:8080/api/test docker logs your-app | tail -20
증상: Service/Repository 메서드가 span으로 추적되지 않음
해결 방법:
-
AOP 활성화 확인:
observability: tracing: aop: enabled: true
-
클래스가 Spring Bean인지 확인:
@Service // 또는 @Repository, @Component class MyService { fun myMethod() { } }
-
public 메서드인지 확인:
@Service class MyService { fun publicMethod() { } // public (암묵적) private fun privateMethod() { } // private는 추적 안 됨 }
-
패키지 위치 확인:
com.example패키지 내에 있어야 함com.example.observability패키지는 제외됨
증상: SQL 쿼리가 별도의 span으로 생성되지 않음
해결 방법:
-
환경변수 확인:
OTEL_INSTRUMENTATION_JDBC_ENABLED=true
-
JDBC Driver가 호환되는지 확인:
- PostgreSQL: 지원
- MySQL: 지원
- Oracle: 지원
-
실제 쿼리가 실행되는지 확인:
curl http://localhost:8080/api/query-data docker logs postgres
증상: /actuator/prometheus 엔드포인트가 있지만 메트릭이 없음
해결 방법:
-
엔드포인트 활성화 확인:
management: endpoints: web: exposure: include: health,info,prometheus,metrics
-
메트릭 수집 확인:
curl http://localhost:8080/actuator/prometheus | head -50 -
Prometheus 스크래핑 확인:
curl http://localhost:9090/api/v1/targets
증상: 100% 샘플링으로 인한 성능 저하
해결 방법:
-
샘플링 비율 감소:
management: tracing: sampling: probability: 0.1 # 또는 더 낮게
-
로깅 레벨 상향:
logging: level: root: WARN io.micrometer: WARN
-
AOP 비활성화 (필요시):
observability: tracing: aop: enabled: false
- ARCHITECTURE.md - 아키텍처 상세
- Dockerfile - Docker 설정 예시
- docker-compose.yml - 전체 스택 설정
- gradle/libs.versions.toml - 모든 의존성 버전
- observability-core - 재사용 가능한 모듈
- sample-app - 완전한 구현 예시