Spring Boot Actuator 2のエンドポイントを叩いてみる

Spring

ヘルスチェックなどの必須機能の他にも何かと便利なSpring Boot Actuatorですが、Spring Bootが2系にバージョンアップしてから大きく仕様が変わったとかなんとか。1系の仕様もそんなに詳しく抑えているわけではないですが1回さらっと見ておくのも良いかな、ということで軽く動かしてみました。

前提バージョン

この記事で使用した各種ライブラリのバージョンは以下の通り。

  • Spring Boot:2.0.4.RELEASE
  • kotlin:1.2.61
  • Java:1.8.0_171
  • Gradle:4.8.1
  • com.gorylenko.gradle-git-properties:1.5.1

その他Spring系のライブラリはspring-boot-dependenciesに従います。

依存関係の定義

starterがいるのでbuilg.gradleに依存関係を設定します。

dependencies {
    // 省略
    compile('org.springframework.boot:spring-boot-starter-actuator')
    // 省略
}

使うだけならこれだけですね。

全エンドポイント開放

Spring Boot Actuatorが提供するエンドポイントの一覧についてはこちらをご参照ください。

50. Endpoints

Spring Boot Actuatorの各エンドポイントが利用可能になるかどうかは以下の条件に従います。

  • プロパティの設定で有効(enable)かつ開放(exposure)となっている
  • AutoConfigurationでエンドポイントが有効になる条件を満たす

shutdownエンドポイントを除いて、各エンドポイントはデフォルトで有効になっていますが、デフォルトで開放されているのはhealth,infoのみとなります。ということでapplication.propertiesに対してエンドポイントの全開放とshutdownエンドポイントの有効化を設定します。

management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true

これでAutoConfigurationでエンドポイントがコンフィグされれば利用可能となります。

検証用に開放しているだけですので実際は運用上必要な設定で開放してください。
各エンドポイントの有効/無効を制御するプロパティキーはSpring Bootのリファレンスをご参照ください。
Appendix A. Common application properties

各エンドポイントの振る舞い

というわけで各エンドポイントについて見ていきましょう。と言っても基本的な使い方は全てリファレンスに載っていますので基本的にはそちらをご参照ください。

Spring Boot Actuator Web API Documentation

ここでは、簡単な説明とAutoConfigurationの条件を確認しつつ、補足したくなったポイントを書いていこうと思います。

auditevents

auditeventsエンドポイントは、その名の通り監査イベントを参照するエンドポイントです。説明はこちら。

56. Auditing

Spring Securityでの認証系のイベントを扱ったり、独自の監査イベントを発行して登録することができます。デフォルトではAuditEventRepositoryの実装クラスとしてInMemoryAuditEventRepositoryがBean定義されますので、それを使用して監査イベントを登録できます。

auditEventRepository.add(AuditEvent("anonymous", "action", mapOf("method" to "get")))

InMemoryAuditEventRepositoryはその名の通りインメモリなので、ちゃんと保存しておきたい場合などは独自に実装が必要です。

AutoConfiguration

auditeventsエンドポイントのConfigurationクラスは、AuditEventsEndpointAutoConfigurationになります。

実装を見てみると、

@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(AuditEventRepository.class)
@ConditionalOnEnabledEndpoint
public AuditEventsEndpoint auditEventsEndpoint(
    AuditEventRepository auditEventRepository) {
  return new AuditEventsEndpoint(auditEventRepository);
}

ということなので、

  • AuditEventsEndpointがBean定義されていない
  • AuditEventRepositoryの実装クラスがBean定義されている
  • プロパティ設定で有効になっている

が条件になります。前述の通り、spring-boot-starter-actuatorを使用して依存関係を定義することでSpring Boot ActuatorのAutoConfigurationが有効となり、デフォルトでInMemoryAuditEventRepositoryがBean定義されますので、特段指定せずとも有効です。

実行してみる

curl 'http://localhost:8080/' -i -X GET -u user:password

こんなのが返ってきます。

{
  "events": [
    {
      "timestamp": "2018-08-27T06:56:08.688Z",
      "principal": "user",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": null
        }
      }
    },
    {
      "timestamp": "2018-08-27T06:56:08.714Z",
      "principal": "anonymous",
      "type": "action",
      "data": {
        "method": "get"
      }
    },
    {
      "timestamp": "2018-08-27T06:56:10.896Z",
      "principal": "user",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": null
        }
      }
    }
  ]
}

