반응형
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()));
}
}
반응형