Filebeatでアプリの独自のログをパースしてElasticsearchに送りたい

ファイルのパーミッションの問題でWSLでFilebeatが使えなかったので、Windows版で。

ログファイル my_app.log。

2020-07-02 08:00:00 成功した
2020-07-02 10:01:12 ダメだった
2020-07-02 20:03:04 こんにちは

↑このログに↓このインデックスをつけたい。

フィールド
@timestamp 時間
myapp.success "成功"のときtrue、"ダメ"のときfalse、それ以外はnull
event.dataset myapp.log

filebeat.yml

filebeat.inputs:
 - type: log
   enabled: true
   paths:
    - ./my_app.log
   encoding: utf-8
   processors:
    - script:
       lang: javascript
       id: my_log_parser
       source: >
         function process(event) {
           var m = event.Get("message").match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.*)/);
           if (m !== null) {
             var success = null;
             if (m[2].match(/成功/)) success = true;
             if (m[2].match(/ダメ/)) success = false;
             event.Put('myapp', { time: m[1], success: success });
             event.Put('event.dataset', 'myapp.result');
           }
         }
    - timestamp:
       field: myapp.time
       layouts:
        - '2006-01-02 15:04:05'
       timezone: '+0900'

output.console:
  enabled: true
  pretty: true

#output.elasticsearch:
#  hosts: ["localhost:9200"]

パースの処理を javascript で書けるのは便利ですな。.jsファイルに分けることもできるので、複雑になってもデバッグしやすそう。

output は console に。Elasticsearch に流す前にコンソールで確認できる。

テスト用に test.bat を作った。ログファイルの読み込み位置が保存されている data.json を毎回削除するようにして、繰り返し実行しても常に新しいファイルとして処理されるように。
(もっとちゃんとしたやり方があるかもしれないけどとりあえず)

> del data\registry\filebeat\data.json
> filebeat -v --path.config %~dp0 --path.data %~dp0data --path.logs %~dp0log

実行結果 (長いので関係ない @metadata, agent, host, log, input,ecs は削除)

{
  "@timestamp": "2020-07-01T23:00:00.000Z",
  "message": "2020-07-02 08:00:00 成功した",
  "myapp": {
    "time": "2020-07-02 08:00:00",
    "success": true
  },
  "event": {
    "dataset": "myapp.result"
  },
}
{
  "@timestamp": "2020-07-02T01:01:12.000Z",
  "message": "2020-07-02 10:01:12 ダメだった",
  "myapp": {
    "time": "2020-07-02 10:01:12",
    "success": false
  },
  "event": {
    "dataset": "myapp.result"
  },
}
{
  "@timestamp": "2020-07-02T11:03:04.000Z",
  "message": "2020-07-02 20:03:04 こんにちは",
  "myapp": {
    "time": "2020-07-02 20:03:04",
    "success": null
  },
  "event": {
    "dataset": "myapp.result"
  },
}

期待通りに @timestamp と myapp.success が設定された。

Kibana でログ確認。
f:id:gae:20200702140119p:plain

フィールドを success ではなく myapp.success としたのは、既存のフィールドとの衝突を避けるため。
もしも、success というフィールドがすでに存在していて number 型だったりすると、bool は入れられないので reject されてしまう。
↓ここに共通フィールドのスキーマがあるので、目的にあうものがあればそれを使うか、インデックス自体をアプリ専用に作った方がよさげ。

インデックスを分ける方法は、こんな感じでいけるみたい。

output.elasticsearch:
  hosts: ["localhost:9200"]
  indices:
    - index: "myapp-log-%{+yyyy.MM.dd}"
      when.equals:
        event.dataset: 'myapp.result'

まだほとんどまともに使ってないけど、javascriptでパースの処理を書けるのは学習コストが低くて良いですな。

dockerのnginxのコンテナのログをfilebeatでElasticsearchに送りたい

前回、 Docker → Fluentd → Elasticsearch の流れでログを送ってみたけど、やっぱログはファイルとしてホストに保存しつつ、コピーを Elasticsearch にも送りたいと思うようになった。
いろんな方法があるみたいだけど、Filebeat が楽そうだった。


これ使うと簡単な設定で nginx のログを Elasticsearch に送って、pipelineでパースしてインデックスしてくれる。

- module: nginx
  access:
    enabled: true
    var.paths: ["/path/to/log/nginx/access.log*"]
  error:
    enabled: true
    var.paths: ["/path/to/log/nginx/error.log*"]

ただ、dockerのログは /var/lib/docker/container/{id}/{id}.json みたいなパスだし、中身がjsonになっているので、

var.paths: ["/var/lib/docker/container/*/*.json"]