Spring SecurityでBasic認証を有効にし、簡単なGETメソッドだけのREST-APIの中で自前の監査イベントを登録しています。1つ目のものがSpring Securityの認証成功イベント、2つ目が自前のイベントですね。

何も指定せずに叩くと監査イベントを全部返してきますが、リクエストパラメータで絞り込みが可能です。
Spring Boot Actuator Web API Documentation

beans

beansエンドポイントは、Bean定義の内容を取得できます。試験中やトラブル解析なんかに便利ですね。Scopeや親子関係なども表示してくれます。

AutoConfiguration

beansエンドポイントのConfigurationクラスは、BeansEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public BeansEndpoint beansEndpoint(
    ConfigurableApplicationContext applicationContext) {
  return new BeansEndpoint(applicationContext);
}

ということなので、

  • BeansEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。面白みは無いので実行結果は省略します。

conditions

conditionsエンドポイントはConfigurationクラスで使用した条件(@ConditionalOn**)がマッチしたとかしなかったとかその辺の情報を返してくれます。

AutoConfiguration

conditionsエンドポイントのConfigurationクラスは、ConditionsReportEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnEnabledEndpoint
public ConditionsReportEndpoint conditionsReportEndpoint(
    ConfigurableApplicationContext context) {
  return new ConditionsReportEndpoint(context);
}

ということなので、

  • (親コンテキストは考慮せず)ConditionsReportEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。面白みは無いので実行結果は省略します。

configprops

configpropsエンドポイントは@ConfigurationPropertiesで取得したプロパティ値がどうなっているかを返してくれます。

AutoConfiguration

configpropsエンドポイントのConfigurationクラスは、ConfigurationPropertiesReportEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() {
  ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
  String[] keysToSanitize = this.properties.getKeysToSanitize();
  if (keysToSanitize != null) {
    endpoint.setKeysToSanitize(keysToSanitize);
  }
  return endpoint;
}

ということなので、

  • ConfigurationPropertiesReportEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。面白みは無いので実行結果は省略します。

env

envエンドポイントはプロパティの値とどこに設定されたものが有効になっているかを取得できます。

AutoConfiguration

envエンドポイントのConfigurationクラスは、EnvironmentEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public EnvironmentEndpoint environmentEndpoint(Environment environment) {
  EnvironmentEndpoint endpoint = new EnvironmentEndpoint(environment);
  String[] keysToSanitize = this.properties.getKeysToSanitize();
  if (keysToSanitize != null) {
    endpoint.setKeysToSanitize(keysToSanitize);
  }
  return endpoint;
}

ということなので、

  • EnvironmentEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。

実行してみる

PathVariableでプロパティキーを指定します。

curl 'http://localhost:8080/actuator/env/spring.security.user.name' -i -X GET -u user:password

Basic認証用のユーザを指定しているのでそれを参照してみます。

{
  "property": {
    "source": "applicationConfig: [classpath:/application.properties]",
    "value": "user"
  },
  "activeProfiles": [],
  "propertySources": [
    {
      "name": "server.ports"
    },
    {
      "name": "servletConfigInitParams"
    },
    {
      "name": "servletContextInitParams"
    },
    {
      "name": "systemProperties"
    },
    {
      "name": "systemEnvironment"
    },
    {
      "name": "random"
    },
    {
      "name": "applicationConfig: [classpath:/application.properties]",
      "property": {
        "value": "user",
        "origin": "class path resource [application.properties]:6:27"
      }
    },
    {
      "name": "Management Server"
    }
  ]
}

application.propertiesの設定が有効となっており、設定値がuserであることが確認できました。併せて有効になっているSpring Profile(何も指定していないのでブランク)と設定値の導出元(環境変数その他)が表示されています。環境変数などから取得したものであればそこに表示される感じですね。

flyway

flywayエンドポイントはアプリケーション内でFlywayによるDBマイグレーションを実施している場合に、その実行状況等を返してくれます。

AutoConfiguration

flywayエンドポイントのConfigurationクラスは、FlywayEndpointAutoConfigurationになります。

