Elasticsearch 検索クエリを JSON 形式でログ出力する

こんにちは。株式会社 Interfamilia の見習い社員、Wakaです。2度目の記事投稿になります。

最近は天気が不安定で、昼間は日差しが強くて暑いのに、夕方は涼しくて肌寒い!なんてことが多いですね。
体調管理に気をつけねば…と思う今日この頃です。

今回の記事では、自分が日々の開発業務で学んだ知識をご紹介したいと思います!


目次

概要

環境

  • OS:Windows 10 Pro
  • Java:11.0.9 (OpenJDK)
  • Elasticsearch :7.12.1
  • Kibana:7.12.1

Elasticsearch Java API の検索クエリをJSON形式で確認したい!

Elasticsearch と Java の連携処理を実装したい場合は、専用ライブラリの利用が欠かせません。

Javaアプリケーション側でクエリを組み立て、RestHighLevelClient を使用して、Elasticsearch にクエリを投げる!といった要領で使います。かなり柔軟な操作を実現できるようです。

しかし、ここで気になるのが… Javaから投げたクエリが本当に正しいのか? という問題です。
特にElasticsearchの検索処理は、想定通りの結果が得られないことも多々あります。
自分の想定とは全く異なる検索クエリが送られ、間違った処理が実行されている可能性も無きにしも非ず…です。

こうした場合、Java から Elasticsearch に送った検索クエリ内容を、JSON形式でログ出力 すれば、調査がしやすくなります。

その具体的な方法について、今回テストコードを書いて確認してみました。


予備知識

手順説明にあたって必要な、Elasticsearch Java API 関連の前提知識です。

RestHighLevelClient

SearchRequest

1
2
3
4
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

SearchSourceBuilder

1
2
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));

QueryBuilders

SearchResponse


ログ出力手順

1. Elasticsearch のインデックス作成

ログ出力手順の確認にあたり、Elasticsearch に テスト用インデックスを作成しました。

Kibana で以下のコマンドを実行します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
PUT /animals/

POST /animals/_doc/1
{
  "name" : "タカ",
  "type" : "鳥類",
  "description" : "猛禽類、空の王者"
}

POST /animals/_doc/2
{
  "name" : "トラ",
  "type" : "哺乳類",
  "description" : "肉食動物、森林の覇者"
}

POST /animals/_doc/3
{
  "name" : "バッタ",
  "type" : "昆虫",
  "description" : "驚きの跳躍力、個人的にオンブバッタが好き"
}

GET /animals/_search で、ドキュメントが作成されたことを確認します。問題無さそうです。

animalsインデックスを作成
animalsインデックスを作成

2. build.gradleファイルの編集

Eclipse で新規のGradleプロジェクトを作成して、Elasticsearch Java API をインポートします。

公式ドキュメント「Maven Repository | Java REST Client [master] | Elastic」 の記載内容を参考に、build.gradle の設定項目として、以下の repositories / dependencies を追記しました。

1
2
3
repositories {
    maven { url "https://snapshots.elastic.co/maven/" }
}
1
2
3
dependencies {
    compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.12.1'
}

デフォルトの設定を少しだけ修正。最終的に以下のような形に落ち着きました。

※ログ出力用に Logback もインポートしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
plugins {
    id 'java-library'
}

repositories {
    jcenter()
    maven { url "https://snapshots.elastic.co/maven/" }
}

dependencies {
    api 'org.apache.commons:commons-math3:3.6.1'
    implementation 'com.google.guava:guava:28.2-jre'
    testImplementation 'junit:junit:4.12'
    compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.12.1'
    compile 'ch.qos.logback:logback-classic:1.1.3'

}

3. Java側でログを出力

EsLogTest.java ソースコード

一通りの準備ができたので、Javaのソースコードを書いていきます。

今回はログ出力の手順確認がしたいだけなので、mainメソッドだけの簡潔な構成になっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package test;

import java.io.IOException;

import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EsLogTest {

    private static final Logger log = LoggerFactory.getLogger(EsLogTest.class);

    public static void main(String[] args) {

        log.info("【ログ出力 開始】");
        try (RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")))) {

            //クエリを組み立てる
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.termQuery("name", "タカ"));
            SearchRequest searchRequest = new SearchRequest().indices("animals").source(sourceBuilder);

            //検索クエリをログ出力
            log.info(sourceBuilder.toString());

            //Esにクエリを投げ、検索結果を取得
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            //検索結果をログ出力
            log.info(searchResponse.toString());

            log.info("【ログ出力 終了】");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

解説

クエリ作成の大元となる SearchSourceBuilder に対して toString() を実行すると、検索クエリの文字列 (JSON形式) が取得可能 です。

あとは、この文字列を Logback等でログ出力すれば、今回の目標が達成できます!

27
28
29
30
31
32
33
//クエリを組み立てる
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("name", "タカ"));
SearchRequest searchRequest = new SearchRequest().indices("animals").source(sourceBuilder);

//検索クエリをログ出力
log.info(sourceBuilder.toString());

今回のテーマから若干逸れますが、検索処理を実行後のレスポンスの内容も、おまけでログ出力してみました。

SearchResponse 形式で実行結果を受け取れるので、それに対して toString() を実施。String化・ログ出力します。

35
36
37
38
39
//Esにクエリを投げ、検索結果を取得
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

//検索結果をログ出力
log.info(searchResponse.toString());

出力されたログ

上記の EsLogTest.java をコンパイル&実行すると、以下のようなログが出力されました。

3行目 (黄色ハイライトの箇所) に、お目当ての検索クエリがありました!
ちゃんとJSON形式で出力されていますね。

1
2
3
4
5
14:24:44.306 [main] INFO  test.EsLogTest - 【ログ出力 開始】
ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...
14:24:46.071 [main] INFO  test.EsLogTest - {"query":{"term":{"name":{"value":"タカ","boost":1.0}}}}
14:24:46.288 [main] INFO  test.EsLogTest - {"took":1,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":0.9808291,"hits":[{"_index":"animals","_type":"_doc","_id":"1","_score":0.9808291,"_source":{"name":"タカ","type":"鳥類","description":"猛禽類、空の王者"}}]}}
14:24:46.288 [main] INFO  test.EsLogTest - 【ログ出力 終了】

上の例では、2行目の箇所で Log4j2 関連のエラーが発生しています。
コレについては、build.gradle に以下のような dependencies を追記したところ、解消されました。

1
2
3
dependencies {
    implementation 'org.apache.logging.log4j:log4j-core:2.14.1'
}

出力された検索クエリのJSONを整形して、Kibanaで実行してテストしてみます。

すると、Javaのログで出力されたレスポンス内容 (5行目) と、同一の結果が確認できました。

検索クエリの構成も問題無さそう
検索クエリの構成も問題無さそう

最後に

以上、Elasticsearch Java API の検索クエリをJSON形式でログ出力するための手順説明でした。

SearchSourceBuilder を String文字列に変換して、ログ出力する だけ。非常にシンプルです。

また、今回は調査が及びませんでしたが、Elasticsearch Java API は、ドキュメントの更新・削除等の処理も用意されています。
こうした操作に関しても、クエリ内容をログ出力するための方法が用意されている…ハズだと思われます。

今後も継続して調査を行い、最適な方法を編み出せたら、弊ブログで連携したいと思います。それではまた!


参考リンク