つよくなるブログ

その場にとどまるためには、全力で走り続けなければならない

Nginx のアクセスログを Embulk で PostgreSQL に入れて分析する

モチベーション

  • SQL でアクセスログを集計可能にして「今週のアクセス数ランキング」的なページを作成するため
  • AWS であればマネージドサービスを使って CloudWatch -> Kinesis -> RDS でも実現可能であるが、汎用的に使える Embulk (or Fluentd) を触ってみたかったため

環境

  • Ubuntu 18.04

Nginx のログフォーマットを変更

log_format embulk '"$http_x_forwarded_for" "[$time_local]" "$request_method" "$request_uri" '
                  '$status $body_bytes_sent $request_time "$http_referer" '
                  '"$http_user_agent"';

access_log /var/log/nginx/access.log embulk;

Nginx のアクセスログを tsv 形式に変換

$ cat /var/log/nginx/access.log | nkf --url-input | perl -ne 'print join("\t", /^"(.*?)" "\[(.*?)\]" "(.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" "(.*?)"/), "\n"' | grep -v '^\s*$' > access.log.csv

1. nkf --url-input

/search?q=検索 の URL にアクセスしたときにアクセスログ上には
URI エンコードされて /search?q=%E6%A4%9C%E7%B4%A2 になっていたのでデコード

2. print join("\t", /^"(.?)" "[(.?)]" "(.?)" "(.?)" (.?) (.?) (.?) "(.?)" "(.*?)"/), "\n"'

アクセスログを tsv 形式に変換

3. grep -v '^\s*\$'

2 で指定した tsv 形式に一致しないアクセスログは空白行になるので削除

Embulk の環境設定

Embulk は Java で動作するアプリケーションなので Java をインストール

$ sudo apt-get install openjdk-8-jre
$ java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-8u242-b08-0ubuntu3~18.04-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)

Embulk をインストール

$ curl --create-dirs -o ~/.embulk/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar"
$ chmod +x ~/.embulk/bin/embulk
$ echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
$ embulk -version
embulk 0.9.23

Embulk の設定ファイルを生成

seed.yml を作成
アクセスログから作成した .csv ファイルのパスを指定

in:
  type: file
  path_prefix: "./access.log.csv"
out:
  type: stdout

embulk guess コマンドで config.yml を作成

$ embulk guess seed.yml -o config.yml
in:
  type: file
  path_prefix: ./access.log.csv
  parser:
    charset: UTF-8
    newline: LF
    type: csv
    delimiter: "\t"
    quote: '"'
    escape: '"'
    trim_if_not_quoted: false
    skip_header_lines: 0
    allow_extra_columns: false
    allow_optional_columns: false
    columns:
    - {name: c0, type: string}
    - {name: c1, type: timestamp, format: '%d/%b/%Y:%H:%M:%S %z'}
    - {name: c2, type: string}
    - {name: c3, type: string}
    - {name: c4, type: long}
    - {name: c5, type: long}
    - {name: c6, type: double}
    - {name: c7, type: string}
    - {name: c8, type: string}
out: {type: stdout}

生成されたカラム名をアクセスログの内容に合わせて修正

in:
  type: file
  path_prefix: ./access-log.csv
  parser:
    charset: UTF-8
    newline: LF
    type: csv
    delimiter: "\t"
    quote: '"'
    escape: '"'
    trim_if_not_quoted: false
    skip_header_lines: 0
    allow_extra_columns: false
    allow_optional_columns: false
    default_timezone: Asia/Tokyo
    columns:
    - {name: ip, type: string}
    - {name: time, type: timestamp, format: '%d/%b/%Y:%H:%M:%S %z'}
    - {name: request_method, type: string}
    - {name: request_uri, type: string}
    - {name: status_code, type: long}
    - {name: body_bytes_sent, type: long}
    - {name: request_time, type: double}
    - {name: referer, type: string}
    - {name: user_agent, type: string}
