1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elastic Stack (Elasticsearch) Advent Calendar 2023Advent Calendar 2023

Day 9

Elasticsearchで日付周りをPainlessを使ってうまい具合にハンドリングする

Last updated at Posted at 2023-12-08

Elasticsearchは内部的にUTCで時間を管理しています。そのためその日付情報を使ってユーザーとのタッチポイントとなる箇所、例えばアラートメールなどに表示する時刻もUTCになってしまって、日本時間がわかりにくいということがたまにあります。たまにとは言っても年に数回は日付のフォーマットについて調べている気がするので、ここでまとめておきます。

基本的にはZonedDateTimeでパースして、あとはうまいこと表示します。

検索時にRuntime Field

検索結果に対してダイナミックにRuntime Filedを生成する方式。一番現実的かつ効率的。これが使えるケースはこれで良いでしょう。以下の例ではtimestamp_jpフィールドをruntime_mappingsとして作成しています。

Painless的にはdocオブジェクトから取得して、処理結果はemitする、というのがこのコンテキストでのポイントです。

GET myindex/_search
{
  "runtime_mappings": {
    "timestamp_jp": {
      "type": "keyword",
      "script": {
        "source": """
        ZonedDateTime zdt = ZonedDateTime.parse(doc['@timestamp'].value.toString());
        zdt = zdt.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        String formattedString = zdt.format(formatter);
        emit(formattedString);
        """
      }
    }
  },
  "_source": {"excludes": "*"}, 
  "fields": [
    "@timestamp", "timestamp_jp"
  ]
}

レスポンス

{
  "hits": {
    "hits": [
      {
        "fields": {
          "@timestamp": [
            "2023-11-10T07:04:15.000Z"
          ],
          "timestamp_jp": [
            "2023/11/10 16:04:15"
          ]
        }
      },
      {
        "fields": {
          "@timestamp": [
            "2023-11-10T07:04:23.000Z"
          ],
          "timestamp_jp": [
            "2023/11/10 16:04:23"
          ]
        }
      }
      ...
    ]
  }
}

Ingest Pipelineで日本時間をフィールドに持たせてしまう

日付を表示する際に適切なゾーンに変換して表示できれば一番美しいですが、必ずしもどこでもそれができるわけではありません。例えばSecurity Alertなどでこれをやるのは現状難しいです。そこでもうインデックスに表示用のデータを文字列方で作ってしまう方法がこちらです。

Ingest Pipeline内で日付データをパース、フォーマットして新しいフィールドを作成します。以下はパイプラインのシミュレーションのみ実施しています。

Painless的にはctxオブジェクトに対してフィールド操作する、というのがこのコンテキストでのポイントです。

POST _ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
        "script": {
          "lang": "painless",
          "source": """
            def timestamp = ctx['@timestamp'].toString();
            ZonedDateTime zdt = ZonedDateTime.parse(timestamp);
            zdt = zdt.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
            String formattedString = zdt.format(formatter);
            ctx['timestamp_jp'] = formattedString;
          """
        }
      }
    ]
  },
  "docs": [
    {
      "_source": {
        "@timestamp": "2023-12-05T02:34:06.000Z"
      }
    }
  ]
}

以下のような結果になります。(関係ないフィールドは消してます)

{
  "docs": [
    {
      "doc": {
        "_source": {
          "timestamp_jp": "2023/12/05 11:34:06",
          "@timestamp": "2023-12-05T02:34:06.000Z"
        }
      }
    }
  ]
}

WatcherのActionで処理する場合

Elasticsearch界隈では根強い人気のWatcherですが、本気で取り組むといつも大変な作業になる印象がありますね。。とはいえ日付の変換処理は当然他と同じです。search inputからのデータはctx.payload.hits.hits.0._sourceなどから取得します。ここで0は検索結果配列のインデックス(番号)です。

{
  "trigger": { "schedule": { "interval": "30m" } },
  "input": {
    "search": {
      "request": {
        "body": {
          "size": 1,
          "query": { "match_all": {} }
        },
        "indices": [ "*" ]
      }
    }
  },
  "condition": { "always": {} },
  "transform": {
    "script" : """
    def timestamp = ctx.payload.hits.hits.0._source['@timestamp'];
    ZonedDateTime zdt = ZonedDateTime.parse(timestamp);
    zdt = zdt.withZoneSameInstant(ZoneId.of('Asia/Tokyo'));
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern('yyyy/MM/dd HH:mm:ss');
    String formattedString = zdt.format(formatter);
    return [
      'timestamp': timestamp,
      'timestamp_jp' : formattedString
    ];
    """
  },
  "actions": {
    "my-logging-action": {
      "logging": {
        "text": "@timestamp: {{ctx.payload.timestamp}}, timestamp_jp: {{ctx.payload.timestamp_jp}}"
      }
    }
  }
}

このWatcherでは以下のような文字列がログに出力されます。

@timestamp: 2023-12-05T06:32:29.143Z, timestamp_jp: 2023/12/05 15:32:29

まとめ

というわけで様々なコンテキストで日付フォーマットを変換する方法を紹介しました。Painlessでの変換について書いているので当然ながら変換処理内容は変わらないのですが、元になるデータへのアクセス方法が違うのでいつも迷子になります。今後は自分でもこの記事を見直すようにします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?