Java
최종 수정: 2026. 1. 16.
Java 계측
Java 애플리케이션에 OpenTelemetry를 적용하는 방법을 안내합니다.
개요
Java는 OpenTelemetry에서 가장 성숙한 지원을 제공합니다.
계측 옵션
| 방식 | 설명 | 코드 변경 |
|---|---|---|
| APM Agent (eBPF) | 커널 레벨 자동 감지 | 불필요 |
| OTel Java Agent | JVM 에이전트 자동 계측 | 불필요 |
| SDK 수동 계측 | 코드에 직접 추가 | 필요 |
자동 계측 (권장)
OTel Operator 사용
가장 쉬운 방법은 OTel Operator를 통한 자동 주입입니다.
Deployment 설정
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-java-app
namespace: production
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "skuber-observability/otel-instrumentation"
spec:
containers:
- name: app
image: my-java-app:1.0.0
ports:
- containerPort: 8080지원 프레임워크
자동으로 계측되는 프레임워크:
| 카테고리 | 프레임워크 |
|---|---|
| 웹 | Spring Web MVC, Spring WebFlux, JAX-RS, Servlet |
| DB | JDBC, Hibernate, JPA, MyBatis |
| HTTP 클라이언트 | OkHttp, Apache HttpClient, WebClient |
| 메시징 | Kafka, RabbitMQ, JMS |
| gRPC | gRPC Java |
| 캐시 | Redis (Jedis, Lettuce), Memcached |
Java Agent 직접 사용
Kubernetes 외부 환경이나 더 세밀한 제어가 필요한 경우:
FROM openjdk:17-slim
# OTel Java Agent 다운로드
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar /opt/opentelemetry-javaagent.jar
COPY target/myapp.jar /app/myapp.jar
ENV JAVA_TOOL_OPTIONS="-javaagent:/opt/opentelemetry-javaagent.jar"
ENV OTEL_SERVICE_NAME="my-java-app"
ENV OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4317"
CMD ["java", "-jar", "/app/myapp.jar"]수동 계측
비즈니스 로직에 커스텀 스팬을 추가하려면 수동 계측을 사용합니다.
의존성 추가
Maven
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.35.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>Gradle
implementation platform('io.opentelemetry:opentelemetry-bom:1.35.0')
implementation 'io.opentelemetry:opentelemetry-api'
implementation 'io.opentelemetry:opentelemetry-sdk'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'SDK 초기화
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.ResourceAttributes;
public class OtelConfig {
public static OpenTelemetry initOpenTelemetry() {
// 리소스 정의
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(ResourceAttributes.SERVICE_NAME, "order-service")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.build());
// OTLP Exporter 설정
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("http://otel-collector:4317")
.build();
// Tracer Provider 설정
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setResource(resource)
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();
// OpenTelemetry SDK 빌드
OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();
// 종료 시 정리
Runtime.getRuntime().addShutdownHook(new Thread(tracerProvider::close));
return openTelemetry;
}
}트레이스 생성
기본 스팬 생성
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
public class OrderService {
private final Tracer tracer;
public OrderService(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("order-service", "1.0.0");
}
public Order createOrder(OrderRequest request) {
// 스팬 시작
Span span = tracer.spanBuilder("createOrder")
.setAttribute("order.customer_id", request.getCustomerId())
.setAttribute("order.items_count", request.getItems().size())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 비즈니스 로직
Order order = processOrder(request);
// 결과 속성 추가
span.setAttribute("order.id", order.getId());
span.setAttribute("order.total", order.getTotal());
return order;
} catch (Exception e) {
// 에러 기록
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
}중첩 스팬
public Order processOrder(OrderRequest request) {
Span span = tracer.spanBuilder("processOrder").startSpan();
try (Scope scope = span.makeCurrent()) {
// 하위 스팬은 자동으로 부모와 연결됨
validateInventory(request.getItems());
calculatePricing(request);
saveOrder(request);
return createOrderResponse();
} finally {
span.end();
}
}
private void validateInventory(List<Item> items) {
Span span = tracer.spanBuilder("validateInventory").startSpan();
try (Scope scope = span.makeCurrent()) {
// 재고 확인 로직
} finally {
span.end();
}
}메트릭 생성
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
public class OrderMetrics {
private final LongCounter ordersCreated;
private final LongCounter ordersFailed;
public OrderMetrics(OpenTelemetry openTelemetry) {
Meter meter = openTelemetry.getMeter("order-service", "1.0.0");
this.ordersCreated = meter.counterBuilder("orders.created")
.setDescription("Number of orders created")
.build();
this.ordersFailed = meter.counterBuilder("orders.failed")
.setDescription("Number of failed orders")
.build();
}
public void recordOrderCreated(String customerId) {
ordersCreated.add(1, Attributes.of(
AttributeKey.stringKey("customer_id"), customerId
));
}
public void recordOrderFailed(String reason) {
ordersFailed.add(1, Attributes.of(
AttributeKey.stringKey("reason"), reason
));
}
}Spring Boot 통합
Spring Boot Starter
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>application.yml 설정
otel:
service:
name: order-service
exporter:
otlp:
endpoint: http://otel-collector:4317
traces:
exporter: otlp
metrics:
exporter: otlp
logs:
exporter: otlp어노테이션 기반 계측
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
@Service
public class OrderService {
@WithSpan("createOrder")
public Order createOrder(
@SpanAttribute("customer.id") String customerId,
@SpanAttribute("items.count") int itemCount) {
// 자동으로 스팬이 생성됨
return processOrder(customerId, itemCount);
}
}컨텍스트 전파
HTTP 클라이언트
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
// RestTemplate에 컨텍스트 전파
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
TextMapSetter<HttpRequest> setter = (carrier, key, value) ->
carrier.getHeaders().set(key, value);
GlobalOpenTelemetry.getPropagators()
.getTextMapPropagator()
.inject(Context.current(), request, setter);
return execution.execute(request, body);
});환경 변수 설정
Kubernetes에서 환경 변수로 설정:
env:
- name: OTEL_SERVICE_NAME
value: "order-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector:4317"
- name: OTEL_TRACES_SAMPLER
value: "parentbased_traceidratio"
- name: OTEL_TRACES_SAMPLER_ARG
value: "0.1" # 10% 샘플링
- name: OTEL_RESOURCE_ATTRIBUTES
valueFrom:
fieldRef:
fieldPath: metadata.labels['app']트러블슈팅
트레이스가 수집되지 않음
- Exporter 엔드포인트 확인
- 네트워크 연결 확인
- 로그에서 에러 확인:
-Dotel.javaagent.debug=true
스팬이 연결되지 않음
- 컨텍스트 전파 확인
Scope사용 확인- 비동기 코드에서 컨텍스트 전달 확인
다음 단계
- 자동 계측 - OTel Operator 상세 설정
- 트레이스 - 트레이스 분석 방법
- 서비스 - 서비스 성능 모니터링