みたいなことをしても、nginxのログとして扱われないので期待通りにパースしてくれない。
なので、以下のサイトに書かれてるようにする。

filebeat.autodiscover:
 providers:
   - type: docker
     templates:
       - condition:
         contains:
           docker.container.image: nginx
         config:
           - module: nginx
             access:
               input:
                 type: docker
                 containers.ids:
                   - "${data.docker.container.id}"
             error:
               input:
                 type: docker
                 containers.ids:
                   - "${data.docker.container.id}"

コンテナのイメージ名に "nginx" が含まれているものを nginx モジュールを使って処理する
これなら自分でヘンな作り込みしなくても簡単にログを送って、ちゃんとインデックスもつくので楽ですな。

dockerのnginxのコンテナのログをfluentdでパースして送りたい

最近docker使い始めて、ログの扱いに困り、fluentdで他のサーバに送ることを知り、Elasticsearchで保存し、Kibanaで閲覧することを覚えたばかり。

docker から fluentd でログを送ると、nginx の access.log の行が log フィールドにそのまま入ってきて、パースされていないので扱いにくいので、パースされた状態で送りたいと単純に思ったが、何しろ使い始めたばかりでいろいろ勘違いしているかもしれない。

docker run --rm --name nginx -p 80:80 --log-driver fluentd --log-opt tag="MY_APP_NAME.nginx" nginx

などとして nginx を起動して送り先を fluentd にし、fluentd側から nginx コンテナの出力だとわかるように tag を設定。
これで

curl localhost

とすると、fluentd にはこんなログが記録される。

fluentd_1 | 2020-06-26 13:55:31.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "log":"172.17.0.1 - - [26/Jun/2020:04:55:31 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.55.1\" \"-\""}

この log フィールドをパースしたい。

curl localhsot/hoge

などと、存在しないリソースを要求すると、こう。

fluentd_1 | 2020-06-26 13:55:35.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stderr", "log":"2020/06/26 04:55:35 [error] 28#28: *11 open() \"/usr/share/nginx/html/hoge\" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: \"GET /hoge HTTP/1.1\", host: \"localhost\""}

fluentd_1 | 2020-06-26 13:55:35.000000000 +0900 MY_APP_NAME.nginx: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "log":"172.17.0.1 - - [26/Jun/2020:04:55:35 +0000] \"GET /hoge HTTP/1.1\" 404 153 \"-\" \"curl/7.55.1\" \"-\""}

nginx イメージのデフォルトでは access.log は /dev/stdout で、error.log は /dev/stderr になっていて、ログの source フィールドで判別できる。
この情報を使って fluent.conf でパースできるように設定したい。

rewrite-tag-filter が必要だったのでインストール。

gem install fluent-plugin-rewrite-tag-filter

fluent.conf で *.nginx のタグのうち source が stdout のものは *.nginx-access に、stderr のものは *.nginx-error にタグをrewriteする。
そして、そのタグを目印に filter で parse する。

<source>
    @type forward
    @label @mainstream
</source>

<label @mainstream>
    <match *.nginx>
        @type rewrite_tag_filter
        <rule>
          key source
          pattern /^stdout$/
          tag ${tag}-access
        </rule>
        <rule>
          key source
          pattern /^stderr$/
          tag ${tag}-error
        </rule>
    </match>
    
    <filter *.nginx-access>
      @type parser
      key_name log
      reserve_data true
      remove_key_name_field true
      <parse>
        @type nginx
        keep_time_key true
      </parse>
    </filter>
    
    <filter *.nginx-error>
      @type parser
      key_name log
      reserve_data true
      remove_key_name_field true
      <parse>
        @type multiline
        format_firstline /^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[\w+\] (?<pid>\d+).(?<tid>\d+): /
        format1 /^(?<time>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(?<log_level>\w+)\] (?<pid>\d+).(?<tid>\d+): (?<message>.*)/
        keep_time_key true
      </parse>
    </filter>
    
    <match>
      @type stdout
    </match>
</label>

nginx-error の parse で使っている正規表現は fluentd の古いドキュメントに載っていたものをそのまま使用。

この設定でログを処理すると、access.log の内容は nginx-access タグ、error.log の内容は nginx-error タグをつけて、パースした状態のログを転送できた。

