全文検索システムFessのジョブログをCLIで取得したい

# 全文検索システムFessって何
[オープンソース全文検索サーバー Fess](https://fess.codelibs.org/ja/)
内部でElasticsearchを使っている全文検索システム。
全文検索＝ファイルの中身本文、メタデータ等も含めてまるごと検索すること。
Fessが指示屋でElasticsearchがDB役と検索担当みたいな感じ。
商用の全文検索システムと比較しても遜色のない、軽くて早くてうまい優れもの。
(Fessも[商用サポートサービス](https://fess.codelibs.org/ja/support-services.html)がある)


# 解決したい事柄
一晩に一回ファイルサーバをなめさせて検索DBの情報を更新している(クロールと呼ぶ)。
このジョブの開始終了時刻等のステータスはウェブの管理画面から見られるのだけど、日々の確認でいちいち管理画面開くのめどい。レポートにする時見て書き写すのもコピペるのもめどい。
クロール終了時のメール通知機能はあるのだが、そのためにメールサーバを準備するのはめどい。
というわけで、内部のどこかに格納されているジョブログの元からデータ引っ張り出して都合よく扱いやすくするスクリプトを書いてなんとかする。



# 環境
環境の都合上、Windows(PowerShell)を使う。
Windows10 1903
PowerShell 5.1
Fess 12.6.2
Elasticsearch 6.7.2


# 方針
- ジョブログが格納されているインデックスを特定する。
- マッピングを確認し目的のフィールドを探し出す。
- 目的のデータを読み出すJSONを書く。
- 読みだしたデータを扱いやすいよう加工する。


# ジョブログが格納されているインデックス
```powershell
(Invoke-WebRequest http://localhost:9200/_cat/indices?v"&"s=index).content
```

インデックス一覧を確認するいつものコード。PowerShellでこのように`&`を含めたい場合`"&"`と書かなければならないのはお約束。
一覧から目星をつけて`.fess_config.job_log`に目的のログが入っていることを確認した。


# 目的のフィールドを探し出す
```powershell
(Invoke-WebRequest http://localhost:9200/.fess_config.job_log/_mapping?pretty).content
```

マッピングを確認するいつものやつ。結果は省略。
properties以下にある`startTime`、`endTime`が目的のフィールドだ。


# 目的のデータを読み出すJSON

```json:特定のログだけ読み出すJSON
{
    "size":5,
    "query":{
        "match":{
            "jobName":"Default Crawler"
        }
    },
    "sort":[
        {"startTime":{"order":"desc"}}
    ]
}
```

同インデックスにはいろんなジョブログが入っているので、そのうち`Default Crawler`ログだけを抽出する。
読み出すフィールドを限定するかどうかは悩みどころ。この程度のデータ量なら全部とってきて後でフィルタしたほうが便利なのだけど、全部とってきて後で～はアンチパターンのダークサイドへ近づく道な気もする…。


# 読みだしたデータを扱いやすいよう加工する

## UnixTimeから日本時間への変換

問合せを投げてやれば結果一覧がオブジェクトの形で返ってくる。あとはよしなに…とそう簡単にはいかない。格納されているデータは`UnixTime`なのだ。1599400800000なんて返されても人間にはわからないので、タイムゾーンも考慮して変換しなければならない。

変換にあたっては以下の記事にあるコードを参考にした。丁寧に説明されていてわかりやすい。
記事中では秒を想定し`AddSeconds`メソッドを使用しているところ、UnixTimeはミリ秒なので`AddMilliSeconds`メソッドを使用した。
[【PowerShell】Unix時間を人が読めるようにする](https://buralog.jp/powershell-unixtime-readable/)

```powershell:UnixTimeから日本時間へ変換
Function Get-DateFromUnixTime($FessUnixTime){
    [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1970/1/1').AddMilliSeconds($FessUnixTime))
}
```

[timezone]::CurrentTimeZone はC#の書き方らしい。PowerShellで細かいことしようとするとたいていC#か.NETの話が出てくるな。
ちなみに上で例に出した UnixTime 1599400800000 は 2020年9月6日23:00:00 である。

## 時刻の差分を求める

ジョブの処理に何時間かかったか？も一緒に求めておきたいため、差分を取るコードを書く。

```powershell:開始終了時刻と時刻の差分
# $RespはElasticsearchへ問い合わせた結果が入っている

$Obj = $Resp.hits.hits._source |
select jobName,startTime,endTime

$Obj | ForEach-Object{
    $_.startTime = Get-DateFromUnixTime($_.startTime)
    $_.endTime = Get-DateFromUnixTime($_.endTime)
    $TimeSpan = ($_.endTime - $_.startTime).ToString("hh'時間'mm'分'ss'秒'")
    $_ | Add-Member -MemberType NoteProperty -Name "timeSpan" -Value $TimeSpan
}
```

`$Resp.hits.hits._source`は問い合わせた際のお約束で、ここに結果一覧が入っている。だいたいこれをWhere-ObjectやSelect-ObjectでフィルタしForEach-Objectで回すことになる。

`$TimeSpan`の所で時刻の差分を求めている。DateTime型である`$_.endTime`と`$_.startTime`の差を求めると、TimeSpan型が返るのだそうだ。
[DateTimeとTimeSpanをWindows PowerShellであれこれ](https://rh-kimata.hatenablog.com/entry/2017/12/13/235858)

> DateTimeオブジェクト同士を減算するとTimeSpanオブジェクトが戻り値になります。

TimeSpan型そのままだとこれまた人間の目では読みづらいので、ToStringメソッドで時間表記に直している。


# 出来たPSスクリプト

```powershell:Fessのジョブログ開始終了時刻を読み出す
Function Get-DateFromUnixTime($FessUnixTime){
    [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1970/1/1').AddMilliSeconds($FessUnixTime))
}

$IndexName = ".fess_config.job_log"
$Body = @'
{
    "size":5,
    "query":{
        "match":{
            "jobName":"Default Crawler"
        }
    },
    "sort":[
        {"startTime":{"order":"desc"}}
    ]
}
'@

$Param = @{
    ContentType = "application/json"
    Method = "Post"
    Uri = "http://localhost:9200/${IndexName}/_search/"
    Body = $Body
}
$Resp = Invoke-RestMethod @Param

$Obj = $Resp.hits.hits._source |
Select-Object jobName,startTime,endTime

$Obj | ForEach-Object{
    $_.startTime = Get-DateFromUnixTime($_.startTime)
    $_.endTime = Get-DateFromUnixTime($_.endTime)
    $TimeSpan = ($_.endTime - $_.startTime).ToString("hh'時間'mm'分'ss'秒'")
    $_ | Add-Member -MemberType NoteProperty -Name "timeSpan" -Value $TimeSpan
}

$Obj | Format-Table
```

```console:結果
jobName         startTime           endTime            timeSpan  
-------         ---------           -------            --------  
Default Crawler 2020/09/06 23:00:00 2020/09/07 5:48:58 06時間48分58秒
Default Crawler 2020/09/05 23:00:00 2020/09/06 5:32:11 06時間32分11秒
Default Crawler 2020/09/04 23:00:00 2020/09/05 5:34:28 06時間34分28秒
Default Crawler 2020/09/03 23:00:00 2020/09/04 5:46:35 06時間46分35秒
Default Crawler 2020/09/02 23:00:00 2020/09/03 5:40:22 06時間40分22秒
```

あとはファイル出力するなり、時刻の差分チェックして7時間以上かかってたら通知するなり、いろいろ。