リアルタイムでログを分析できることは、生産アプリケーションにとって非常に重要です。ClickHouseがログデータの保存と分析に優れているかどうか、考えたことはありますか? ELKからClickHouseへのログインフラの移行に関するUberの経験をチェックしてみてください。
このガイドでは、人気のデータパイプラインVectorを使用して、Nginxのログファイルをテールし、それをClickHouseに送信する方法を示します。以下のステップは、任意の種類のログファイルをテールする際にも似ています。すでにClickHouseが稼働しており、Vectorがインストールされていると仮定します(まだ起動する必要はありません)。
1. データベースとテーブルの作成
ログイベントを保存するためのテーブルを定義します:
- 新しいデータベース 
nginxdb から始めます: 
CREATE DATABASE IF NOT EXISTS nginxdb
 
- 初めは、全ログイベントを単一の文字列として挿入します。明らかにこれはログデータの分析に適した形式ではありませんが、マテリアライズドビューを使用してその部分を後で解決します。
 
CREATE TABLE IF NOT EXISTS  nginxdb.access_logs (
    message String
)
ENGINE = MergeTree()
ORDER BY tuple()
 
注記
現時点では主キーは本当に必要ありませんので、ORDER BYは**tuple()**に設定されています。
 
Nginxの詳細をあまり説明するつもりはありませんが、すべての詳細を隠すつもりもありませんので、このステップではNginxのログ設定を行うために十分な詳細を提供します。
- 次の 
access_log プロパティは、combined形式でログを /var/log/nginx/my_access.log に送信します。この値は、nginx.conf ファイルの http セクションに入ります: 
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    access_log  /var/log/nginx/my_access.log combined;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}
 
- 
nginx.conf を変更した場合は、必ずNginxを再起動してください。
 
- 
ウェブサーバーのページを訪問して、アクセスログにいくつかのログイベントを生成します。combined形式のログは次のような形式を持っています:
 
