LoginSignup
18
18

More than 5 years have passed since last update.

slackのログをAWS(DynamoDB)にためて、検索できるようにした

Posted at

> chatOps的な話ではなく、slack のことでお願いします。

すみません、書く直前に知りました。。
でも、カレンダー全然埋まってないし、chatOps的なこと書いてる人他にもいるので強行します。
ごめんなさい。

はじめに

slack【有料版】使ってますか?でしたらこの記事はあなたには特に必要ないです。

無料版の制約

有料版にどんな素晴らしい機能があるのかよくわかりませんが、無料版で特に不自由していません。
ただ、1つだけ、過去ログが消えてしまう、という点を除いては。

どうするか?

slackでの発言が消えてしまう前に、全部DBに溜め込んで、そこから検索させればいいじゃない。

構成

slackでのHubotとの連携の記事は腐るほどあるので、省きます。
構成は、こんな感じでやりました。

  • 理想
    • client > 自鯖(Go aws-sdk) > DynamoDB <> CloudSearch ← AWS Management Console から検索
  • 現在
    • client > 自鯖(Go aws-sdk) > DynamoDB ← aws-sdk(javascript) で検索

EC2使えばいいじゃない、という話ですが、自前の環境があったのでそちらを使いました。
golangは書いてみたかったので選択しました。

本当は理想の通り、DynamoDB→CloudSearchにデータを入れて、AWSのManagementConsoleから全文検索させてかったのですが、
DynamoDB→CloudSearchにデータを定期的に入れる方法と、いまいちCloudSearchの使い方を理解できておらず
結局のところ、DynamoDBをLIKE検索する方法で落ち着いています。

slackでの発言をDynamoDBにInsert
(slackでの発言を受信して、↓のプログラムに渡してます。hubotスクリプトは省きます)

dynamodb.go
package main

// Import the AWS SDK for Go
import (
    "log"
    "flag"
    "time"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

/**
 * Don't hard-code your credentials!
 * Export the following environment variables instead:
 *
 * export AWS_ACCESS_KEY_ID='AKID'
 * export AWS_SECRET_ACCESS_KEY='SECRET'
**/

func main() {

    var account = flag.String("a", "NULL", "help message for a")
    var date_time = flag.String("t", time.Now().Format("2006-01-02 15:04:05"), "help message for t")
    var channel = flag.String("c", "", "help message for c")
    var message = flag.String("m", "", "help message for m")
    flag.Parse()

    svc := dynamodb.New(session.New(&aws.Config{Region: aws.String("ap-northeast-1")}))
    input := &dynamodb.PutItemInput{
        TableName: aws.String("slack_log"),
        Item: map[string]*dynamodb.AttributeValue{
            "id": &dynamodb.AttributeValue{
                S: aws.String(*account),
            },
            "created": &dynamodb.AttributeValue{
                S: aws.String(*date_time),
            },
            "channel": &dynamodb.AttributeValue{
                S: aws.String(*channel),
            },
            "message": &dynamodb.AttributeValue{
                S: aws.String(*message),
            },
        },
    }

    _, err2 := svc.PutItem(input)
    if err2 != nil {
        log.Println(err2)
        return
    }
}

DynamoDBをWebブラウザから検索

  • 検索条件
    • チャンネル名(=)
    • 発言(LIKE)
    • 発言日時(BETWEEN)
dynamodb.js
function search_slack() {
    var attrValues = [];
    var attrNames = [];
    var filter = '';

    var channel = document.getElementById("search_channel").value;
    var message = document.getElementById("search_message").value;
    var start = document.getElementById("search_date_start").value;
    var end = document.getElementById("search_date_end").value;

    var dynamodb = new AWS.DynamoDB({
        dynamodb: '2012-08-10',
        region: 'ap-northeast-1',
        accessKeyId: 'hogehoge',
        secretAccessKey: 'fugafuga',
    });

    var params = {
        TableName: 'slack_log',
    };

    if(channel != '') {
        attrNames['#channel'] = 'channel';
        attrValues[':val1'] = {S: channel};
        filter = 'contains (#channel, :val1)';
    }

    if(message != '') {
        attrNames['#message'] = 'message';
        attrValues[':val2'] = {S: message};
        if(filter != '') filter += ' AND ';
        filter += 'contains (#message, :val2)';
    }

    if(start != '' && end == '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: start + ' 00:00:00'};
        if(filter != '') filter += ' AND ';
        filter += '#created >= :val3';
    }

    if(start == '' && end != '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: end + ' 23:59:59'};
        if(filter != '') filter += ' AND ';
        filter += '#created <= :val3';
    }


    if(start != '' && end != '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: start + ' 00:00:00'};
        attrValues[':val4'] = {S: end + ' 23:59:59'};
        if(filter != '') filter += ' AND ';
        filter += '#created BETWEEN :val3 AND :val4';
    }

    if(filter != '') {
        params['ExpressionAttributeNames'] = attrNames;
        params['ExpressionAttributeValues'] = attrValues;
        params['FilterExpression'] = filter;
    }

    dynamodb.scan(params, function(err, data) {
        if (err) {
            console.log(err, err.stack); // an error occurred
        } else {
            console.log(data);           // successful response
            $('#result').empty();
            items = data.Items;
            for (var i = items.length - 1; i >= 0; i--) {
                var item = items[i]
                $('#result').append('<div>' + item.channel.S + '</div>');
                $('#result').append('<div>' + item.created.S + '</div>');
                $('#result').append('<div>' + item.id.S + '</div>');
                $('#result').append('<div>' + item.message.S + '</div>');
                $('#result').append('<br />');
            };
        }
    });

}

課題

  • 発言が"編集"された場合に、検知不可
  • 発言が”削除”された場合も、検知不可
    • 新規発言はbotが拾ってくれるのですが、上記は拾ってくれないようです
  • Public, Private 一即多に収集
    • 発言を拾うには、channelにbotを招待しなくてはいけません
    • Private channelにbotを招待した結果、それを誰もが検索できていいのか?あまりよろしくないと思います
    • 誰がどのchannelに参加していて、検索権限があるか、というところまで考えるとだいぶ大変です。。
    • 結局、そんなクリティカルな話しをしているわけではないので、誰でも検索可能なchannelのみbotを招待するようにしています

最大の課題

  • DMが検知不可
    • DMは1対1のやり取りなので、botを招待することができません
    • やるとしたら、botを含んだ3人のPrivateChannelを作成する必要がありますが、面倒臭すぎる・・

DMは別に検索できなくても良くない?と、言われたので、とりあえず諦めました。。

でも・・

DMの利用率だいぶ高いです\(^o^)/(うちのチームでは平均70%がDM)

結局

きちんとログを検索したければ、有料会員になったほうがいいです
もしもっといい方法があれば教えて下さいε≡≡ヘ( ´Д`)ノ

18
18
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
18
18