아무거나

[Spring Actuator] Example 1편 - Custom Endpoint 생성 본문

Java/Spring

[Spring Actuator] Example 1편 - Custom Endpoint 생성

전봉근 2023. 11. 26. 02:00
반응형

소스코드는: https://github.com/bkjeon1614/java-example-code/tree/develop/bkjeon-mybatis-codebase 를 참고하시면 됩니다.

 

개념

  • Actuator 은 상태정보를 Hateoas(헤이티오스) 를 사용하여 표시한다. (ex: /actuator)
    • Hateoas(헤이티오스) 란
      • 서버가 클라이언트에게 하이퍼 미디어를 통해 정보를 동적으로 제공 (API 에서 리소스에 대해 어떠한 행동을 할 수 있는지 URL 을 전달하여 클라이언트가 참고하고 사용할 수 있도록 하며 해당 리소스의 상태에 따라 링크 정보가 바뀌며 동적으로 리소스를 구성)
  • Actuator Lib (의존성 라이브러리)
    • core lib 는 micrometer 를 사용하고 있다. (중요)
    • micrometer 를 spring boot 에서 bean 을 등록하기 위한 코드들은 spring-boot-actuator-autoconfigure 를 확인

의존성 추가

[build.gradle]

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

설정

Custom Endpoint 생성

  • actuator 접근 시 기본 제공되는 endpoints 말고 추가하는 방법
    • 공식페이지에 Endpoints 쪽을 보면 종류를 확인할 수 있다. 참고
    • 필요한 종류
      • beans: 등록된 bean 목록 제공
      • caches: cache 사용중이라면 cache 관련 정보 제공
      • conditions: spring auto configuration 에 의해 bean 으로 등록된 것과 그렇지 않은 것의 상세 이유를 제공
      • health: application 구동중인지, 연동되는 다른 서비스가 구동중인지에 대한 여부 제공
      • info: application 의 대략적인 정보
      • metrics: cpu, mem, thread count 등 모니터링용 메트릭 정보
      • logger: 현재 로거의 설정 확인 및 실시간 로그 레벨 변경 제공
      • quartz: quartz 라는 스케쥴링 관련 라이브러리를 사용하고 있다면 해당 정보를 상세히 제공
    • 설정방법 (설정한다고 무조건 되는것은 아니다 공식문서를 참고해보면 특정 필요 조건들이 존재하는 경우도 있다.)
      • Endpoint 활성화 설정
        [application.yml]
        management:
          endpoint:
            health:
              enabled: true
            beans:
              enabled: false
            caches:
              enabled: true
            heapdump:
              enabled: true                                    
        
      • 노출설정
        [application.yml]
        management:
          endpoint:
            health:
              enabled: true
            beans:
              enabled: false
            caches:
              enabled: true
            heapdump:
              enabled: true  
          endpoints:
            web:
              exposure:
                include:  # 다 할꺼면 "*" 로 하면 된다. 또한 exclude 가 우선순위가 높다. (단, 아무데서나 접근이 불가능하게 Security 를 설정하자)
                  - caches
                  - heapdump 
                  - health
                exclude:
                  - beans
        
    • 설정 후 /actuator 로 접속하면 각 endpoint 들을 확인할 수 있다.
    • Endpoint Cache 적용
      [application.yml]
      management:
        endpoint:
          health:
            enabled: true
            cache:
              time-to-live: 1d  # 1m: 1분, 1d: 1일 등 직관적으로 정할 수 있다.
          beans:
            enabled: false
          caches:
            enabled: true
          heapdump:
            enabled: true  
        
      ...
      
    • CORS 허용
      [application.yml]
      management:
        endpoint:
          web:
            cors:
              allowed-origins: http://www.test.com, http://test2.com
              allowed-methods: GET
      
  • Custom Endpoint 생성
    • 공식가이드
      • https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.implementing-custom
    • application 에서 참조하는 라이브러리 이름과 버전 정보를 응답으로 내보내는 Custom Endpoint 를 생성하는 예제
      • 샘플코드
        [ApplicationLibInfoEndpoint.java]
        import com.example.bkjeon.dto.actuator.LibarayInfo;
        import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
        import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
        
        import java.util.Arrays;
        import java.util.List;
        
        // @Endpoint(id = "applicationLibInfo")
        // @JmxEndpoint(id = "applicationLibInfo")
        @WebEndpoint(id = "applicationLibInfo") // web 만 열어준다.
        public class ApplicationLibInfoEndpoint {
        
            @ReadOperation
            public List<LibarayInfo> getLibraryInfo() {
                LibarayInfo libarayInfo = LibarayInfo.builder()
                        .name("bkjeon")
                        .version("1.0.0")
                        .build();
        
                LibarayInfo libarayInfo2 = LibarayInfo.builder()
                        .name("bkjeon2")
                        .version("2.0.0")
                        .build();
        
                return Arrays.asList(libarayInfo, libarayInfo2);
            }
        
        }
        
        [LibarayInfo.java]
        import lombok.AllArgsConstructor;
        import lombok.Builder;
        import lombok.Getter;
        import lombok.NoArgsConstructor;
        
        @Getter
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        public class LibarayInfo {
        
            private String name;
            private String version;
        
        }
        
        [ApplicationLibInfoEndpoint.java]
        // ApplicationLibInfoEndpoint 자체적으로는 bean 등록을 하지 않으므로 @Component 추가 또는 bean 등록을 해준다.
        import com.example.bkjeon.base.services.api.v1.actuator.ApplicationLibInfoEndpoint;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        @Configuration
        public class ApplicationLibInfoEndpointConfig {
        
            @Bean
            public ApplicationLibInfoEndpoint applicationLibInfoEndpoint() {
                return new ApplicationLibInfoEndpoint();
            }
        
        }
        
        [application.yml]
        ...
        endpoints:
          web:
            exposure:
              include: health, heapdump, info, metrics, prometheus, retries, applicationLibInfo  # applicationLibInfo 를 추가해준다.
              ...  
        
      • 생성 후 /actuator 로 접속하면 _links 하위에 applicationLibInfo 관련 endpoint 가 추가된걸 확인할 수 있다. 추가된 href 로 접속하면 결과가 표시된다.
        [
            {
                "name": "bkjeon",
                "version": "1.0.0"
            },
            {
                "name": "bkjeon2",
                "version": "2.0.0"
            }
        ]
        
    • 상기 생성된 Endpoint 에 Querystring을 받아서 조건에 맞게 표시하는 방법
      • 코드
        [ApplicationLibInfoEndpoint.java]
        import com.example.bkjeon.dto.actuator.LibarayInfo;
        import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
        import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
        import org.springframework.lang.Nullable;
        
        import java.util.Arrays;
        import java.util.List;
        import java.util.stream.Collectors;
        
        @Endpoint(id = "applicationLibInfo")
        public class ApplicationLibInfoEndpoint {
        
            @ReadOperation
            public List<LibarayInfo> getLibraryInfo(@Nullable String name, boolean includeVersion) {
                LibarayInfo libarayInfo = LibarayInfo.builder()
                        .name("bkjeon")
                        .version("1.0.0")
                        .build();
        
                LibarayInfo libarayInfo2 = LibarayInfo.builder()
                        .name("bkjeon2")
                        .version("2.0.0")
                        .build();
        
                List<LibarayInfo> libarayInfoList =  Arrays.asList(libarayInfo, libarayInfo2);
        
                if (name != null) {
                    libarayInfoList = libarayInfoList.stream()
                            .filter(lib -> lib.getName().equals(name))
                            .collect(Collectors.toList());
                }
        
                if (!includeVersion) {
                    libarayInfoList = libarayInfoList.stream()
                            .map(lib -> LibarayInfo.builder().name(lib.getName()).build())
                            .collect(Collectors.toList());
                }
        
                return libarayInfoList;
            }
        
        }    
        
      • /actuator/applicationLibInfo?includeVersion=true&name=bkjeon 와 같이 파라미터를 전송하면 확인할 수 있다.
    • http body 정보를 파라미터로 전달하는 방법
      • 샘플코드
        [ApplicationLibInfoEndpoint.java]
        @Slf4j
        @Endpoint(id = "applicationLibInfo")
        public class ApplicationLibInfoEndpoint {
        
            // 여기서는 복합객체를 갖고있는 @RequestBody 와 같은 방식을 지원하지 않아서 하기와 같은 방법으로 진행
            @WriteOperation
            public void changeSomething(String name, boolean enableSomething) {
                log.info("name: {}, enableSomething: {} ", name, enableSomething);
            }          
        
            ...
        
        }
        
      • 파라미터값들을 json 으로 전송하여 확인하자.
    • path parameter 정보를 전달하는 방법
      • 샘플코드
        [ApplicationLibInfoEndpoint.java]
        ...
        
        // 단일 path 값
        @ReadOperation
        public String getPathParameter(@Selector String path) {
            return "path: " + path;
        }   
        
        // 여러개의 path 값
        @ReadOperation
        public String getPathParameters(@Selector(match = Selector.Match.ALL_REMAINING) String[] paths) {
            return "pathList: " + Arrays.asList(paths);
        }   
        
      • 단일: /actuator/applicationLibInfo/1234 또는 여러개: /actuator/applicationLibInfo/1234/567 로 접근하여 path 에 찍히는 결과값을 확인할 수 있다.

생각해보면 결국 RestController 을 만들면 되는거지 않나? 라고 생각하게 되는데 그렇게 된다면 prometheus 와 같은 actuator 와 호환이 되는 여러 라이브러리와 연동이 될 수 없다. 즉, actuator 가 일종의 인터페이스 역할이므로 타 라이브러리와의 연동을 위해 actuator 를 이용하는걸 권장한다.

 

참고

  • https://semtul79.tistory.com/
  • https://docs.spring.io/
  • https://www.inflearn.com/course/spring-boot-actuator-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0/dashboard
반응형
Comments