들어가기 전: 이 글은 [인프런] 에서 '김영한님의 스프링 부트 - 핵심 원리와 활용'을 수강하고 일부를 요약한 글입니다.
개발자가 애플리케이션에서 지표를 수집해서 이를 마이크로미터가 제공한느 표준방법에 따라 등록하면 actuator 에서 확인할 수 있다.
@AutoConfiguration
을 통해 자동으로 등록해준다.{
"names": [
"application.ready.time",
"application.started.time",
"disk.free",
"disk.total",
"hikaricp.connections",
"hikaricp.connections.acquire",
"hikaricp.connections.active",
"hikaricp.connections.idle",
"hikaricp.connections.max",
"hikaricp.connections.usage",
"http.server.requests",
"http.server.requests.active",
"jdbc.connections.active",
"jdbc.connections.idle",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.memory.used",
"jvm.memory.max",
"logback.events",
...
]
}
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 131172848
}
],
"availableTags": [
{
"tag": "area",
"values": [
"heap",
"nonheap"
]
},
{
"tag": "id",
"values": [
"G1 Survivor Space",
"Compressed Class Space",
"Metaspace",
"CodeCache",
"G1 Old Gen",
"G1 Eden Space"
]
}
]
}
위에서 다음과 같은 항목을 찾아볼 수 있다.
tag:area
, values[heap, nonheap]
tag:id
, values[G1 Survivor Space, ...]
그렇다면 이번에는 metrics 내부에 Tag 정보를 필터링해서 정보를 확인해보자.
tag=KEY:VALUE
tag 를 사용하면 힙 메모리, 힙이 아닌 메모리로 분류해서 데이터를 확인할 수 있다.
마이크로미터와 actuator 가 기본적으로 제공하는 다양한 메트릭을 확인해보자.
JVM 관련 메트릭을 제공한다. jvm.
으로 시작한다.
시스템 메트릭을 제공한다. system.
, process.
, disk.
으로 시작한다.
애플리케이션 시작 시간 메트릭을 제공한다.
스프링은 내부에 여러 초기화 단계가 있고 각 단계별로 내부에서 애플리케이션 이벤트를 발행한다.
스프링 MVC 컨트롤러가 처리하는 모든 요청을 다룬다.
메트릭 이름: http.server.requests
TAG 를 사용해서 다음 정보를 분류해서 확인할 수 있다.
커넥션 풀에 관한 메트릭을 확인할 수 있따.
jdbc.connections
으로 시작한다.logback.events: logback 로그에 대한 메트릭을 확인할 수 있다.
마이크로미터를 사용하면, 애플리케이션 내에서 비즈니스 모니터링에 알맞은 메트릭을 자유롭게 추가하고 actuator/metrics 에서 확인할 수 있다.
사용자가 정의할 수 있는 메트릭은 3가지로 다음과 같다.
@Counted
를 사용하면 result, exception, method, class 같은 다양한 tag 를 자동으로 적용한다.
@Counted
애노테이션 측정을 원하는 메서드에 적용한다.tag
에 method
를 기준으로 분류해서 적용한다.@slf4j
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Override
@Counted("my.order")
public void order() {
log.info("주문");
stock.decrementAndGet();
}
@Override
@Counted("my.order")
public void cancel() {
log.info("취소");
stock.incrementAndGet();
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
@Configuration
public class OrderConfig {
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
}
시간을 측정하는데 사용된다. 카운터와 유사한데, Timer 를 사용하면 실행시간도 함께 측정할 수 있다. 따라서, 다음과 같은 내용을 한 반에 측정할 수 있다.
seconds_count: 누적 실행 수
seconds_sum: 실행시간의 합
seconds_max: 최대 실행 시간(가장 오래 걸린 실행시간 (가장 오래걸린 실행시간). 내부에 타임윈도우라는 개념이 있어서 1 ~ 3분마다 최대 실행시간이 다시 계산된다.
애노테이션 적용
class 에 적용하면 해당 타임의 모든 public 메서드 타이머가 적용된다.
@Slf4j
@Timed("my.order")
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
sleep(500);
}
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
sleep(200);
}
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
@Configuration
public class ORderConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
참고: 카운터와 게이지를 구분할 때는 값이 감소할 수 있는가를 고민해보면 도움이 된다.
@Configuration
public class StockConfig {
@Bean
public MyStockMetric myStockMetric(
OrderServcie orderService,
MeterRegistry registry
) {
return new MyStockMetric(orderService.getStock(), registry);
}
@Slf4j
static class MyStockMetric {
private OrderService orderService;
private MeterREgistry registry;
public MyStockMetric(
OrderService orderService,
MeterRegistry registry
) {
this.orderService = orderService;
this.registry = registry;
}
@PostContstruct
public void init() {
Gauge.builder("my.stock", orderService, serivce -> {
log.info("stock gauge call");
return service.getStock().get();
}).registry(registry);
}
}
}
@Slf4j
@Configuration
public class StockConfig {
@Bean
public MeterBuilder stockSize(OrderService orderService) {
return registry -> Gauge.bulider("my.stock", orderService, service -> {
log.info("stock gauge call");
return service.getStock().get();
}).registry(registry):
}
}