Development/Spring

Spring Boot Prometheus Converter 406 Not Acceptable

반응형

개요

Spring Boot Project에 actuator를 적용한 뒤, prometheus micrometer를 적용했을 때 WebMvcConfigurationSupport 를 customizing 하게 되면 발생하는 문제에 대한 해결책이다.

해당 문제를 보려면 일단 Spring Boot Project를 생성하자. (Spring boot Initializer를 사용하면 편리하다)

그 뒤, 아래의 dependency를 추가한다.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Micrometer Prometheus registry  -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>

application.properties에 아래 내용을 추가한다(추가하지 않으면 /actuator/prometheus가 exposure되지 않아서 접근할 수 없다)

management.endpoints.web.exposure.include=*

그렇게 되면 아래의 path로 prometheus exporter 를 접근할 수 있게 된다(기본 8080 포트 사용 시)

localhost:8080/actuator/prometheus

문제

자, 이 상태로 무언가 WebMvcConfigurationSupport 의 ObjectMapper를 수정하고 싶을 때(default serialize, deserialize 등의 행동을 바꾸고자 할 때 간혹 사용된다) 아래와 같은 method를 override를 하게 된다.

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Override
    public void configureMessageConverters(List converters) {

        final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();

        // ObjectMapper Settings(JODA)
        objectMapper.registerModule(new JodaModule());
        objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.
                WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        converter.setObjectMapper(objectMapper);

        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);

        // Add settings to converter, builder
        converters.add(converter);
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }

}

위와 같은 override를 해서 Spring 내부의 MessageConverter 를 수정했을 경우 API 등은 잘 동작할 수 있다.

하지만, prometheus exporter에서 사용하는 Content Type은 text/plain 을 사용하게 된다.

백문이 불여일견, 위 Config class와 함께 prometheus exporter 페이지를 접근해보자(localhost:8080/actuator/prometheus)

image-20190323145710182

위와 같이 Http 406 Not Acceptable 과 함께 나오지 않는다.

해결방법

  1. MappingJackson2HttpMessageConverter 에 Content Type을 지정한다

JSON은 정상적으로 나오니, Content Type에 TEXT/PLIAN 을 추가하면 되지 않을까?

converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));

 

엇 무언가 나온다. 근데 포맷팅이 이상하다

TEXT/HTML의 내용을 JSON형식으로 마셜링을 해버린것이다..;

이렇게 된 경우, prometheus server에서 이를 수집할 수가 없다.(formatting이 맞지 않기 때문)

사용하지 않도록 하자 ..

  1. StringHttpMessageConverter 를 converter에 추가한다
converters.add(new StringHttpMessageConverter());

를 아까 생성한 Config 클래스에 추가를 해주자.

 

추가가 잘 되면 드디어! 정상적인 format으로 나오게 된다.

그럼 StringHttpMessageConverter 는 뭘하는놈일까

내부를 열어보면

    public StringHttpMessageConverter(Charset defaultCharset) {
        super(defaultCharset, new MediaType[]{MediaType.TEXT_PLAIN, MediaType.ALL});
        this.writeAcceptCharset = true;
    }

기본적으로 TEXT_PLAIN 과 모든 MediaType을 가지고 생성이 된다.

    protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
        if (this.writeAcceptCharset) {
            outputMessage.getHeaders().setAcceptCharset(this.getAcceptedCharsets());
        }

        Charset charset = this.getContentTypeCharset(outputMessage.getHeaders().getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

그리고 해당 함수에서 Response로 값을 write 하는 부분을 찾아보게 되면 위와 같이 String으로 convert를 해서 내보내는 것을 알 수 있다.

    protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
        Charset charset = this.getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return StreamUtils.copyToString(inputMessage.getBody(), charset);
    }

읽을땐 역시 반대로 String으로 convert를 한 뒤 String으로 전달을 해준다.

반응형

'Development > Spring' 카테고리의 다른 글

Spring Redis Template Transaction  (2) 2021.09.09
Spring RequestContextHolder  (8) 2020.07.05
Spring Password Encoder  (4) 2019.04.06
Dispatcher Servlet  (0) 2019.04.06
[JAVA/Spring] BeanUtils 관련  (2) 2018.07.20