Kibanaを用いたSlackの統計情報の可視化と全文検索

本記事は[Slack Advent Calendar 2017](https://qiita.com/advent-calendar/2017/slack)の1日目の記事です。

# 概要
Kibanaを用いてSlackの過去ログから統計情報の可視化と全文検索を行う方法を紹介します。
Slackの過去ログをELKのような全文検索エンジンに入れることで、以下のメリットが得られます。

1. Slackの提供していない統計情報の可視化
2. 無料版Slackのメッセージ数制限を超えて全ての過去ログから全文検索

本記事では、簡単のためDockerでELKスタックを立ち上げ、自作したスクリプトでSlackの過去ログを流し込み、Kibanaから可視化と全文検索を行う方法を解説します。

# Slackの過去ログのexport
**過去ログのexportは、workspaceのadminのみが行えます**

Slackの過去ログは https://my.slack.com/services/export からzip形式でダウンロードできます。無料版でもメッセージ数の制限なく全てのメッセージがダウンロードできますが、プライベートチャンネルやDMは含まれていません。

複数のworkspaceに所属していて、上記URLでは目的のworkspaceのページにリダイレクトされなかった場合には "my." の部分を目的のworkspaceのサブドメインに変える必要があります。

# ELKスタックを立てる
ELKスタックは全部入りのDocker Composeもあり、これを用いればコマンド一発で立ち上げることができます。Dockerで立てた場合、通常の方法で立てた場合よりもパフォーマンスや拡張性、安定性において劣るものの、今回はワンタイムの用途なので立ち上げと処分の手軽さのためDockerを使う方法を紹介します。今回は [docker-elk](https://github.com/deviantony/docker-elk) を用いる方法を解説します。

## docker-elk

### 基本設定
docker-elkのリポジトリをGitHubからcloneしてきます。

```bash
$ git clone https://github.com/deviantony/docker-elk
```

設定を少し変更します。デフォルトのヒープサイズは少ないのでこれを増やし、Logstashは今回不要なのでその部分を消します。

```diff:docker-compose.yml
diff --git a/docker-compose.yml b/docker-compose.yml
index c923261..21beb31 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,27 +13,10 @@ services:
       - "9200:9200"
       - "9300:9300"
     environment:
-      ES_JAVA_OPTS: "-Xmx256m -Xms256m"
+      ES_JAVA_OPTS: "-Xmx4g -Xms4g"
     networks:
       - elk

-  logstash:
-    build:
-      context: logstash/
-      args:
-        ELK_VERSION: $ELK_VERSION
-    volumes:
-      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml
-      - ./logstash/pipeline:/usr/share/logstash/pipeline
-    ports:
-      - "5000:5000"
-    environment:
-      LS_JAVA_OPTS: "-Xmx256m -Xms256m"
-    networks:
-      - elk
-    depends_on:
-      - elasticsearch
-
   kibana:
     build:
       context: kibana/
```

### kuromoji analysis pluginを入れる
Elasticsearchの日本語形態素解析プラグインであるkuromojiを入れます。これは、後述する日本語の全文検索やタグクラウドを作る上で役に立ちますが、統計情報を可視化したいだけであれば入れる必要はないので、飛ばしても問題ありません。

ElasticsearchのDockerfileに以下の一行を加えます。

```diff:elasticsearch/Dockerfile
diff --git a/elasticsearch/Dockerfile b/elasticsearch/Dockerfile
index f2f4f56..b08bc18 100644
--- a/elasticsearch/Dockerfile
+++ b/elasticsearch/Dockerfile
@@ -5,3 +5,5 @@ FROM docker.elastic.co/elasticsearch/elasticsearch-oss:${ELK_VERSION}

 # Add your elasticsearch plugins setup here
 # Example: RUN elasticsearch-plugin install analysis-icu
+
+RUN elasticsearch-plugin install analysis-kuromoji
```

最後に docker-elk を立ち上げます。

```bash
$ docker-compose up
```

以下のコマンドでhealth checkをして、statusがredになっていたりしなければ大丈夫です。

```bash
$ curl 'localhost:9200/_cat/health?v'
epoch      timestamp cluster        status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1511987763 20:36:03  docker-cluster yellow          1         1      1   1    0    0        1             0                  -                 50.0%
```

# ログをElasticsearchに流し込む
ExportしたSlackの過去ログzipの中身は、チャンネルごとにディレクトリに分けられたjsonファイル群です。これを一つ一つパースし、少し加工してElasticsearchにbulk indexしていくスクリプト
 (https://github.com/shinkbr/slack-export-shipper) を書きました。これを使って流し込んでいきます。

```bash
$ git clone https://github.com/shinkbr/slack-export-shipper.git
$ cd slack-export-shipper
$ bundle install
```

## kuromoji用のmappingを設定する
*kuromojiを使わない場合、この手順は不要です。*
kuromojiで形態素解析を行う場合、最初のindexを作る前に解析対象のフィールドに対するmappingを設定する必要があります。先程cloneしたリポジトリに同梱の、以下のjsonファイルをPUTすることでmappingを設定します。

```json:template/slack.json
{
  "template": "slack-*",
  "settings":{
    "index": {
      "analysis":{
        "analyzer":{
          "kuromoji":{
            "type":"custom",
            "tokenizer":"kuromoji_tokenizer",
            "char_filter": [
              "url_filter",
              "mention_filter"
            ],
            "filter":[
              "ja_stop",
              "kuromoji_baseform",
              "pos_filter"
            ]
          }
        },
        "filter": {
          "pos_filter": {
            "type": "kuromoji_part_of_speech",
            "stoptags": [
              "助詞-並立助詞",
              "助詞-係助詞",
              "助詞-副助詞／並立助詞／終助詞",
              "助詞-副詞化",
              "助詞-接続助詞",
              "助詞-格助詞",
              "助詞-格助詞",
              "助詞-格助詞-一般",
              "助詞-格助詞-引用",
              "助詞-格助詞-連語",
              "助詞-終助詞",
              "助詞-連体化",
              "助詞-間投助詞",
              "助動詞",
              "動詞-接尾",
              "動詞-非自立",
              "接続詞"
            ]
          }
        },
        "char_filter": {
          "url_filter": {
            "type": "pattern_replace",
            "pattern": "<http[^>]+>",
            "replacement": ""
          },
          "mention_filter": {
            "type": "pattern_replace",
            "pattern": "<@[a-zA-Z0-9]+>",
            "replacement": ""
          }
        }
      }
    }
  },
  "mappings": {
    "slack-message": {
      "dynamic_templates":[
        {
          "texts": {
            "match_mapping_type": "string",
            "match": "text",
            "mapping": {
              "type": "text",
              "analyzer": "kuromoji",
              "fielddata": true
            }
          }
        }
      ]
    }
  }
}
```

- `stoptags` の部分は(多分)アグレッシブに品詞をstripしていますが、調整の余地があると思います。
- `kuromoji_baseform` を使うことで、文章中の活用された単語を基本形で検索できるようになります。 (「行く」で「行った」を検索するなど)
- `url_filter` と `mention_filter` はメッセージ中の `<http://...>` や `<@U12345678>` のようなURLとメンションを集計と検索から除くもので、後に記述するタグクラウドの見栄えを意識して入れたものですが、filterしない方が検索の上では望ましいかもしれません。

先程cloneしたslack-export-shipperのルートディレクトリにて以下のコマンドを実行し、上記ファイルをPUTします。

```bash
$ curl -X PUT 'localhost:9200/_template/slack' -H 'Content-Type:application/json' -d @template/slack.json
```

`{"acknowledged":true}` というレスポンスが返って来ればOKです。

## 流し込む
最初にダウンロードしたログzipを適当なディレクトリにunzipします。

```bash
$ unzip -d /path/to/unzip your_slack_log.zip
```

先程cloneしたslack-export-shipperというリポジトリのルートディレクトリにて以下を実行します。 `/path/to/unzip` は先程unzipしたディレクトリです。

```bash
$ bundle exec rake ship logdir=/path/to/unzip
```

これを行うと、Elasticsearchに `slack-YYYY.MM.DD` という命名規則のindexが作成されます。
ログのメッセージ数やホストのスペック次第ですが、これには数十分から数時間かかる可能性があります。

何もエラー無くrakeタスクの実行が終われば、あとはブラウザからKibanaをポチポチするだけです。

# Kibanaで検索・可視化を行う
*※以下に示す画像では、チャンネル名およびユーザー名はランダムな地名や人物名に変更してあります*

ブラウザで http://localhost:5601 にアクセスして、Kibanaを触っていきます。

## Index patternの設定
初回アクセス時に "Configure an index pattern" と聞かれたら Index patternに
 `slack-*` , Time filter field nameに `@timestamp` を指定します。

## 検索
Kuromojiをインストールしていれば、日本語が綺麗に形態素に分割されて検索されます。また、本記事の設定だと、 `kuromoji_baseform` フィルタにより活用形の単語も基本形で検索してヒットするようになっています。

<img width="1280" alt="search.png" src="https://qiita-image-store.s3.amazonaws.com/0/53690/9415fd27-484e-31fe-6d57-bce8f198f58a.png">

## 可視化
Kibanaでは様々な方法で可視化が行えます。例えば、以下は一人のユーザーのチャンネルごとの投稿数の棒グラフ / 時系列グラフ、時間毎 / 曜日ごとの投稿数の積み上げ棒グラフ、チャンネルごとの投稿の割合の円グラフです。 

<img width="1280" alt="per_user.png" src="https://qiita-image-store.s3.amazonaws.com/0/53690/d450b05b-21df-61d1-c1c7-69bda0a23200.png">

もちろん、一人のユーザーではなくworkspace全体の統計情報もグラフ化することができます。

<img width="1280" alt="all_channels.png" src="https://qiita-image-store.s3.amazonaws.com/0/53690/2b425959-b379-4a4f-c317-c974f3e1de90.png">

また、kuromojiを設定していれば以下のように比較的綺麗なタグクラウドを作ることができます。
以下の例は、「示し合わせて東京都内でご飯を食べよう」という主旨のチャンネルから作られたタグクラウドです。

<img width="877" alt="tokyo.png" src="https://qiita-image-store.s3.amazonaws.com/0/53690/0993f142-cde8-c563-8ff6-a8cc5896c71b.png">

# まとめ
ELKスタックはセットアップからデータのindex、可視化までが簡単に行えます。今回はDockerを使用する方法を紹介しましたが、Elasticの公式リポジトリをパッケージマネージャに追加してインストールするのも同じぐらいに簡単です。もしもSlackの過去ログなどの手頃なデータがあれば、一度遊んでみてはいかがでしょうか。