오늘역시 저번시간에 이어 Spring boot Actuator 를 좀 더 살펴보기로 하겠다. 그 전에 좋은 소식이 하나 있다. java9의 포함예정이였던 jigsaw 프로젝트 jsr376 (JPMS) 가 드디어 한달간의 리뷰를 마치고 통과하였다. 이번 리뷰에는 저번보다 두 회사가 늘어 25개 회사 중 24개 회사가 찬성표를 던졌고 한 회사(Red Hat)이 기권을 하였다. 이제 java9에 jigsaw 프로젝트가 포함되니 슬슬 공부좀 해야 겠다. 조만간 기회가 된다면 한번 포스팅을 해보도록 하자.

CounterService

metrics에 우리가 원하는 커스텀한 정보를 넣을 수 없을까? 만약 타 API와 호출하는 부분을 카운팅을 하고 싶다고 가정해보자. 얼마나 호출하는지 알고 싶다면 우리는 Spring boot에서 제공해주는 CounterService 인터페이스를 사용하면 된다. 참고로 언어는 코틀린으로 만들었다. 그리 어려운 문법을 사용한건 아니고 대충 봐도 무슨 내용인지 다 알 듯 싶다.

@RestController
class HelloController(val counterService: CounterService) {

  @GetMapping("/increment")
  fun increment(): String {
    counterService.increment("hello.foo.bar")
    return "hello";
  }

  @GetMapping("/decrement")
  fun decrement(): String {
    counterService.decrement("hello.foo.bar")
    return "hello";
  }

  @GetMapping("/reset")
  fun reset(): String {
    counterService.reset("hello.foo.bar")
    return "hello";
  }
}

우리는 위와 같이 increment() 메서드를 사용해서 카운트를 증가 시킬 수 있다. 만약 감소 시키고 싶다면 decrement() 메서드를 사용하면 되고 리셋을 하고 싶다면 reset() 메서드를 사용하면 된다.

한번 호출 해보자.

http localhost:8080/increment

http localhost:9090/application/metrics
{
    "classes": 6290,
    "classes.loaded": 6290,
    "classes.unloaded": 0,
    "counter.hello.foo.bar": 1
    ...
}

위와 같이 increment 엔드포인트를 호출한 후에 다시 metrics 을 호출해보면 counter.hello.foo.bar의 값이 증가했다. 이렇게 우리는 아주 손쉽게 metrics에 정보를 추가할 수 있다.

GaugeService

CounterService 인터페이스 경우에는 카운트만 증가 시키는 용도로 사용한다. 카운트만 증가 하는 것 말고 어떠한 게이지를 기록하고 싶다면 GaugeService 인터페이스를 사용하면 된다.

@RestController
class FooController(val gaugeService: GaugeService) {

  @GetMapping("/submit")
  fun submit(): String {
    val startTime = System.currentTimeMillis();
    Thread.sleep(100)
    gaugeService.submit("hello.foo.bar", (System.currentTimeMillis() - startTime).toDouble())
    return "submit"
  }
}

submit() 메서드를 사용해서 게이지의 값을 넣어주면 된다.

http localhost:8080/submit

http localhost:9090/application/metrics
{
    "classes": 6255,
    "classes.loaded": 6255,
    "classes.unloaded": 0,
    "counter.status.200.submit": 1,
    "gauge.hello.foo.bar": 105.0,
    "gauge.response.submit": 114.0
    ...
}

이렇게 특정한 게이지의 값 예를 들어 타 API 호출시 걸린 속도를 넣어 줘도 된다.

PublicMetrics

위의 CounterService 와 GaugeService으로 나타내기 어려운 것들도 존재 할지 모른다. 예를 들어 시스템의 사양이라던지 메모리라던지 기타 정보들을 나타내고 싶다면 PublicMetrics 인터페이스를 이용하면 된다.

