Actuactor 는 L7 체크 또는 애플리케이션 Health 체크에 도움이 되는 Spring Boot 자원이다.



1. 단순 Actuactor 예제


pom.xml 추가

<dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-actuator</artifactId>

    </dependency>


소스 추가


import org.springframework.boot.actuate.health.Health;

import org.springframework.boot.actuate.health.HealthIndicator;

import org.springframework.stereotype.Component;


@Component

public class HealthChecker implements HealthIndicator {


@Override

public Health health() {

    boolean isOk = check();

    if (!isOk) {

        return Health.down().withDetail("Error Code", 10000).build();

    }

    return Health.up().build();

}


public boolean check() {

return true;

}


}


성공시(check() 리턴이 true) 결과 리턴 값

{"status": "UP"}


그리고, curl로 확인하면 다음과 같다.

$ calhost:8080/health

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

X-Application-Context: application

Content-Type: application/json;charset=UTF-8

Transfer-Encoding: chunked

Date: Tue, 26 May 2015 15:18:38 GMT


{"status":"UP"}



실패시(check() 리턴이 false) 결과 리턴값

{

"status": "DOWN"}


그리고, curl로 확인하면 다음과 같다.

$ curl -i http://localhost:8080/health

HTTP/1.1 503 Service Unavailable

Server: Apache-Coyote/1.1

X-Application-Context: application

Content-Type: application/json;charset=UTF-8

Transfer-Encoding: chunked

Date: Tue, 26 May 2015 15:18:15 GMT

Connection: close


{"status":"DOWN"}



2. 응답 상태 코드 변경 (response header)


application.properties 에 추가하면 health check 실패시 500 에러 발생한다.

endpoints.health.mapping.DOWN: INTERNAL_SERVER_ERROR


curl 요청


$ curalhost:8080/health

HTTP/1.1 500 Internal Server Error

Server: Apache-Coyote/1.1

X-Application-Context: application

Content-Type: application/json;charset=UTF-8

Transfer-Encoding: chunked

Date: Tue, 26 May 2015 15:23:45 GMT

Connection: close


{"status":"DOWN"}



endpoints.health.mapping.DOWN의 값에 아무거나 넣을 수 없다. Spring의 HttpStatus 상수 값을 넣어야 작동된다.


http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/HttpStatus.html


예를 들어 그냥 숫자만 넣거나 HttpStatus 상수 값을 넣지 않는다면, 다음과 같은 에러가 발생한다.


Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'endpoints.health.CONFIGURATION_PROPERTIES': Could not bind properties to [unknown] (target=endpoints.health, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

Field error in object 'endpoints.health' on field 'mapping[DOWN]': rejected value ["501"]; codes [typeMismatch.endpoints.health.mapping[DOWN],typeMismatch.endpoints.health.mapping,typeMismatch.mapping[DOWN],typeMismatch.mapping,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [endpoints.health.mapping[DOWN],mapping[DOWN]]; arguments []; default message [mapping[DOWN]]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.http.HttpStatus' for property 'mapping[DOWN]'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type org.springframework.http.HttpStatus for value '"501"'; nested exception is java.lang.IllegalArgumentException: No enum constant org.springframework.http.HttpStatus."501"]

at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:303)

at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:250)





이외, DB나 Nosql과 같은 서버의 Health 체크를 할 수 있다. 


예를 들어, DataSourceHealthIndicator (https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DataSourceHealthIndicator.java) 도 있다. 소스 보면 알겠지만, select 1 로 validation한다.