@Bean
@ConditionalOnBean(Flyway.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public FlywayEndpoint flywayEndpoint(ApplicationContext context) {
  return new FlywayEndpoint(context);
}

ということなので、

  • FlywayクラスがBean定義されている
  • FlywayEndpointクラスがBean定義されていない
  • プロパティ設定で有効になっている

何はなくともFlywayによるマイグレーションが有効になっている必要がありますね。

実行してみる

当然ですがFlywayを入れなきゃいけません。build.gradleに依存関係を設定します。

dependencies {
  // 省略
  compile('org.springframework.boot:spring-boot-starter-jdbc')
  compile("org.flywaydb:flyway-core")
  runtime('com.h2database:h2')
  // 省略
}

DBはとりあえずH2を使うことにします。

加えて、マイグレーション用のSQLをデフォルトのパスに配置します。

PROFECT_ROOT
`-- src
    `-- main
        `-- resources
            `-- db
                `-- migration
                    |-- V1__Add_new_table.sql
                    `-- V2__Insert_new_data.sql

SQLは適当になにか作ります。DB接続はSpring Bootのデフォルト(H2を使用したDataSourceがコンフィグされる)におまかせすることにして何も設定せずに行きます。

起動するとマイグレーションが実行されるのがわかります。

2018-08-27 17:19:41.230  INFO 19972 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 2 migrations (execution time 00:00.024s)
2018-08-27 17:19:41.252  INFO 19972 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table: "PUBLIC"."flyway_schema_history"
2018-08-27 17:19:41.280  INFO 19972 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2018-08-27 17:19:41.283  INFO 19972 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1 - Add new table
2018-08-27 17:19:41.299  INFO 19972 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 2 - Insert new datae
2018-08-27 17:19:41.311  INFO 19972 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 2 migrations to schema "PUBLIC" (execution time 00:00.065s)

起動したら改めてエンドポイントを叩いてみます。

curl 'http://localhost:8080/actuator/flyway' -i -X GET -u user:password

こんなのが返ってきます。

{
  "contexts": {
    "application": {
      "flywayBeans": {
        "flyway": {
          "migrations": [
            {
              "type": "SQL",
              "checksum": 1730293508,
              "version": "1",
              "description": "Add new table",
              "script": "V1__Add_new_table.sql",
              "state": "SUCCESS",
              "installedBy": "SA",
              "installedOn": "2018-08-27T06:55:55.482Z",
              "installedRank": 1,
              "executionTime": 1
            },
            {
              "type": "SQL",
              "checksum": 1134636294,
              "version": "2",
              "description": "Insert new datae",
              "script": "V2__Insert_new_datae.sql",
              "state": "SUCCESS",
              "installedBy": "SA",
              "installedOn": "2018-08-27T06:55:55.494Z",
              "installedRank": 2,
              "executionTime": 1
            }
          ]
        }
      },
      "parentId": null
    }
  }
}

2バージョンのマイグレーションが成功していることが見て取れます。

health

なんかかんや一番重要なhealthエンドポイント。ロードバランサやKubernetesのReadiness Probeから叩かせてリクエストの振り分け制御に使いますね。

AutoConfiguration

healthエンドポイントのConfigurationクラスは、HealthEndpointAutoConfigurationHealthEndpointConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) {
  return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext));
}

ということなので、

  • HealthEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。面白みは無いので実行結果は省略します。

management.endpoint.health.show-detailsプロパティで全体のステータスだけを返すか、各インジケータの結果詳細を返すかを制御できます。

  • never:常に詳細なし
  • when-authorized:認証済みアクセス時のみ(management.endpoint.health.rolesに指定したロールのみ)
  • always:常に詳細
50. Endpoints

httptrace

httptraceは直近(デフォルトでは100)のリクエストの結果を返してくれます。

AutoConfiguration

healthエンドポイントのConfigurationクラスは、HttpTraceEndpointAutoConfigurationになります。

@Bean
@ConditionalOnBean(HttpTraceRepository.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HttpTraceEndpoint httpTraceEndpoint(HttpTraceRepository traceRepository) {
  return new HttpTraceEndpoint(traceRepository);
}

ということなので、

  • HttpTraceRepositoryがBean定義されている
  • HttpTraceEndpointがBean定義されていない
  • プロパティ設定で有効になっている

HttpTraceRepositoryHttpTraceAutoConfigurationでBean定義されています。

@Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(HttpTraceProperties.class)
public class HttpTraceAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean(HttpTraceRepository.class)
  public InMemoryHttpTraceRepository traceRepository() {
    return new InMemoryHttpTraceRepository();
  }
  // 省略
}

WebアプリでHTTP tracingが有効であればコンフィグされますね。

実行してみる