@Component
class ControllerMetrics : PublicMetrics {
  override fun metrics(): List<Metric<*>> {
    return listOf(Metric("max.mem", Runtime.getRuntime().maxMemory()));
  };
}

만약 자상머신의 최대 메모리의 양을 구하고 메트릭으로 표현하고 싶다면 위와 같이 해주면된다. 간단하다.

http localhost:9090/application/metrics
{
    ...
    "threads.peak": 43,
    "threads.totalStarted": 46,
    "max.mem": 1908932608,
    "uptime": 30381
}

우리는 아주 쉽게 메트릭에 표현할 수 있다.

HealthIndicator

만약 특정한 곳에 상태 체크를 하고 싶다면 HealthIndicator 인터페이스를 이용하면 된다. 예를들어 외부와 연결된 서버라던지 DB라던지 혹은 외부 파일 기타 외부의 접근요소들의 상태 체크를 할 수 있다. Spring boot에서는 기존의 다양한 상태체크를 지원한다. DB, redis, Mail, Jms, 기타 다양한 Nosql 서버등이 자동설정에 포함되어 있다. 하지만 만약에 다른 타 외부 API의 상태를 체크 하고 싶다면 커스텀하게 구현해주면 된다.

@Component
class GithubHealth : HealthIndicator {
  override fun health(): Health {
    val restTemplate = RestTemplate()
    try {
      restTemplate.getForEntity("https://api.github.com", String::class.java);
      return Health.up().build()
    } catch (e: Exception) {
      return Health.down().build()
    }
  }
}

위와 같이 간단하게 HealthIndicator 인터페이스를 구현해주면 된다. 정상적일 경우에는 Health.up() 을 해주면 되고 비정상일 경우에는 Health.down() 메서드를 사용하면 된다. 한번 호출해보도록 하자.

http localhost:9090/application/health
{
    "diskSpace": {
        "free": 71309070336,
        "status": "UP",
        "threshold": 10485760,
        "total": 120137318400
    },
    "githubHealth": {
        "status": "UP"
    },
    "status": "UP"
}

githubHealth 라는 키에 해당 상태가 포함되어 있다. 만약 커스텀하게 더 많은 정보를 넣고 싶다면 아래와 같이 하면 된다.

Health.up().withDetail("statusCode", response.statusCode.value()).build()

그럼 다음과 같이 표현 된다.

{
    "diskSpace": {
        "free": 71307206656,
        "status": "UP",
        "threshold": 10485760,
        "total": 120137318400
    },
    "githubHealth": {
        "status": "UP",
        "statusCode": 200
    },
    "status": "UP"
}

HealthIndicator 인터페이스를 사용해도 되지만 좀 더 추상화된 AbstractHealthIndicator 클래스를 이용해도 된다.

@Component
class GithubHealth2 : AbstractHealthIndicator() {
  override fun doHealthCheck(builder: Health.Builder?) {
    val restTemplate = RestTemplate()
    val response = restTemplate.getForEntity("https://api.github.com", String::class.java);
    builder?.up()?.withDetail("statusCode", response.statusCode.value())
  }
}

해당 클래스를 사용하면 좀 더 깔끔해진다. try catch 문도 사라질 뿐더러 return 값도 필요 없다. 파라미터로 전달 된 Health.Builder를 이용해서 값을 넣으면 된다.

이들의 정보는 Spring shell을 이용해서 확인도 가능하다. 위의 정보보다는 없지만.. 하지만 spring shell은 spring boot 2.0 이후에는 지원하지 않으니 2.0 이전의 버전들만 사용가능하다. Spring shell 프로젝트도 나름 괜찮다고 생각했는데 없어지니 아쉽다.

오늘도 이렇게 좀 더 유용한 spring Actuator 를 살펴 봤다. 다음 시간에는 전 시간에 말한 trace를 영구적으로 저장해서 사용할 수 있도록 모색하며 개발을 해보자. 어제보다는 조금 유용한 정보이길 바라며 오늘은 이만.