192.168.208.1 - - [12/Oct/2021:03:31:44 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
192.168.208.1 - - [12/Oct/2021:03:31:44 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
192.168.208.1 - - [12/Oct/2021:03:31:49 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
 
Vectorは、ログ、メトリクス、およびトレース(ソースと呼ばれます)を収集、変換し、多くの異なるベンダー(シンクと呼ばれます)にルーティングします。ClickHouseとの標準的な互換性があります。ソースとシンクは、vector.tomlという設定ファイルで定義します。
- 次の vector.toml は、my_access.log の末尾をテールする fileタイプのソースを定義し、上記で定義した access_logs テーブルを シンクとして定義しています:
 
[sources.nginx_logs]
type = "file"
include = [ "/var/log/nginx/my_access.log" ]
read_from = "end"
[sinks.clickhouse]
type = "clickhouse"
inputs = ["nginx_logs"]
endpoint = "http://clickhouse-server:8123"
database = "nginxdb"
table = "access_logs"
skip_unknown_fields = true
 
- 
上記の設定を使用してVectorを起動します。ソースとシンクの定義についてはVectorのドキュメントを参照してください。
 
- 
アクセスログがClickHouseに挿入されているかどうかを確認します。次のクエリを実行すると、テーブルにアクセスログが表示されるはずです:
 
SELECT * FROM nginxdb.access_logs
 
4. ログの解析
ClickHouseにログがあるのは素晴らしいことですが、各イベントを単一の文字列として保存すると、あまりデータ分析ができません。マテリアライズドビューを使用してログイベントを解析する方法を見てみましょう。
- マテリアライズドビュー(MVの略)は、既存のテーブルに基づく新しいテーブルであり、既存のテーブルに挿入が行われると、新しいデータもマテリアライズドビューに追加されます。access_logsにログイベントの解析された表現を含むMVを定義する方法を見てみましょう、言い換えれば:
 
192.168.208.1 - - [12/Oct/2021:15:32:43 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
 
ClickHouseには文字列を解析するためのさまざまな関数がありますが、初めに見てみるべきはsplitByWhitespaceです。これは、空白で文字列を解析し、各トークンを配列として返します。デモンストレーションのために、次のコマンドを実行します:
SELECT splitByWhitespace('192.168.208.1 - - [12/Oct/2021:15:32:43 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"')
 
返答は私たちが欲しいものにかなり近いことに気付いてください!いくつかの文字列には余分な文字があり、ユーザーエージェント(ブラウザの詳細)は解析する必要がありませんでしたが、次のステップでそれを解決します:
["192.168.208.1","-","-","[12/Oct/2021:15:32:43","+0000]","\"GET","/","HTTP/1.1\"","304","0","\"-\"","\"Mozilla/5.0","(Macintosh;","Intel","Mac","OS","X","10_15_7)","AppleWebKit/537.36","(KHTML,","like","Gecko)","Chrome/93.0.4577.63","Safari/537.36\""]
 
- splitByWhitespaceに似て、splitByRegexp関数は正規表現に基づいて文字列を配列に分割します。次のコマンドを実行すると、2つの文字列が返されます。
 
SELECT splitByRegexp('\S \d+ "([^"]*)"', '192.168.208.1 - - [12/Oct/2021:15:32:43 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"')
 
返された2番目の文字列は、ログから正常に解析されたユーザーエージェントです:
["192.168.208.1 - - [12/Oct/2021:15:32:43 +0000] \"GET / HTTP/1.1\" 30"," \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36\""]
 
- 最終的なCREATE MATERIALIZED VIEWコマンドを確認する前に、データをクリーンアップするために使用されるいくつかの関数を見てみましょう。たとえば、
RequestMethod は "GET という不要な二重引用符を持っています。次の trim 関数を実行すると、二重引用符を削除できます: 
SELECT trim(LEADING '"' FROM '"GET')
 
- 時間文字列には先頭に角括弧があり、ClickHouseが日付に解析できる形式でもありません。しかし、区切り文字をコロン(:)からカンマ(,)に変更すれば、解析がうまくいきます:
 
SELECT parseDateTimeBestEffort(replaceOne(trim(LEADING '[' FROM '[12/Oct/2021:15:32:43'), ':', ' '))
 
- マテリアライズドビューを定義する準備が整いました。私たちの定義には POPULATE が含まれており、これにより access_logs の既存の行がすぐに処理されて挿入されます。次のSQL文を実行します:
 
CREATE MATERIALIZED VIEW nginxdb.access_logs_view
(
    RemoteAddr String,
    Client String,
    RemoteUser String,
    TimeLocal DateTime,
    RequestMethod String,
    Request String,
    HttpVersion String,
    Status Int32,
    BytesSent Int64,
    UserAgent String
)
ENGINE = MergeTree()
ORDER BY RemoteAddr
POPULATE AS
WITH
    splitByWhitespace(message) as split,
    splitByRegexp('\S \d+ "([^"]*)"', message) as referer
SELECT
    split[1] AS RemoteAddr,
    split[2] AS Client,
    split[3] AS RemoteUser,
    parseDateTimeBestEffort(replaceOne(trim(LEADING '[' FROM split[4]), ':', ' ')) AS TimeLocal,
    trim(LEADING '"' FROM split[6]) AS RequestMethod,
    split[7] AS Request,
    trim(TRAILING '"' FROM split[8]) AS HttpVersion,
    split[9] AS Status,
    split[10] AS BytesSent,
    trim(BOTH '"' from referer[2]) AS UserAgent
FROM
    (SELECT message FROM nginxdb.access_logs)
 
- 正常に機能したことを確認します。アクセスログがきれいにカラムに解析されて表示されるはずです:
 
SELECT * FROM nginxdb.access_logs_view
 
注記
上記のレッスンではデータを2つのテーブルに保存しましたが、最初の nginxdb.access_logs テーブルを Null テーブルエンジンを使用するように変更することもできます - 解析されたデータは依然として nginxdb.access_logs_view テーブルに入りますが、生のデータはテーブルに保存されません。
 
まとめ: シンプルなインストールと迅速な設定を必要とするVectorを使用することで、NginxサーバーからClickHouseのテーブルにログを送信できます。巧妙なマテリアライズドビューを使用することで、これらのログをカラムに解析し、より簡単に分析できるようになります。