curl 'http://localhost:8080/actuator/httptrace' -i -X GET -u user:password

こんなのが返ってきます。

{
  "traces": [
    {
      "timestamp": "2018-08-27T08:26:47.628Z",
      "principal": null,
      "session": null,
      "request": {
        "method": "GET",
        "uri": "http://localhost:8080/actuator/health",
        "headers": {
          "host": [
            "localhost:8080"
          ],
          "accept": [
            "*/*"
          ],
          "user-agent": [
            "curl/7.60.0"
          ]
        },
        "remoteAddress": null
      },
      "response": {
        "status": 200,
        "headers": {
          "Transfer-Encoding": [
            "chunked"
          ],
          "X-Frame-Options": [
            "DENY"
          ],
          "Cache-Control": [
            "no-cache, no-store, max-age=0, must-revalidate"
          ],
          "X-Content-Type-Options": [
            "nosniff"
          ],
          "Set-Cookie": [
            "SESSION=OTkzOTkwMzYtZTE1ZC00NjljLWE0OWYtODg1NzVhZGQ1ZjY3; Path=/; HttpOnly"
          ],
          "Expires": [
            "0"
          ],
          "Pragma": [
            "no-cache"
          ],
          "X-XSS-Protection": [
            "1; mode=block"
          ],
          "Date": [
            "Mon, 27 Aug 2018 08:26:47 GMT"
          ],
          "Content-Type": [
            "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
          ]
        }
      },
      "timeTaken": 29
    }
  ]
}

とりあえず一つ分。

info

infoエンドポイントはアプリケーションの情報を返してくれます。

gitのbranchやcommitの情報、バージョンなどのbuild情報を表示することが可能です。gitの情報については、classpath上のgit.propertiesに指定したものを表示可能ですが、ビルド時にgit.propertiesを生成してくれるプラグインがあるので今回はそれを使用してみます。

設定はこちらを参照。

Gradle - Plugin: com.gorylenko.gradle-git-properties

今リリースされてるアプリって具体的にどのバージョンなんだっけ?が見えるって奴ですね。

AutoConfiguration

infoエンドポイントのConfigurationクラスはInfoEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public InfoEndpoint infoEndpoint(
    ObjectProvider<List> infoContributors) {
  return new InfoEndpoint(infoContributors.getIfAvailable(Collections::emptyList));
}

ということなので、

  • InfoEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特に設定しなくても有効になります。

実行してみる

curl 'http://localhost:8080/actuator/info' -i -X GET -u user:password

こんなのが返ってきます。

{
  "git": {
    "build": {
      "host": "MAIN-PC",
      "version": "0.0.1-SNAPSHOT",
      "time": "2018-08-27T06:55:48Z",
      "user": {
        "name": "Tohru Kawakami",
        "email": "email@gmail.com"
      }
    },
    "branch": "master",
    "commit": {
      "message": {
        "short": "add spring-boot-actuator-sample",
        "full": "add spring-boot-actuator-sample\n"
      },
      "id": {
        "describe": "",
        "abbrev": "9e8dc19",
        "full": "9e8dc1964b705c9ebb9db797d3607427071c7b9f"
      },
      "time": "2018-08-27T05:49:34Z",
      "user": {
        "email": "email@gmail.com",
        "name": "Tohru Kawakami"
      }
    },
    "closest": {
      "tag": {
        "name": "",
        "commit": {
          "count": ""
        }
      }
    },
    "dirty": "false",
    "remote": {
      "origin": {
        "url": "git@github.com:kawakamitor/blog-materials.git"
      }
    },
    "tags": "",
    "total": {
      "commit": {
        "count": "6"
      }
    }
  }
}

loggers

loggersエンドポイントはログレベルの参照や設定が可能です。アプリケーションを起動したままログレベルを変更する方法は昔からあったりもしましたが、APIとして口が開いてるっていうのは良いですよね。さすがSpring Boot。

AutoConfiguration

loggersエンドポイントのConfigurationクラスはLoggersEndpointAutoConfigurationになります。

@Bean
@ConditionalOnBean(LoggingSystem.class)
@Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
  return new LoggersEndpoint(loggingSystem);
}