out: {type: stdout}

embulk-output-postgresql をインストール

Embulk 本体はデータを読み込んで変換を行う基本機能のことを指すので
input, output で利用する各サービスに合わせプラグインのインストールが必要になる

$ embulk mkbundle bundle

./bundle/Gemfile が作成されるので embulk-output-postgresql を追記

source 'https://rubygems.org/'

gem 'embulk'
gem 'embulk-output-postgresql' # 追記

./bundle 配下で embulk bundle コマンドを実行してプラグインをインストール

$ embulk bundle

PostgreSQL の設定追記

設定ファイル内で環境変数を利用可能にするため config.ymlconfig.yml.liquid に変更

$ mv config.yml config.yml.liquid

config.yml.liquidout: 以下を変更
PostgreSQL への接続情報は環境変数から取得しテーブル名は access_logs とする場合の例

in:
  type: file
  path_prefix: ./access-log.csv
  parser:
    charset: UTF-8
    newline: LF
    type: csv
    delimiter: "\t"
    quote: '"'
    escape: '"'
    trim_if_not_quoted: false
    skip_header_lines: 0
    allow_extra_columns: false
    allow_optional_columns: false
    default_timezone: Asia/Tokyo
    columns:
    - {name: ip, type: string}
    - {name: time, type: timestamp, format: '%d/%b/%Y:%H:%M:%S %z'}
    - {name: request_method, type: string}
    - {name: request_uri, type: string}
    - {name: status_code, type: long}
    - {name: body_bytes_sent, type: long}
    - {name: request_time, type: double}
    - {name: referer, type: string}
    - {name: user_agent, type: string}
out:
  type: postgresql
  host: {{ env.DB_HOST }}
  user: {{ env.DB_USER }}
  password: "{{ env.DB_PASSWORD }}"
  database: {{ env.DB_NAME }}
  table: access_logs
  mode: insert

Embulk を実行

$ embulk run -b bundle config.yml.liquid

テーブルが存在しない場合は自動で生成され、tsv 形式のアクセスログが PostgreSQL へ登録される

参考リンク

ローカル環境でも快適に LIFF の開発ができる liff-sdk-local を作りました

なぜ作ったのか