AbstractHealthIndicator(http://docs.spring.io/autorepo/docs/spring-boot/1.2.0.M1/api/org/springframework/boot/actuate/health/AbstractHealthIndicator.html)를 이용하여 다양한 Data Access에 대한 확장을 할 수 있다.



3. 정보를 볼 수 있는 다른 URL


health 뿐 아니라. info, beans, dump, env, metrics, trace... 등 다양하게 쓸 수 있고, 

security 를 이용하여 인증처리를 해야만 볼 수 있게도 한다.


예를 들어, 

http://localhost:8080/trace 를 요청하면, 어떤 url로 요청이 들어왔는지 100개 정보를 보여주고,

http://localhost:8080/dump 는 쓰레드 덤프를 요청한다. 



info* 정보를 properties에 추가하면, /info 요청을 보내면, info*설정 값을 웹으로 볼 수 있다. 


application.properties

info.app.name=TestService

info.app.description=test test

info.app.version=1.0.0-snapshot



{"app": {"description": "test test","name": "TestService","version": "1.0.0-snapshot"}

}



출처 :


http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html

Posted by '김용환'
,


STS 3.6.2를 사용중인데, 아래 URL을 참조해서 spring boot 애플리케이션 데모를 해볼 수 있다.

IDEA Intellij 만큼 잘 동작된다. 


https://spring.io/blog/2015/03/18/spring-boot-support-in-spring-tool-suite-3-6-4




Posted by '김용환'
,



gradle에서 remote maven repository 에서 pom/ jar를 못 읽어올 때가 있다. (ex : android test 관련 lib등등..)


그럴 때는 gradle이 maven local repository에서 읽어 오도록 수정하면 동작이 된다. 


아래와 같이.. 사용하면 될 것 같다.



repositories {

    mavenLocal()

    mavenCentral()

    maven { url "http://repo.spring.io/libs-snapshot" }

}

Posted by '김용환'
,



logback.xml과 application.yml 파일에서 동시에 선언된  logger 설정은 어느 것이 우선할까?


applicaiton.yml에서 설정한 것이 우선이었다. 



# application.yml

logging:
level:
org.springframework.web: DEBUG



# logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
...
<logger name="org.springframework.web" level="DEBUG"/>
...
</configuration>



Posted by '김용환'
,



application.yml 파일에서 profiles별로 정리할 수 있다. yml 파일에서는 '---;로 나눌 수 있다. 

그게 아니면, 상단에서 디폴트로 정할 수 있다. 


http://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html

server:
port: 8080

---

spring:
profiles : alpha
main:
show-banner: false
web_environment: false
server:
port: 8080

---


spring:
profiles : production
main:
show-banner: false
web_environment: false
server:
port: 8080

---


실행시 아래와 같이 사용하여 profile을 사용할 수 있다. 


java -jar .. -Dspring.profiles.active=production
java -jar .. -Dspring.profiles.active=alpha


http://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html#howto-set-active-spring-profiles




Posted by '김용환'
,




spring boot 배포할 때, application.yml(application.properties)에 

다음과 같이 pid와 port 번호를 저장하게 하여 동시에 두개의 데몬이 뜨지 않도록 처리할 수 있다.


context:
listener:
classes: org.springframework.boot.actuate.system.ApplicationPidFileWriter,org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter


실행 후에 application.pid와 port파일이 생성됨을 확인할 수 있다.



application.pid 파일에서 pid 를 일고 kill 하면 정상적으로 사라진다. 

Posted by '김용환'
,



spring boot로 배포하는 방법은 다음에 설명되어 있다. tomcat을 사용하는 경우의 배포 방식(war 배포)은 아래 문서를 참조하면 된다.


http://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html


container를 undertow + freemaker를 사용 않는 경우는 약간 달라서, 작성한다.


pom.xml


<packaging>jar</packaging>


...

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-undertow</artifactId>

        </dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-freemarker</artifactId>

        </dependency>

...        

        

jar를 패키징한 후, 해당 jar를 배포한 후, 아래와 같이 같이 실행(startup)한다.      

  

java -jar $JAR_FILE


종료는 kill 명령어를 이용한다.


kill $PID





그러면, 아래 로그s와 같이 graceful하게 종료가 된다. 



2015-05-20 17:27:16.694  INFO 18281 --- [       Thread-1] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2a7ed1f: startup date [Wed May 20 17:26:53 KST 2015]; root of context hierarchy

2015-05-20 17:27:16.695  INFO 18281 --- [       Thread-1] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 0

2015-05-20 17:27:16.697  INFO 18281 --- [       Thread-1] o.s.b.a.e.ElasticsearchAutoConfiguration : Closing Elasticsearch client

2015-05-20 17:27:16.697  INFO 18281 --- [       Thread-1] org.elasticsearch.node                   : [Eternity] stopping ...

2015-05-20 17:27:16.699  INFO 18281 --- [       Thread-1] org.elasticsearch.node                   : [Eternity] stopped

2015-05-20 17:27:16.699  INFO 18281 --- [       Thread-1] org.elasticsearch.node                   : [Eternity] closing ...

2015-05-20 17:27:16.704  INFO 18281 --- [       Thread-1] org.elasticsearch.node                   : [Eternity] closed

2015-05-20 17:27:16.705 DEBUG 18281 --- [       Thread-1] o.s.b.a.e.jmx.EndpointMBeanExporter      : Unregistering JMX-exposed beans on shutdown

2015-05-20 17:27:16.706 DEBUG 18281 --- [       Thread-1] o.s.b.a.e.jmx.EndpointMBeanExporter      : Unregistering JMX-exposed beans

2015-05-20 17:27:16.706  INFO 18281 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

2015-05-20 17:27:16.740  INFO 18281 --- [       Thread-1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'metricsExecutor'





Posted by '김용환'
,


[공부] Spring main 클래스(http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-structuring-your-code.html)에는  반드시 있어야할 Annotation이 있다.



출처 : http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-using-springbootapplication-annotation.html



1. @Configuration, @EnableAutoConfiguration, @ComponentScan  


2. @SpringBootApplication 

SpringBootApplication 소스는 다음과 같다. 1번을 다 포함시킨 것과 같다. 


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
Class<?>[] exclude() default {};
}



Posted by '김용환'
,




http://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html


Annotation (@SpringBootAnnotation) 잘 활용한 후..


undertow를 사용하면, packaging을 jar로 하면 끝이다.


패키징 (mvn clean package) 후, jar파일을 실행(java -jar google.jar) 하면 완료이다. 



Posted by '김용환'
,


spring boot/loaded 테스트하다가 다음과 같은 java.io.FileNotFoundException : FreeMarkerConfigurer 발생했다. 실행이 되길래, ERROR인 줄 알았는데,  DEBUG 이다. 

절대 경로에서 해당 파일이 없어서 Exception이 발생했고, 대비책(fallback)으로 spring classpath에서 파일을 읽은 것이다. 

따라서 절대로 에러가 아니다!!

사실 Exception 을 wrap 해서 간단히 보여줘도 될 것을.. exception을 다 stack으로 출력했나 싶긴하다..



2015-05-18 18:01:23.618 DEBUG 52607 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : Cannot resolve template loader path [classpath:/templates/] to [java.io.File]: using SpringTemplateLoader as fallback


java.io.FileNotFoundException: class path resource [templates/] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/mydev/location-lab/target/google-location-lab-0.0.1-SNAPSHOT.jar!/templates/

at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:212)