static class OnEnabledLoggingSystemCondition extends SpringBootCondition {

  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context,
      AnnotatedTypeMetadata metadata) {
    ConditionMessage.Builder message = ConditionMessage
        .forCondition("Logging System");
    String loggingSystem = System.getProperty(LoggingSystem.SYSTEM_PROPERTY);
    if (LoggingSystem.NONE.equals(loggingSystem)) {
      return ConditionOutcome.noMatch(message.because("system property "
          + LoggingSystem.SYSTEM_PROPERTY + " is set to none"));
    }
    return ConditionOutcome.match(message.because("enabled"));
  }

}

おお・・・なんか複雑ですね。

  • LoggingSystemがBean定義されている
  • -Dorg.springframework.boot.logging.LoggingSystem=noneとか設定してない
  • LoggersEndpointがBean定義されていない
  • プロパティ設定で有効になっている

OnEnabledLoggingSystemConditionを正しく読めたか怪しい(; ・`д・´)

実行してみる

まずはログレベルの参照。

curl 'http://localhost:8080/actuator/loggers/com.example' -i -X GET -u user:password

こんなのが返ってきます。

{
  "configuredLevel": null,
  "effectiveLevel": "INFO"
}

個別に指定は指定無くて、INFOレベルが適用されている、と。

続いてログレベルを変えてみます・・・が、その前にSpring Securityを入れているとデフォルトでCSRF対策が有効になっているので、POSTメソッドがそのままでは叩けないので無効にします。

28. Security
@Configuration
class WebSecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http.csrf().disable()
    }

}

その上でログレベルを変更します。

クライアントとしてブラウザを想定する場合は完全に無効にするのではなくて、対象のパスのみ無効化してください。
curl 'http://localhost:8080/actuator/loggers/com.example' -i -X POST -u user:password -H 'Content-Type: application/json' -d '{"configuredLevel":"debug"}'

もう一度参照してみると変更が反映されているのがわかります。

curl 'http://localhost:8080/actuator/loggers/com.example' -i -X GET -u user:password

// 省略

{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

liquibase

liquibaseエンドポイントはLiquibaseについてflywayエンドポイントと同様の機能を提供します。今回はちょっと省略します。

metrics

metricsエンドポイントはメモリ使用量やコネクションプール情報その他の数値が取得できます。

AutoConfiguration

metricsエンドポイントのConfigurationクラスは、MetricsEndpointAutoConfigurationになります。

@Configuration
@ConditionalOnClass(Timed.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class,
    CompositeMeterRegistryAutoConfiguration.class })
public class MetricsEndpointAutoConfiguration {

  @Bean
  @ConditionalOnBean(MeterRegistry.class)
  @ConditionalOnMissingBean
  @ConditionalOnEnabledEndpoint
  public MetricsEndpoint metricsEndpoint(MeterRegistry registry) {
    return new MetricsEndpoint(registry);
  }

}

ということなので、

  • MeterRegistryの具象クラスがBean定義されている
  • MetricsEndpointがBean定義されていない
  • プロパティ設定で有効になっている

MeterRegistryの具象クラスはCompositeMeterRegistryConfigurationで定義されています。

実行してみる

まずは、取得可能なMetricsの一覧を取得します。

curl 'http://localhost:8080/actuator/metrics' -i -X GET -u user:password

色々取れますが、それぞれが何を意味しているかは今回は省略します。雰囲気でわかる感じでもありますし。

{
  "names": [
    "jvm.memory.max",
    "jdbc.connections.active",
    "jvm.gc.memory.promoted",
    "tomcat.cache.hit",
    "tomcat.cache.access",
    "jvm.memory.used",
    "jvm.gc.max.data.size",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "jvm.gc.pause",
    "jvm.memory.committed",
    "system.cpu.count",
    "logback.events",
    "tomcat.global.sent",
    "http.server.requests",
    "jvm.buffer.memory.used",
    "tomcat.sessions.created",
    "jvm.threads.daemon",
    "system.cpu.usage",
    "jvm.gc.memory.allocated",
    "tomcat.global.request.max",
    "hikaricp.connections.idle",
    "hikaricp.connections.pending",
    "tomcat.global.request",
    "tomcat.sessions.expired",
    "hikaricp.connections",
    "jvm.threads.live",
    "jvm.threads.peak",
    "tomcat.global.received",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "process.uptime",
    "tomcat.sessions.rejected",
    "process.cpu.usage",
    "tomcat.threads.config.max",
    "jvm.classes.loaded",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "jvm.classes.unloaded",
    "tomcat.global.error",
    "tomcat.sessions.active.current",
    "tomcat.sessions.alive.max",
    "jvm.gc.live.data.size",
    "tomcat.servlet.request.max",
    "hikaricp.connections.usage",
    "tomcat.threads.current",
    "tomcat.servlet.request",
    "hikaricp.connections.timeout",
    "jvm.buffer.count",
    "jvm.buffer.total.capacity",
    "tomcat.sessions.active.max",
    "hikaricp.connections.acquire",
    "tomcat.threads.busy",
    "process.start.time",
    "tomcat.servlet.error"
  ]
}

一番上に出てきたjvm.memory.maxを取得してみます。

curl 'http://localhost:8080/actuator/metrics/jvm.memory.max' -i -X GET -u user:password

こんな感じで返ってきます。

{
  "name": "jvm.memory.max",
  "description": "The maximum amount of memory in bytes that can be used for memory management",
  "baseUnit": "bytes",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 33446428671
    }
  ],
  "availableTags": [
    {
      "tag": "area",
      "values": [
        "heap",
        "nonheap"
      ]
    },
    {
      "tag": "id",
      "values": [
        "Compressed Class Space",
        "PS Survivor Space",
        "PS Old Gen",
        "Metaspace",
        "PS Eden Space",
        "Code Cache"
      ]
    }
  ]
}

色々取得できて便利です。

mappings

mappingsエンドポイントは@RequestMappingされているパスを取得できます。

AutoConfiguration

mappingsエンドポイントのConfigurationクラスは、MappingsEndpointAutoConfigurationになります。

@Bean
@ConditionalOnEnabledEndpoint
public MappingsEndpoint mappingsEndpoint(ApplicationContext applicationContext,
    ObjectProvider<Collection> descriptionProviders) {
  return new MappingsEndpoint(
      descriptionProviders.getIfAvailable(Collections::emptyList),
      applicationContext);
}

ということなので、プロパティ設定で有効なら使えますね。面白みは無いので実行結果は省略します。

scheduledtasks

scheduledtasksエンドポイントはSpringの仕組みで実行されているスケジュールタスクの情報を取得できます。せっかくなのでスケジュールタスクを作ります。

@SpringBootApplication
@EnableScheduling
class SpringBootActuatorSampleApplication(val auditEventRepository: AuditEventRepository) {

    private final val logger = LoggerFactory.getLogger(SpringBootActuatorSampleApplication::class.java)

    @Scheduled(fixedDelay = 5000)
    fun async() {
        logger.info("Execute async function.")
    }
}

起動クラスに@EnableSchedulingを付与し、スケジュールタスクを有効にします。そのうえで、@Scheduledを付与したメソッドを用意し、fixedDelayを指定して実行させます。

5秒おきにログを出すだけですが、アプリケーションを起動すると延々とこんなログが出ます。

2018-08-27 20:41:18.116  INFO 19304 --- [pool-1-thread-1] .e.s.SpringBootActuatorSampleApplication : Execute async function.
2018-08-27 20:41:23.116  INFO 19304 --- [pool-1-thread-1] .e.s.SpringBootActuatorSampleApplication : Execute async function.
2018-08-27 20:41:28.117  INFO 19304 --- [pool-1-thread-1] .e.s.SpringBootActuatorSampleApplication : Execute async function.
2018-08-27 20:41:33.117  INFO 19304 --- [pool-1-thread-1] .e.s.SpringBootActuatorSampleApplication : Execute async function.

AutoConfiguration

scheduledtasksエンドポイントのConfigurationクラスは、ScheduledTasksEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ScheduledTasksEndpoint scheduledTasksEndpoint(
    ObjectProvider<List> holders) {
  return new ScheduledTasksEndpoint(holders.getIfAvailable(Collections::emptyList));
}

ということなので、

  • ScheduledTasksEndpointがBean定義されていない
  • プロパティ設定で有効になっている

特段設定しなくても有効になりますね。

実行してみる

curl 'http://localhost:8080/actuator/scheduledtasks' -i -X GET -u user:password

こんなのが返ってきます。

{
  "cron": [
    {
      "runnable": {
        "target": "org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration$$Lambda$533/1465800495"
      },
      "expression": "0 * * * * *"
    }
  ],
  "fixedDelay": [
    {
      "runnable": {
        "target": "com.example.springbootactuatorsample.SpringBootActuatorSampleApplication.async"
      },
      "initialDelay": 0,
      "interval": 5000
    }
  ],
  "fixedRate": []
}

後述するsessionエンドポイント用に入れたSpring Sessionのcronタスクと自前で設定したfixedDelayの設定が確認できます。

sessions

sessionsエンドポイントはSpring Sessionを使用して管理されているセッション情報を参照できます。Spring Sessionを使っていないとダメです。ストア先はRedisでもJDBCでもOKです。

とりあえずお手軽にということでspring-session-jdbcを使用します。

dependencies {
    // 省略
    compile('org.springframework.boot:spring-boot-starter-jdbc')
    compile('org.springframework.session:spring-session-jdbc')
    runtime('com.h2database:h2')
    // 省略
}

上の方で説明した内容と重複するかもしれませんが、DBはH2を使用、セッションのストア先とするためにspring-session-jdbcの依存関係を設定します。

DataSourceはSpring Bootにお任せでコンフィグされたものを使用しておけば自動的に起動時にテーブルを初期化してくれます。

2018-08-27 20:54:29.892  INFO 18688 --- [           main] o.s.jdbc.datasource.init.ScriptUtils     : Executing SQL script from class path resource [org/springframework/session/jdbc/schema-h2.sql]
2018-08-27 20:54:29.902  INFO 18688 --- [           main] o.s.jdbc.datasource.init.ScriptUtils     : Executed SQL script from class path resource [org/springframework/session/jdbc/schema-h2.sql] in 10 ms.

AutoConfiguration

sessionsエンドポイントのConfigurationクラスは、SessionsEndpointAutoConfigurationになります。

@Bean
@ConditionalOnBean(FindByIndexNameSessionRepository.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public SessionsEndpoint sessionEndpoint(
    FindByIndexNameSessionRepository<? extends Session> sessionRepository) {
  return new SessionsEndpoint(sessionRepository);
}

ということなので、

  • FindByIndexNameSessionRepositoryの実装クラスがBean定義されている
  • SessionsEndpointがBean定義されていない
  • プロパティ設定で有効になっている

FindByIndexNameSessionRepositoryはSpring Sessionのインターフェースなので、Spring Sessionの導入が必要です。実装クラスのコンフィグはspring-session-jdbcの場合JdbcOperationsSessionRepositoryが有効になっています。

面白みは無いので実行結果は省略します。

shutdown

shutdownエンドポイントは文字通りSpring Bootアプリケーションのシャットダウンを提供します。POSTメソッドでエンドポイントを叩くことで停止可能です。

shutdownエンドポイントの実装クラスはShutdownEndpointですが、実装を見ればわかる通り、0.5秒待ってConfigurableApplicationContext.closeなので、Gracefulではありません。Gracefulにする場合は独自の実装が必要です。

AutoConfiguration

shutdownエンドポイントのConfigurationクラスは、ShutdownEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ShutdownEndpoint shutdownEndpoint() {
  return new ShutdownEndpoint();
}

ということなので、プロパティ設定が有効であれば使用可能です。面白みは無いので実行結果は省略します。

threaddump

threaddumpエンドポイントは、文字通りスレッドダンプを取得できます。

AutoConfiguration

threaddumpエンドポイントのConfigurationクラスは、ThreadDumpEndpointAutoConfigurationになります。

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public ThreadDumpEndpoint dumpEndpoint() {
  return new ThreadDumpEndpoint();
}

ということなので、プロパティ設定で有効であれば使用可能です。面白みは無いので実行結果は省略します。

実はまだあるのですが

とりあえず公式リファレンスでテクノロジーに依存しないエンドポイントとして紹介されているものについてさらっと触れてみました。

各エンドポイントについてもそうですし、Actuatorそのものの振る舞いを制御するプロパティキーなんかもたくさんあるので、使うときはそのへんも抑えたいところですね。

カスタムエンドポイントの実装方法なんかも紹介されていますし、そのへんも必要に応じて使いこなせると便利そうですね。

50. Endpoints

まとめ

Spring Bootの1系から新しく追加されたり名前が変わったりしたものもちょこちょこありつつ、プロパティキーやパスのデフォルト値が変わっていたり移行しようとすると結構ハマるなんて話もありつつのSpring Boot Actuatorですが。

使いこなせると便利ですよね~ということで一度提供されているエンドポイントについてさらっと触れてみました。サンプルと言うほどのサンプルではないですが、叩ける状態にしたものを置いておきます。

kawakamitor/blog-materials
Contribute to kawakamitor/blog-materials development by creating an account on GitHub.

何かの参考になれば。

この記事に対するコメント