LIFF(LINE Front-end Framework) は SSL 対応した URL を元に LIFF URL(line://app/xxx)を生成することで使えるようになります。
その仕様で動作確認のしずらさを感じることがよくあり、どうしたらもっと開発効率があがるかなと考えていました。

解決策その1として liff-cli を去年作り、1年で約5,600回!ダウンロード(npm install liff)されています。

github.com

LIFF のリリース当初(2018/6) は LIFF URL の生成が LINE Messaging API を直接実行するしかなかったため、毎回ドキュメントの curl サンプルをコピペして実行していました。
今では LINE Developers Console にて LIFF を管理できる機能が追加されてたため、LIFF URL の生成は楽になりました。

LIFF URL 生成は楽になったものの
そもそも LIFF URL を生成せずに LIFF の開発できないんだっけ?
と考えて今回の liff-sdk-local を作りました。

github.com

なにが変わるのか

liff-sdk-local を使うことで次のように LIFF 開発が変わります!

今までの LIFF 開発
  1. LIFF SDK を導入、実装
  2. SSL 対応のサーバーにファイルをアップロード or ngrok などを使ってローカル環境を外部公開
  3. 2 で生成された URL を LINE Developers Console or LINE Messaging API を使い LIFF URL を生成
  4. 3 を LINE に送信して動作確認
これからの LIFF 開発
  1. LIFF SDK と liff-local-sdk を導入、実装
  2. ローカル環境で動作確認

liff-sdk-local 対応 API

以下の LIFF API に対応しています。

liff-sdk-local 使い方

  1. GitHub リポジトリより最新版をダウンロードしてください。
  2. dist/liff-sdk-local.min.js をプロジェクトに追加し、本家の LIFF SDK をコメントアウトしてください。
<!-- <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script> -->
<script src="path/to/liff-sdk-local.min.js"></script>
<script>
   liff.init(function(data) {
       // 処理...
   });
</script>

これで LIFF URL を生成せずにローカル環境で LIFF の動作確認ができます。

戻り値をカスタマイズする

各 API の戻り値はデフォルトでは LIFF API のドキュメントのサンプルと同じ値が返ります。

戻り値を変更するには window.liffSettings に設定してください。

<script>
   window.liffSettings = {
       userId: 'U12345',
   };
</script>
<!-- <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script> -->
<script src="path/to/liff-sdk-local.min.js"></script>
<script>
   liff.init(function(data) {
       console.log(data.context.userId); // 出力: U12345
   });
</script>

設定可能な戻り値は以下です。

  • userId
  • displayName
  • pictureUrl
  • statusMessage
  • accessToken
  • language
  • type
  • utouId
  • groupId
  • roomId
  • viewType

おまけ

9/22 に開催された技術書典 7 にて販売された LINE API HANDBOOK で第 4 章「LIFF 活用事例と開発 Tips の紹介」を書かきました!

booth.pm

Sequelize PostgreSQL での SSL 接続設定(と主要オプションの紹介)

Heroku でホスティングしている PostgreSQL に
Sequelize から接続しようとしたときに SSL 必須のエラーが出てた.

エラーは解決はしたもののドキュメントに詳しい説明がなく
調査したときに関連する Sequelize のコードを読んだでわかったことをまとめた.

まずはエラー対策の結論

環境.dialect.dialectOptions.ssl: true にすると
SSLでの接続が可能になる.

{
    "production": {
        "dialect": "postgres",
        "dialectOptions": {
            "ssl": true
        }
    }
}

dialectOptions とは?

Sequelize のドキュメントには dialectOptions にどのようなパラメータが設定できるか
といった記述はない.

Sequelize は MySQL は mysql2, PostgreSQL は pg といったように
内部的には DB への接続部分に各 Node.js 実装のライブラリを使用している.

dialectOptions についてドキュメントに記述がないのは
dialectOptions は各ライブラリへそのまま設定値を受け渡すのが役割なためである.

主要な dialectOptions の設定項目

とはいえ今回の ssl のように
主要なパラメータのドキュメントはあった方が親切だと感じたので 調査していく過程で見つけたものをまとめておく.

PostgreSQL

application_name

pg_stat_activity で表示されるログに表示される名前を設定する.
これを使うことで DB 側で接続元ごとの状況を確認することが可能になる.

PostgreSQL: Documentation: 9.3: Error Reporting and Logging

ssl

true で DB に SSL 接続を行う.

client_encoding

接続する際の文字エンコーディングを指定する.
auto を指定すると接続元の環境をそのまま利用する.
Unix システムであれば LC_CTYPE の値を利用.

keepAlive

true でkeepAlive を有効にする.

statement_timeout

クエリをタイムアウトさせる時間(ミリ秒)

ドキュメントにミスみつけたので PR 出した

マージされた🎉

github.com

まとめ

  • SSL 必須のエラーを解決した.
  • Sequelize のコードを読んで主要な dialectOptions をまとめた.
  • ↑を追記する PR もまとめて出そう.

Amazon ECS で Mautic を動かす

OSS の MAツールである Mutic を動かすことがあり
公式が用意している Docker イメージを使ってローカルマシンでは簡単にセットアップできた.

その後サーバーに載せる際に Amazon ECS を使ってセットアップした後
Mautic コンテナに接続するところまでの手順をまとめておく.

前提

以下の設定は済んでいること前提に Mautic を動作させることにフォーカスする.

  • AWS の設定(認証ファイル, EC2 キーペア作成など)
  • ecs-cli のインストール
  • docker-compose 実行環境

Mautic をローカルマシンで起動

github.com

上記のを元に 80 ポートで立ち上がるように修正したのが以下のgist.

gist.github.com

docker-compose.yml として保存して docker-compose up で起動.

http://localhost/ で Mautic が表示される.

続いて Amazon ECS で動かす設定へ.

Amazon ECS で動かす

初期設定

$ ecs-cli configure --region ap-northeast-1 --cluster mautic

--region: ECS を設定するリージョン
--cluster: クラスタ名

今回はクラスタ名を mautic とする.

クラスタ作成

$ ecs-cli up --keypair KEY_NAME --capability-iam --size 1 --instance-type t3.medium

--keypair: 使用する EC2 キーペア
--size: クラスタに登録するインスタンス数
--instance-type: EC2 インスタンスタイプ

t2.micro で試したところメモリ不足のエラーがでたので t3.medium で作成.

INFO[0015] (service mautic) was unable to place a task because no container instance met all of its requirements. 
The closest matching (container-instance xxxxx) has insufficient memory available. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide.

コンテナ起動

$ ecs-cli compose -f docker-compose.yml up

コンテナに接続

Mautic の言語設定を日本語にしたものの, キャッシュが原因で変更されなかったので
コンテナに接続して対応していく.

EC2 の DNS を特定して ssh 接続

  1. AWS コンソールで Amazon ECS を開く
  2. クラスターを選択
  3. 対象のクラスター名を選択(今回は mautic)
  4. ECS インスタンスのタブを選択
  5. EC2 インスタンスのインスタンスID(i-から始まる文字列)を選択
  6. おなじみの EC2 の画面でパブリック DNS が特定できる
  7. ssh接続
$ ssh ec2-user@ec2-xxx.amazonaws.com -i KEY_NAME

Docker コンテナに接続

EC2 の中で Docker コンテナが起動しているので特定して接続する.

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                NAMES
xxx        mautic/mautic                    "/entrypoint.sh apac…"   1 days ago         Up 1 days          0.0.0.0:80->80/tcp   ecs-mautic-1-mautic-xxx
xxx        mysql:5.6                        "docker-entrypoint.s…"   1 days ago         Up 1 days          3306/tcp             ecs-mautic-1-mauticdb-xxx
xxx        amazon/amazon-ecs-agent:latest   "/agent"                 1 days ago         Up 1 days                               ecs-agent

mautic, mysql, amazon-ecs-agent の 3つのコンテンが立ち上がっている.

mautic の NAMES に対して以下のコマンドを実行すると
mautic が動いているコンテナに接続できる.

$ docker exec -it ecs-mautic-1-mautic-xxx bash

/var/www/html/app/cache/prod 配下のキャッシュを削除して
設定が反映されていることが確認できた.

まとめ

  • Mautic を Amazon ECS を使ってサーバに載せた.
  • Mautic のメール設定は Amazon SES を使えるためその後の連携が楽にできる.

LINE Messaging API の replyToken は何秒で無効になるのか検証

モチベーション

LINE Messaging API の公式ドキュメントでは
replyTokenの失効期限は一定期間と記載されており明確な数値がなく
正確に検証したいと思ったため.

応答できるイベントには応答トークンが発行されます。 応答トークンは一定の期間が経過すると無効になるため、メッセージを受信したらすぐに応答を返す必要があります。

developers.line.biz

結論

30秒

検証方法

検証用のシンプルな LINE ボットを開発.
express + line-bot-sdk-nodejs を使って heroku にデプロイ.
ソースコードは以下リポジトリ.

github.com

ボットの仕様

  • 数字(n)を送信数すると n 秒後に replyMessage を送信
  • replyMessage が送信できなかった場合は pushMessage でエラー内容を送信
  • 数字以外は数字を送信するように促す

検証内容

f:id:morugu:20190714152702p:plain
検証内容

まとめ

  • Messaging API の仕様を検証するためのボットを作った!
  • 30秒以下であれば replyMessage で返信が可能
  • 思っていたより期限が長かった!