at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)

at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.getTemplateLoaderForPath(FreeMarkerConfigurationFactory.java:342)

at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.createConfiguration(FreeMarkerConfigurationFactory.java:290)

at org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer.afterPropertiesSet(FreeMarkerConfigurer.java:116)



소스를 보면, debug일 때에만 발생하도록 되어 있다. 

/**
* Determine a FreeMarker TemplateLoader for the given path.
* <p>Default implementation creates either a FileTemplateLoader or
* a SpringTemplateLoader.
* @param templateLoaderPath the path to load templates from
* @return an appropriate TemplateLoader
* @see freemarker.cache.FileTemplateLoader
* @see SpringTemplateLoader
*/
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) {
   if (isPreferFileSystemAccess()) {
      // Try to load via the file system, fall back to SpringTemplateLoader
      // (for hot detection of template changes, if possible).
      try {
         Resource path = getResourceLoader().getResource(templateLoaderPath);
         File file = path.getFile()// will fail if not resolvable in the file system
         if (logger.isDebugEnabled()) {
            logger.debug(
                  "Template loader path [" + path + "] resolved to file path [" + file.getAbsolutePath() + "]");
         }
         return new FileTemplateLoader(file);
      }
      catch (IOException ex) {
         if (logger.isDebugEnabled()) {
            logger.debug("Cannot resolve template loader path [" + templateLoaderPath +
                  "] to [java.io.File]: using SpringTemplateLoader as fallback", ex);
         }
         return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
      }
   }
   else {
      // Always load via SpringTemplateLoader (without hot detection of template changes).
      logger.debug("File system access not preferred: using SpringTemplateLoader");
      return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
   } }

logback.xml 파일에서 로그 레벨을 info로 수정하면 더이상의 Exception은 당연히 발생하지 않는다. 
<logger name="org.springframework.web" level="INFO"/>




Posted by '김용환'
,