fluentd_1 | 2020-06-26 13:36:39.000000000 +0900 MY_APP_NAME.nginx-access: {"source":"stdout", "container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "remote":"172.17.0.1", "host":"-", "user":"-", "time":"26/Jun/2020:04:36:39 +0000", "method":"GET", "path":"/", "code":"200", "size":"612", "referer":"-", "agent":"curl/7.55.1", "http_x_forwarded_for":"-"}

fluentd_1 | 2020-06-26 13:37:04.000000000 +0900 MY_APP_NAME.nginx-access: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stdout", "remote":"172.17.0.1", "host":"-", "user":"-", "time":"26/Jun/2020:04:37:04 +0000", "method":"GET", "path":"/hoge", "code":"404", "size":"153", "referer":"-", "agent":"curl/7.55.1", "http_x_forwarded_for":"-"}

fluentd_1 | 2020-06-26 04:37:04.000000000 +0900 MY_APP_NAME.nginx-error: {"container_id":"fcde1017a33b494e537923f46bf89aff68c34d2ac776c2b367d92faae69b6fcc", "container_name":"/nginx", "source":"stderr", "time":"2020/06/26 04:37:04", "log_level":"error", "pid":"28", "tid":"28", "message":"*8 open() \"/usr/share/nginx/html/hoge\" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: \"GET /hoge HTTP/1.1\", host: \"localhost\""}

Windows 10 バージョン 2004 へのアップデートが失敗、初期化もできず

からの解決。
Windows 10 Version 2004 を使いたいがアップデートが失敗。
初期化しようにも初期化も失敗して元に戻り、もうメディアからの再インストールするしかないかとも思ったけど、Wi-Fi 6 カードを抜いたらアップデートできた。

マザーボードMSI MPG Z390M GAMING EDGE AC。

Wi-Fi 6 のカードはこれ。

マザーボードIntel Wireless-AC 9560 が載ってるところに、PCI ExpressIntel Wi-Fi 6 AX200 を付けている状態だったので、PCI Expressカードを抜いてみたら無事アップデート完了した。

ただし、アップデート後に、再び Wi-Fi 6 のカードを差し込んだらまたブルースクリーンでクラッシュしてしまった。
デスクトップが出る前にクラッシュしてしまうのでsどうにもならないが、2回起動失敗すると選択肢が出てくるので「トラブルシューティング」「詳細オプション」「スタートアップ設定」「再起動」と進み再起動する。
すると1~9の数字が表示された画面が出てくるので「4) セーフモードを有効にする」を選ぶ。

これでデスクトップにアクセスできるので、デバイスマネージャーで AC 9560 を無効化して再起動。
これで Wi-Fi 6 AX200 が有効にしたままデスクトップまでいけるので、デバイスマネージャで AX200 のドライバを更新。

これで一件落着。


Version 2004 になったらさっそく WSL2 をセットアップ。
これで Windows 10 「Home」でも docker が使えるようになった。

zabbix-agentでnginxのログから2xxの数を数えたい

nginxのログから 2xx や 4xx や 5xx のエラーの数を計算して取得したい。
logrt.count を使うとログファイルを監視して、正規表現にマッチした行数を取得できるらしい。
「Zabbixエージェント(アクティブ)」でキーを

logrt.count[/var/log/nginx/access.log,.*?" 2\d\d ]

にしてみる。
f:id:gae:20200529143443p:plain


これで行けるはずだけど zabbix_agentd.log を見ると

active check "log.count[/var/log/nginx/access.log,.*?" 2\d\d ]" is not supported: Unsupported item key.

"Unsupported item key" とのこと。
ログの上の方を見ると...

Zabbix Agent stopped. Zabbix 3.0.12 (revision 73586).

zabbix-agent のバージョンがむっちゃ古い。
Ubuntu 18.04 で普通にインストールした zabbix-agent はバージョンが 3.0 で logrt.count は 3.2 からの対応なので、新しいのにする必要がある。
↓ここを見てリポジトリを追加。

# wget https://repo.zabbix.com/zabbix/4.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_4.4-1+bionic_all.deb
# dpkg -i zabbix-release_4.4-1+bionic_all.deb
# apt update

zabbix-agent を upgrade する。

apt upgrade

zabbix_agentd.conf はそのままでも問題ないが、アップグレード中に警告がでていた。

dpkg: warning: unable to delete old directory '/var/log/zabbix-agent': Directory not empty

どうもログの場所が /var/log/zabbix-agent から /var/log/zabbix に変わったらしくて、古いディレクトリを消そうとしたらしい。
まぎらわしいので /var/log/zabbix-agent は削除しておく。

# rm -rf /var/log/zabbix-agent

これでアップグレードできたのでzabbix-agentを再起動してみると、またエラー出てる。

active check "logrt.count[/var/log/nginx/access.log,.*?" 2\d\d ]" is not supported: Cannot open file "/var/log/nginx/access.log.1": [13] Permission denied

zabbix がログにアクセスできない。zabbix を adm グループに追加。

# gpasswd -a zabbix adm

これでちゃんと動いた。