GeekFactory

int128.hatenablog.com

Spring BootでログやActuatorにバージョン情報を含める

ログやActuatorにバージョン情報を含めておくと、本番環境でどのバージョンのアプリケーションが実行されているか簡単に確認できるので便利です。

ビルド時にapplication.ymlにバージョン情報を含める

Gradleでは、以下のようなビルドスクリプトを書くとapplication.ymlの文字列を置換できます。

version = System.getenv('TAG_NAME') ?: 'SNAPSHOT'

processResources {
  filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
    'APP_NAME': project.name,
    'APP_VERSION': project.version
  ])
}

ここでは、ビルド時に TAG_NAME という環境変数にバージョン番号が設定されている前提で、application.ymlを置換しています。バージョン番号でなくてもコミットハッシュや日時、ビルド番号など何でも構いません。

例えば、application.ymlに以下を書くとアプリケーション名やバージョン番号に置換されます。

app:
  name: "@APP_NAME@"
  version: "@APP_VERSION@"

なお、Spring Bootの公式ドキュメントではSimpleTemplateEngineによる置換が紹介されていますが、Placeholderと競合するので使いづらいです。ReplaceTokensを利用する方がおすすめです。

72. Properties & configuration

ログにバージョン情報を含める

Spring Cloud Sleuthを使用している場合は、以下を設定するとログの全行にバージョン情報が付加されます。

spring:
  application:
    name: "@APP_NAME@-@APP_VERSION@"
2016-02-26 11:15:47.561  INFO [example-1.0.0,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] com.example.Application   : Hello

(追記) spring.application.nameはService Discoveryのキーに使われるので、上記は避けた方がよいです。

起動時に出るだけでよいという場合は、適当なプロパティにバージョン情報を入れてログで出力するとよいでしょう。

@Slf4j
@SpringBootApplication
class App {
  static void main(String[] args) {
    def context = SpringApplication.run(App, args)
    log.info('Started {}', context.environment.getProperty('info.app.name'))
  }
}

Actuatorでバージョン情報を返す

Actuatorを使用している場合は、以下のように設定するとREST APIでバージョン情報を取得できるようになります。

info:
  app:
    name: "@APP_NAME@"
    version: "@APP_VERSION@"

GET /management/info にアクセスすると以下のようなJSONが得られます。

{
  "app": {
    "name": "example",
    "version": "1.0.0"
  }
}

Actuatorでバージョン情報が取れると便利なのでぜひ使ってみてください。

ターミナルのウィンドウタイトルにホスト名などを入れる

久しぶりにzshネタです。

複数のタブを開いていろんなサーバにSSHしていると区別が付かなくなってきたので、ウィンドウタイトルに実行コマンド、ホスト名、カレントディレクトリを入れてみました。zshの組み込みコマンドだけで実現してみました。

function _window_title_cmd () {
  local pwd="${PWD/~HOME/~}"
  print -n "\e]0;"
  print -n "${pwd##*/} (${HOST%%.*})"
  print -n "\a"
}

function _window_title_exec () {
  local pwd="${PWD/~HOME/~}"
  print -n "\e]0;"
  print -n "${1%% *}:${pwd##*/} (${HOST%%.*})"
  print -n "\a"
}

[[ "$TERM" =~ "^xterm" ]] && {
  add-zsh-hook precmd _window_title_cmd
  add-zsh-hook preexec _window_title_exec
}

コマンドプロンプトのカスタマイズは下記のエントリで紹介しています。ご参考まで。

int128.hatenablog.com

Spring Security OAuth2でリクエストログを出力する

Spring Security OAuth2でアクセストークンのリクエストとレスポンスのログを出力するには、Apache HttpClientを使うと簡単です。

概要

  • Spring Security OAuth2のアクセストークン取得はRestTemplateを利用しています。
  • RestTemplateはデフォルトではHttpURLConnectionを利用しますが、HttpClientを利用することもできます。
  • HttpClientはログレベルを設定するだけでヘッダやボディのログを出力してくれます。

OAuth2RestTemplateの場合

