ククログ

株式会社クリアコード > ククログ > GitHub上のアクティビティを定点観測してみませんか?

GitHub上のアクティビティを定点観測してみませんか?

先日、fluent-plugin-github-activitiesSharetaryという2つのソフトウェアをリリースしました。 これらを組み合わせると、GitHub上の個々人の活動をウォッチして簡単に共有することができます。

実際にどのように動作するのか、ClearCode Inc. Organizationに所属しているアカウントの公開アクティビティを収集するSharetaryの運用サンプルをご覧下さい。 (ちなみに、この運用サンプルは別のエントリで紹介したデモンストレーション用のDroongaクラスタをデータの格納先として利用しています。)

この仕組みは、SEゼミの2015年度の取り組みを支援するために開発しました(実は、先日のリーダブルコード勉強会でも会場の片隅で試験運用していました)。 今年のSEゼミではOSS Hack Weekendと題して、学生の皆さんに実際のOSSプロジェクトに参加してもらう予定ですが、Sharetaryの画面を会場内のスクリーンに出しておくことで、イベントの中で実際にどのような開発が行われているかを会場内で目に見える形で共有できますし、プルリクエストが採用されればその様子が一目で分かるというわけです。

GitHub上のアクティビティをクローリングするfluent-plugin-github-activities

fluent-plugin-github-activitiesは、GitHub上の公開アクティビティをクローリングすることに特化したFluentdプラグインです。 以下のような<source>を定義しておくと、設定で列挙したユーザの公開アクティビティを一定間隔で取得して、新規に取得したアクティビティの情報をFluentdのストリームに流すようになります。

<source>
  type github-activities
  access_token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  users ashie,co-me,cosmo0920,hayamiz,hhatto,kenhys,kou,min-shin,monkey-mas,mooz,okkez,piroor,ridec,s-yata,t2y
  clients 4
  interval 1
  base_tag github-activity.
  pos_file /var/log/td-agent/github-activities.json
</source>

この時レコードとして流れるのは、Events APIの個々のEventです。 どんな種類のアクティビティがあってどんな情報を取得できるのかは、GitHubのAPIドキュメントを参照して下さい。

このプラグインの働きとしてはただそれだけの物なので、Sharetary以外にも様々な用途に流用できます。 例えば社内チャットにアクティビティを自動投稿するというのも、1つの使い方でしょう。

使用の際の注意点として、OAuthのアクセストークンを取得して設定ファイルに書いておく必要があるという点が挙げられます。 GitHub APIは手順に則って取得したアクセストークンを使わない場合、クロールの頻度が最大で60リクエスト毎時までに制限されてしまいます(アクセストークンがあれば5000リクエスト毎時までリクエストできます)。 fluent-plugin-github-activitiesはpushに対応するアクティビティを見付けたときはそれに含まれる各commitの詳細も個別に取得するようになっているため、アクセストークン無しの状態だとすぐにリクエスト過多と判断されてしまいます。

収集されたイベントをタイムライン&アーカイブ表示するSharetary

Sharetaryというのは「Share(共有)」と「Secretary(書記、秘書)」の組み合わせによる造語です。 バックエンドとしてはGroongaまたはDroongaのHTTPサーバを利用する想定になっており、所定の形式に則って定義された「イベント情報」のテーブルに対して検索を行って、新たに増えたイベントのレコードをTwitterなどのタイムライン風にリアルタイム表示することができます。

また、タイムライン表示とは別に、アーカイブ表示というモードもあります。 このモードでは、その時点までに取得済みのイベントのレコードをTogetterのように時系列順で表示することができます。 あるイベントに関連付けられたイベント群はスレッド風に表示されるので、タイムライン表示では追いにくい議論やコメントのやり取りなども、このビューであれば俯瞰して見る事ができます。

なお、Sharetaryで表示するための個々のイベントのレコードには「返信先URI」にあたるフィールドを持たせることができ、例えばGitHub上での「コミット」に対応するイベントに対して「コミットに対するコメントの入力フォーム」のURIを返信先として紐付けておけば、SharetaryのUI上で「このイベントに返信する」というような操作を行った際にそのコメント入力フォームへ遷移させることができます。 そうして追加されたコメントはfluent-plugin-github-activitiesによってクロールされ、Sharetaryのタイムライン上に表れることになります。 このように、Sharetaryはそれ自体に固有の情報をなるべく保持せずに、公開の情報だけをソースとして運用できる設計となっています。

