Observability ใน Spring Boot 2026: OpenTelemetry, Distributed Tracing และคำถามสัมภาษณ์
เรียนรู้ observability ใน Spring Boot ด้วย OpenTelemetry และ Micrometer Tracing คู่มือการตั้งค่า distributed tracing, Observation API, การส่งออก OTLP และการเตรียมตัวสัมภาษณ์งานเทคนิค

Observability ใน Spring Boot รวม logging, metrics และ distributed tracing เข้าด้วยกันเป็นระบบที่เผยให้เห็นว่า request ไหลผ่าน microservices อย่างไร ตั้งแต่ Spring Boot 3 เป็นต้นมา framework นี้ได้นำ Micrometer Tracing มาใช้ (แทนที่ Spring Cloud Sleuth) และเปิดตัว Observation API ที่ให้จุด instrumentation เดียวสำหรับสร้างทั้ง metrics และ trace
Spring Boot แนะนำให้ใช้ Observation.observe() แทนการเรียก OpenTelemetry โดยตรง การเรียก instrumentation ครั้งเดียวจะสร้าง metrics ผ่าน Micrometer และ trace ผ่าน bridge ของ OpenTelemetry ช่วยลดการเขียนโค้ดซ้ำซ้อนและรับประกันการตั้งชื่อ tag ที่สอดคล้องกันในทุกสัญญาณ
Observation API เชื่อมต่อ Micrometer และ OpenTelemetry อย่างไร
Observation API ทำหน้าที่เป็น facade สำหรับทั้ง metrics และ tracing เมื่อโค้ดเรียก Observation.createNotStarted() Spring Boot จะส่ง observation ไปยัง handler ที่ลงทะเบียนไว้: MeterObservationHandler สำหรับ Micrometer metrics และ TracingObservationHandler สำหรับ distributed trace สถาปัตยกรรมนี้หมายความว่าการ instrument ครั้งเดียวสามารถส่งออกได้ทุกที่
Dependency bridge micrometer-tracing-bridge-otel เชื่อมต่อ Micrometer Tracing กับ SDK ของ OpenTelemetry Trace จะไหลผ่าน SdkTracerProvider ของ OpenTelemetry และส่งออกผ่าน OTLP ไปยัง backend เช่น Jaeger, Tempo หรือ collector ใดก็ได้ที่รองรับ OpenTelemetry
@Configuration
public class ObservabilityConfig {
@Bean
public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
return registry -> registry.observationConfig()
.observationHandler(new ObservationTextPublisher()); // Logs observations to console
}
}การตั้งค่าด้านบนลงทะเบียน ObservationHandler ที่บันทึก observation ทุกครั้งลง log ใน production handler ที่ถูกตั้งค่าอัตโนมัติจะส่งข้อมูลไปยัง Micrometer registry และ OpenTelemetry exporter โดยไม่ต้องเขียนโค้ดเพิ่มเติม
Dependency ที่จำเป็นสำหรับ Tracing ใน Spring Boot 3.4
Spring Boot 3.4 ต้องการ dependency ที่ชัดเจนสำหรับ distributed tracing spring-boot-starter-actuator ให้ Observation API แต่ tracing ต้องการ bridge ของ OpenTelemetry และ exporter
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>Artifact micrometer-tracing-bridge-otel เชื่อมต่อ Micrometer Tracing กับ API ของ OpenTelemetry opentelemetry-exporter-otlp ส่ง span ไปยัง collector โดยใช้โปรโตคอล OTLP ผ่าน HTTP หรือ gRPC Spring Boot จัดการการซิงค์เวอร์ชันผ่าน dependency management
การตั้งค่าการส่งออก OTLP ไปยัง Jaeger หรือ Tempo
Spring Boot ตั้งค่า OtlpHttpSpanExporter อัตโนมัติเมื่อ dependency OTLP พร้อมใช้งาน Exporter จะส่ง trace ไปยัง endpoint ที่ระบุใน application.yml
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 100% sampling สำหรับ dev ลดลงใน production
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
opentelemetry:
resource-attributes:
service.name: order-service
deployment.environment: stagingresource-attributes แนบ metadata กับทุก span Backend เช่น Grafana Tempo และ Jaeger ใช้ attribute เหล่านี้ในการจัดกลุ่ม trace ตาม service และ environment การตั้งค่า sampling.probability เป็น 1.0 จะจับทุก request ระหว่างการพัฒนา แต่ระบบ production มักจะ sample ระหว่าง 1% ถึง 10% เพื่อควบคุมต้นทุนการจัดเก็บ
การสร้าง Custom Observation ด้วย Tag แบบ Low และ High Cardinality
Observation รองรับ tag สองประเภท: low cardinality สำหรับ metrics (ค่าที่จำกัดเช่น HTTP method หรือ status code) และ high cardinality สำหรับ trace (ค่าที่ไม่จำกัดเช่น user ID หรือ request ID)
@Service
public class PaymentService {
private final ObservationRegistry observationRegistry;
public PaymentService(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
public PaymentResult processPayment(PaymentRequest request) {
return Observation.createNotStarted("payment.process", observationRegistry)
.lowCardinalityKeyValue("payment.method", request.getMethod().name()) // enum: CARD, BANK_TRANSFER
.lowCardinalityKeyValue("currency", request.getCurrency()) // bounded: USD, EUR, GBP
.highCardinalityKeyValue("payment.id", request.getPaymentId()) // unique per request
.highCardinalityKeyValue("customer.id", request.getCustomerId()) // high cardinality
.observe(() -> executePayment(request));
}
private PaymentResult executePayment(PaymentRequest request) {
// Payment gateway call
return new PaymentResult(true, "TXN-" + UUID.randomUUID());
}
}Tag แบบ low cardinality ปรากฏทั้งใน metrics และ trace Tag แบบ high cardinality ปรากฏเฉพาะใน trace เพราะ metrics ที่มีมิติไม่จำกัดจะทำให้พื้นที่จัดเก็บระเบิด การแยกแยะนี้ป้องกัน cardinality bomb ใน Prometheus ขณะที่รักษารายละเอียด trace ให้สมบูรณ์
ผู้สัมภาษณ์มักถามว่าทำไม tag บางตัวควรถูกยกเว้นจาก metrics คำตอบเกี่ยวข้องกับ cardinality: tag user ID บน metric สร้าง time series หนึ่งชุดต่อ user อาจเป็นล้าน series Backend monitoring มีปัญหากับ high cardinality นำไปสู่หน่วยความจำหมดและ query ช้า Trace จัดการ high cardinality ผ่าน sampling ทำให้เป็นที่ที่ถูกต้องสำหรับ identifier เฉพาะ request
พร้อมที่จะพิชิตการสัมภาษณ์ Spring Boot แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การส่งต่อ Trace Context ข้าม Async Boundary
Distributed tracing ต้องการการส่งต่อ context HTTP header นำ trace ID ระหว่าง service แต่การทำงานแบบ async เช่น method @Async หรือ chain ของ CompletableFuture สามารถสูญเสีย context ได้ถ้าไม่ได้ตั้งค่า
Spring Boot 3.4 ให้การส่งต่ออัตโนมัติสำหรับ reactive stream และ method @Async เมื่อตั้งค่า:
# application.yml
spring:
reactor:
context-propagation: auto
task:
execution:
propagate-context: trueสำหรับ bean TaskExecutor ที่กำหนดเอง ให้ wrap ด้วย ContextPropagatingTaskDecorator:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
executor.initialize();
return executor;
}
}Decorator คัดลอก Observation ปัจจุบันและ trace context ไปยัง thread async ถ้าไม่มี span ที่เริ่มใน task async จะไม่มี parent ทำให้ trace tree ขาด
การใช้ Annotation @Observed และ @NewSpan
Spring Boot 3.4 รองรับ observability แบบ declarative ผ่าน annotation เปิดใช้งานการประมวลผล annotation โดยเพิ่ม AspectJ weaver:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency># application.yml
management:
observations:
annotations:
enabled: true@Service
public class InventoryService {
@Observed(name = "inventory.check", contextualName = "checkStock")
public StockLevel checkStock(String productId) {
// Database query
return new StockLevel(productId, 42);
}
@NewSpan("inventory.reserve") // Creates a child span under the current trace
public ReservationResult reserveStock(String productId, int quantity) {
// Update inventory
return new ReservationResult(true, "RES-" + System.currentTimeMillis());
}
}@Observed สร้างทั้ง metric (timer) และ span @NewSpan สร้างเฉพาะ span มีประโยชน์เมื่อ operation มี metrics อยู่แล้วที่อื่น Annotation ทั้งสองจัดการการบันทึก exception และสถานะ span โดยอัตโนมัติ
Component ที่ถูก Instrument อัตโนมัติใน Spring Boot 3.4
Spring Boot 3.4 instrument component หลายตัวโดยอัตโนมัติโดยไม่ต้องเปลี่ยนโค้ด:
| Component | ชื่อ Observation | สิ่งที่จับได้ |
|---|---|---|
| Spring MVC | http.server.requests | Request path, method, status, exception |
| WebClient | http.client.requests | Outbound HTTP call |
| RestClient | http.client.requests | Outbound HTTP call (Spring 6.1+) |
| Spring Kafka | spring.kafka.listener | Consumer group, topic, partition |
| Spring Data JPA | spring.data.repository | Repository method, query time |
| Scheduled Tasks | spring.scheduling | Task name, execution time |
เอกสาร Spring Boot Actuator แสดงรายการ component ทั้งหมดที่ถูก instrument อัตโนมัติ Library ของบุคคลที่สามเช่น Datasource Micrometer เพิ่ม tracing query JDBC
การกรอง Observation เพื่อลด Noise
Health check, readiness probe และ static resource สร้าง noise ใน trace กรองออกโดยใช้ predicate:
@Configuration
public class ObservationFilterConfig {
@Bean
public ObservationPredicate noHealthChecks() {
return (name, context) -> !name.equals("http.server.requests")
|| !isHealthEndpoint(context);
}
private boolean isHealthEndpoint(Observation.Context context) {
if (context instanceof ServerRequestObservationContext http) {
String path = http.getCarrier().getRequestURI();
return path.startsWith("/actuator/health") || path.startsWith("/actuator/ready");
}
return false;
}
}หรือปิดการใช้งาน observation ตามชื่อในการตั้งค่า:
# application.yml
management:
observations:
enable:
spring.security: false # Disable Spring Security observations
http.server.requests.actuator: false # Custom predicate nameSampling ลดเปอร์เซ็นต์ของ trace ที่เก็บรวบรวมจากทุก endpoint Filtering ลบ endpoint เฉพาะทั้งหมด ใช้ filtering สำหรับ endpoint ที่ไม่เคยให้คุณค่าในการวินิจฉัย (health check, metrics scraping) ใช้ sampling เพื่อควบคุมต้นทุนขณะที่รักษาข้อมูลที่เป็นตัวแทน
การเชื่อมโยง Log กับ Trace ID
Spring Boot 3.4 เพิ่ม trace และ span ID ลงใน MDC (Mapped Diagnostic Context) โดยอัตโนมัติเมื่อ Micrometer Tracing ทำงาน Logback และ Log4j2 สามารถรวม ID เหล่านี้ในผลลัพธ์ log:
<!-- logback-spring.xml -->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>ด้วย pattern นี้ ทุกบรรทัด log จะมี trace ID การค้นหา log ด้วย trace ID จะคืนข้อความทั้งหมดจาก request เดียวข้ามทุก service เชื่อมโยง log กับ span ใน Jaeger หรือ Tempo
คำถามสัมภาษณ์ที่พบบ่อยเกี่ยวกับ Observability ใน Spring Boot
ผู้สัมภาษณ์ประเมินทั้งความเข้าใจแนวคิดและประสบการณ์จริงกับ observability คำถามเหล่านี้ปรากฏบ่อยในตำแหน่ง backend อาวุโส
ถาม: ความแตกต่างระหว่าง Micrometer Tracing และ OpenTelemetry คืออะไร?
Micrometer Tracing เป็น API ที่ไม่ผูกกับ vendor สำหรับ distributed tracing คล้ายกับที่ Micrometer abstract metrics OpenTelemetry เป็น implementation และ protocol เฉพาะ Spring Boot ใช้ Micrometer Tracing เป็น API และเชื่อมต่อกับ OpenTelemetry สำหรับการส่งออกผ่าน micrometer-tracing-bridge-otel การแบ่งชั้นนี้ช่วยให้แอปพลิเคชันเปลี่ยน backend ได้โดยไม่ต้องเปลี่ยนโค้ด
ถาม: ทำไม Spring แนะนำ Observation API มากกว่า instrumentation OpenTelemetry โดยตรง?
Observation API ให้จุด instrumentation เดียวที่สร้างทั้ง metrics และ trace การเรียก OpenTelemetry โดยตรงสร้างเฉพาะ trace การใช้ Observation.observe() สร้าง timer metric และ span จากโค้ดเดียวกัน ลดการซ้ำซ้อนและรับประกันการตั้งชื่อที่สอดคล้อง
ถาม: วิธี debug trace ที่แสดงช่องว่างระหว่าง span?
ช่องว่างบ่งบอกว่าขาด instrumentation หรือการส่งต่อ context เสีย ตรวจสอบว่าโค้ดใช้การทำงาน async โดยไม่มี ContextPropagatingTaskDecorator ยืนยันว่า HTTP client ถูก instrument (WebClient, RestClient หรือ RestTemplate ที่ wrap เอง) สำหรับ message queue ยืนยันว่า trace header ถูกส่งต่อผ่าน property ของ message
ถาม: เกิดอะไรขึ้นถ้าเพิ่ม tag แบบ high cardinality ใน metric?
ทุกค่า tag ที่ไม่ซ้ำจะสร้าง time series ใหม่ tag user ID ที่มีผู้ใช้หลายล้านคนจะสร้างหลายล้าน series ทำให้หน่วยความจำหมดใน Prometheus หรือ backend TSDB อื่น วิธีแก้คือใช้ highCardinalityKeyValue() แทน lowCardinalityKeyValue() ซึ่งเพิ่ม tag เฉพาะใน trace ที่คาดว่าจะมี high cardinality
การสร้าง Pipeline Distributed Tracing ด้วย Docker Compose
Stack observability ในเครื่องช่วยตรวจสอบ instrumentation ก่อน deploy ไป production ตัวอย่างนี้ใช้ OpenTelemetry Collector และ Jaeger:
# docker-compose.yml
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.102.0
command: ["--config", "/etc/otel-collector-config.yml"]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "4318:4318" # OTLP HTTP
- "4317:4317" # OTLP gRPC
jaeger:
image: jaegertracing/jaeger:2.3
ports:
- "16686:16686" # UI
environment:
- COLLECTOR_OTLP_ENABLED=true
app:
build: .
environment:
- MANAGEMENT_OTLP_TRACING_ENDPOINT=http://otel-collector:4318/v1/traces
depends_on:
- otel-collector# otel-collector-config.yml
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 1s
send_batch_size: 1024
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]Collector รับ span จากแอปพลิเคชัน Spring Boot และส่งต่อไปยัง Jaeger สถาปัตยกรรมนี้อนุญาตให้เพิ่ม exporter (Tempo, Zipkin, cloud backend) โดยไม่ต้องเปลี่ยนโค้ดแอปพลิเคชัน
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ประเด็นสำคัญสำหรับ Observability ใน Spring Boot
- Observation API รวม metrics และ tracing: การเรียก
Observation.observe()หนึ่งครั้งสร้าง timer metric และ span ขจัดการ instrument ซ้ำ - เพิ่ม
micrometer-tracing-bridge-otelและopentelemetry-exporter-otlpเพื่อเปิดใช้งานการส่งออก OTLP Spring Boot 3.4 ตั้งค่า HTTP exporter อัตโนมัติด้วย endpoint จากmanagement.otlp.tracing.endpoint - ใช้
lowCardinalityKeyValue()สำหรับมิติที่ปรากฏใน metrics (ค่าที่จำกัด) ใช้highCardinalityKeyValue()สำหรับข้อมูลเฉพาะ trace (user ID, request ID) - เปิดใช้งานการส่งต่อ context สำหรับโค้ด async ด้วย
spring.task.execution.propagate-context=trueและ wrap executor ที่กำหนดเองด้วยContextPropagatingTaskDecorator - กรอง endpoint ที่มี noise เช่น
/actuator/healthโดยใช้ beanObservationPredicateเพื่อให้ trace มุ่งเน้นที่การดำเนินงานทางธุรกิจ - Pattern log ควรรวม
%X{traceId}เพื่อเชื่อมโยงบรรทัด log กับ distributed trace ใน backend observability - คำถามสัมภาษณ์เกี่ยวกับ observability เน้นที่ cardinality ความล้มเหลวในการส่งต่อ context และความแตกต่างระหว่าง Observation API กับการใช้ OpenTelemetry โดยตรง
คุณหาบั๊กใน Spring Boot เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 19 กันยายน 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Spring Boot Actuator: การตรวจสอบบนโปรดักชันด้วย Micrometer และ Prometheus
คู่มือ Spring Boot Actuator ฉบับสมบูรณ์สำหรับการตรวจสอบบนโปรดักชัน ทั้งการตั้งค่า Micrometer, เมตริก Prometheus, endpoint ที่กำหนดเอง และการแจ้งเตือน

Spring Boot YAML vs Properties: เปรียบเทียบการตั้งค่าและคำถามสัมภาษณ์งาน 2026
คู่มือฉบับสมบูรณ์เปรียบเทียบรูปแบบการตั้งค่า YAML และ Properties ใน Spring Boot รวมถึงแนวทางปฏิบัติที่ดีที่สุดและคำถามสัมภาษณ์ที่พบบ่อย

Spring Boot Structured Logging ในปี 2026: Log JSON สำหรับ Production ด้วย Logback และ OpenTelemetry
คู่มือฉบับสมบูรณ์สำหรับ structured logging Spring Boot 4.x รองรับ JSON แบบ native, OpenTelemetry starter, MDC tracing และการรวมกับ ELK Stack สำหรับ observability ใน production