OAuth2RestTemplateを生成する際に以下を行うことで、アクセストークンのリクエストやレスポンスのログ出力が有効になります。アクセストークン取得後のリクエストやレスポンスは別物なので注意してください。

@Configuration
class AppConfiguration {
    @Bean
    OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oAuth2ClientContext) {
        def restTemplate = new OAuth2RestTemplate(resource(), oAuth2ClientContext)

        // HttpClientを利用するように設定する
        def requestFactory = new HttpComponentsClientHttpRequestFactory()
        def accessTokenProviders = Arrays.asList(
            new AuthorizationCodeAccessTokenProvider(),
            new ImplicitAccessTokenProvider(),
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider()
        )
        accessTokenProviders*.requestFactory = requestFactory
        restTemplate.accessTokenProvider = new AccessTokenProviderChain(accessTokenProviders)

        restTemplate
    }

    private resource() {
        def details = new ClientCredentialsResourceDetails()
        // TODO: client_id, client_secretなどを設定する
        details
    }
}
@RestController
class HelloController {
    @Inject
    OAuth2RestTemplate restTemplate

    @GetMapping('/hello')
    Hello helloWorld() {
        // OAuth2RestTemplateを利用してAPIを実行する
        def response = restTemplate.getForEntity("https://.../hello", Hello)
    }
}

OAuth2FeignRequestInterceptorの場合

OAuth2FeignRequestInterceptorはAccessTokenProviderを設定するメソッドを提供していないため、クラスを継承して拡張する必要があります。

class HelloOAuth2FeignRequestInterceptor extends OAuth2FeignRequestInterceptor {
    private final OAuth2ClientContext oAuth2ClientContext

    private final AccessTokenProvider accessTokenProvider = {
        // HttpClientを利用するように設定する
        def requestFactory = new HttpComponentsClientHttpRequestFactory()
        def accessTokenProviders = Arrays.asList(
            new AuthorizationCodeAccessTokenProvider(),
            new ImplicitAccessTokenProvider(),
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider()
        )
        accessTokenProviders*.requestFactory = requestFactory
        new AccessTokenProviderChain(accessTokenProviders)
    }()

    private final OAuth2ProtectedResourceDetails resource = {
        def details = new ClientCredentialsResourceDetails()
        // TODO: client_id, client_secretなどを設定する
        details
    }()

    def HelloOAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext) {
        super(oAuth2ClientContext, null)
        this.oAuth2ClientContext = oAuth2ClientContext
    }

    /**
     * @see OAuth2FeignRequestInterceptor#acquireAccessToken()
     */
    @Override
    protected OAuth2AccessToken acquireAccessToken() {
        // (中略) OAuth2FeignRequestInterceptor#acquireAccessToken() の実装をコピー
    }
}

OAuth2FeignRequestInterceptorに setAccessTokenProvider() メソッドを追加するPull Requestがあるので、将来のバージョンでは改善されるかもしれません。

github.com

ログレベルの設定

HttpClientがログを出力するようにapplication.ymlを設定します。

logging.level:
  # ヘッダのみ出力する場合
  org.apache.http.headers: DEBUG
  # ヘッダとボディを出力する場合
  org.apache.http.wire: DEBUG

アクセストークンリクエストのカスタマイズ(おまけ)

上記と同様に setTokenRequestEnhancer() を利用することで、アクセストークンのリクエストをカスタマイズできます。例えば、client_idclient_secret をボディに入れるといった独自仕様に対応できます。

    @Bean
    OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oAuth2ClientContext) {
        def restTemplate = new OAuth2RestTemplate(resource(), oAuth2ClientContext)
        def accessTokenProvider = new ClientCredentialsAccessTokenProvider()
        // HttpClientを利用するように設定する
        accessTokenProvider.requestFactory = new HttpComponentsClientHttpRequestFactory()
        // アクセストークンリクエストをカスタマイズする
        accessTokenProviders*.tokenRequestEnhancer = new RequestEnhancer() {
            @Override
            void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form, HttpHeaders headers) {
            }
        }
        restTemplate.accessTokenProvider = accessTokenProvider
        restTemplate
    }

ご参考まで。