fluent-plugin-github-activitiesとSharetaryの連携

Sharetary自体はデータをクロールする仕組みを持っていないため、fluent-plugin-github-activitiesのように何らかのクローラと併用することが前提になります。 冒頭でも紹介した実際の運用例は、以下の図のような構成になっています。

(droonga1の上に、サービスを提供しているSharetaryと、クローリングしているFluentdが存在する)

この図を見ると、Sharetaryとfluent-plugin-github-activities(Fluentd)は、共通のデータベースとしてDroongaクラスタにアクセスしてはいますが、互いに独立して動作している事が見て取れるでしょう。

fluent-plugin-github-activitiesは収集したアクティビティの情報をそのままレコードとして流すだけなので、それだけではSharetaryの情報ソースには使えません。 実際には、それらのアクティビティをSharetary用の適切なイベント形式のレコードに変換し、fluent-plugin-groongaを使ってSharetary用のデータベースにloadさせる必要があります。

Fluentdでのレコードの形式変換に使えるプラグインはいくつかありますが、元レコードの情報を使って全く新しいレコードを組み立てるという用途には、fluent-plugin-mapが適しているようです。 例えば、「Issueを新たに作成した」というアクティビティをfluent-plugin-github-activitiesがクロールしてきた際に、それを使ってSharetaryのイベントとなるレコードを組み立てる(GitHubのアクティビティをSharetaryのイベントに変換する)ルールは以下のように書けます。

<match github-activity.issue-open>
  type map
  tag "\"sharetary.\" + tag"
  time time
  record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "type" => "issue-open", "source_icon" => "https://github.com/favicon.ico", "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "actor_class" => "major", "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>

fluent-plugin-mapでは変換ルールを文字列として定義しますが、Fluentdの設定ファイルでは文字列型の設定値を改行できないため、このように横長の記述になってしまっています。

基本的にはこの要領でSharetaryに表示させたいアクティビティ用の変換ルールを定義していくのですが、typeがタグと同じ内容だったり、各ルールでactor_classsource_iconが同じだったりする場合、この段階でフィールドを定義するよりも、後でまとめて設定した方がスッキリしますし、今後の変更も容易になります。 レコードのタグなどを使って追加のフィールドを定義するfluent-plugin-record-reformarプラグインを使うと、これらの共通フィールドは以下のようにして一気に設定できます。

