TroubleShooting

[prometheus] org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

에디개발자 2020. 12. 8. 07:00
반응형

 

Prometheus를 띄워봤으니 프로젝트에 적용해보자! 하던 중에 Exception이 어김없이 발생했습니다.  

정말 고마운 분의 글을 참조해서 문제를 해결하였습니다.

 

나를 닮았다고 한다...

 

 

증상

application에 actuator, prometheus 디펜던시를 추가하고 Security Endpoint도 적용하고 다 했는데 안되었습니다. 

actuator, health, info, metrics 는 다 되는데 prometheus만 안되는 현상이었습니다.

 

증상으로는 에러도 아니고 WARN 이네요 아래와 같습니다.

WARN org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver [AbstractHandlerExceptionResolver.java:199] - Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.lang.String] with preset Content-Type 'null']
content type이 null 이다! 라는 로그네요.

 

 

원인

 

Prometheus exporter의 Content Type은 text/plain 입니다. 그런데 Null로 되있어서 convert 도중에 실패했다고 경고문구가 나타나고 있습니다.

개인 프로젝트에서는 잘 돌아가는 것이 실제 적용하면 에러가 터진다. 슬프게도... 

 

위 참조글에서 찾아보니 WebMvcConfigurationSupport 상속받는 클래스에 문제가 있었습니다.

아래 소스가 원인이었습니다.

@Slf4j
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    ....

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.addMixIn(Object.class, DynamicFilterMixIn.class);
        mapper.setFilterProvider(new DynamicFilterProvider());
        converters.add(new MappingJackson2HttpMessageConverter(mapper));
    }


    ....

}
다른 API는 모두 정상 동작하는데 text/plain이 빠져있어서 문제 발생

WebMvc에서 기본적으로 사용하는 converter가 있는데 강제로 Override하여 값을 변경하여서 문제 발생하고 있습니다.

 

해결

StringHttpMessageConvert()를 추가합니다.

@Slf4j
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    ....

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.addMixIn(Object.class, DynamicFilterMixIn.class);
        mapper.setFilterProvider(new DynamicFilterProvider());
        converters.add(new MappingJackson2HttpMessageConverter(mapper));
        converters.add(new StringHttpMessageConverter());       // 추가
    }


    ....

}

 

StringHttpMessageConverter 클래스는 내부에서 text/plain을 설정합니다.

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

	....


	public StringHttpMessageConverter(Charset defaultCharset) {
		super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
	}
    
    
    ....
}    

 

호기심

그럼 WebMvcConfigurationSupport는 Override를 하지 않으면 내부에서 어떻게 동작하는지 궁금하네요.

 

  • addDefaultHttpMessageConverters() 
    • messageConverters 생성
  • addDefaultHttpMessageConverters()
    • messageConverters값을 configureMessageConverters()메서드
  • configureMessageConverters
    • messageConverters를 사용
관련소스는 밑을 참조해주세요!
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {

    // 문제의 그 메서드
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
	}

    // configureMessageConverters 메서드에 messageConverters 객체 주입
    protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}
    
    // 여기서 messageConverters 세팅
    protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(new StringHttpMessageConverter());
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new ResourceRegionHttpMessageConverter());
		try {
			messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Throwable ex) {
			// Ignore when no TransformerFactory implementation is available...
		}
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
		else if (jaxb2Present) {
			messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
		else if (gsonPresent) {
			messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
			messageConverters.add(new JsonbHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
		}
		if (jackson2CborPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
		}
	}

 

반응형