<match github-activity.issue-open>
  type map
  tag "\"sharetary.\" + tag"
  time time
  # type, actor_class, source_iconはこの段階では埋めていない
  record (require("time") || true) && { "_key" => record["payload"]["issue"]["html_url"], "class" => "major", "scope" => record["repo"]["name"], "scope_icon" => record["$github-activities-related-organization-logo"], "title" => "Open", "description" => "Opened new issue: " + "#" + record["payload"]["issue"]["number"].to_s + " " + record["payload"]["issue"]["title"] + ": " + (record["payload"]["issue"]["body"] || ""), "actor" => record["actor"]["login"], "actor_uri" => "https://github.com/#{record["actor"]["login"]}/", "actor_icon" => record["$github-activities-related-avatar"], "uri" => record["payload"]["issue"]["html_url"], "reply_uri" => "#{record["payload"]["issue"]["html_url"].split("#").first}#new_comment_field", "created_at" => Time.now.to_i, "timestamp" => Time.parse(record["created_at"]).to_i, "parent" => nil }
</match>

<match sharetary.github-activity.**>
  type record_reformer
  enable_ruby false
  tag completed.${tag}
  <record>
    type ${tag_parts[2]}
    actor_class major
    source_icon https://github.com/favicon.ico
  </record>
</match>

こうして組み立てたレコードは、fluent-plugin-groongaを使ってGroongaまたはDroongaに格納します。 以下はfluent-plugin-groonga用の設定例です。

<match completed.sharetary.**>
  type groonga
  store_table Events

  protocol http
  host droonga0

  buffer_type file
  buffer_path /var/spool/td-agent/buffer/groonga
  flush_interval 1s

  <table>
    name Events
    flags TABLE_PAT_KEY
    key_type ShortText
  </table>

  <table>
    name Timestamps
    flags TABLE_PAT_KEY
    key_type Time
  </table>

  <table>
    name Terms
    flags TABLE_PAT_KEY
    key_type ShortText
    default_tokenizer TokenBigram
    normalizer NormalizerAuto
  </table>

  <mapping>
    name type
    type ShortText
  </mapping>

  <mapping>
    name class
    type ShortText
  </mapping>

  <mapping>
    name title
    type ShortText
    <index>
      table Terms
      name Events_title_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name description
    type Text
    <index>
      table Terms
      name Events_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name extra_description
    type Text
    <index>
      table Terms
      name Events_extra_description_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name scope
    type ShortText
    <index>
      table Terms
      name Events_scope_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name scope_icon
    type ShortText
  </mapping>

  <mapping>
    name uri
    type ShortText
    <index>
      table Terms
      name Events_uri_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name source_icon
    type ShortText
  </mapping>

  <mapping>
    name reply_uri
    type ShortText
  </mapping>

  <mapping>
    name actor
    type ShortText
    <index>
      table Terms
      name Events_actor_index
      flags WITH_POSITION
    </index>
  </mapping>

  <mapping>
    name actor_uri
    type ShortText
  </mapping>

  <mapping>
    name actor_icon
    type ShortText
  </mapping>

  <mapping>
    name actor_class
    type ShortText
  </mapping>

  <mapping>
    name timestamp
    type Time
    <index>
      table Timestamps
      name Events_timestamp_index
    </index>
  </mapping>

  <mapping>
    name created_at
    type Time
    <index>
      table Timestamps
      name Events_created_at_index
    </index>
  </mapping>

  <mapping>
    name parent
    type ShortText
    <index>
      table Terms
      name Events_parent_index
      flags WITH_POSITION
    </index>
  </mapping>
</match>

この構成においてはdroonga0droonga1のそれぞれでFluentdが動作していますが、Sharetary関係の物はすべてdroonga1に置いておくという方針で、今回はdroonga1にもfluent-plugin-groongaをインストールして、そこからSharetary用のイベントレコードをloadするようにしています。

Issueの作成以外の各種アクティビティも含めた変換ルールの設定例の完全版は、Sharetaryのリポジトリに含まれていますので、参考にしてみて下さい。 ……というか、認証のための情報やfluent-plugin-groongaの接続先ホスト以外の全ての設定は、上記運用例でもこれをそのまま使っています。 皆さんも、試してみる時はこの設定をスタート地点として、ご自分のニーズに合うように微調整していくとよいでしょう。

ところで、上記の説明からも分かる通り、適切なイベント形式のレコードに変換さえできるのなら、Sharetaryの情報ソースはGitHubのアクティビティでなくても構いません。 この設定例は、イベント同士を関連付けたり、イベントの種類に応じてSharetaryの画面上での目立たせ方を変えたりといった事のサンプルにもなっていますので、他の情報ソースを使う場合の参考になるのではないでしょうか。

まとめ

以上、最近開発・公開したfluent-plugin-github-activitiesSharetaryという2つのソフトウェアの概要と使い方について簡単にご紹介しました。

この両者の組み合わせは利用形態の一例に過ぎないとはいえ、現時点では最も代表的な利用形態でもあります。 GitHubを開発の場所として利用するハッカソン形式のイベントを開催される際は、会場内での賑やかしとして、またイベント期間中の出来事を後から振り返る際のログとして、是非利用してみてください。

また、Sharetaryの運用例としてクリアコード関係者の公開アクティビティの定点観測所についてもご案内しました。 クリアコードが日常の業務の中でOSSやフリーソフトウェアを開発・公開したり既存プロジェクトに協力したりしている様子を俯瞰してご覧頂けますので、弊社オフィスに遊びに来られるような感覚でフラッと覗いてみて頂ければ幸いです。