1.はじめに
Elixirの動作ログをsyslogに送信します。
localhostに向けて送信するには、既存の下記ライブラリが使えます。
今回はリモートホストに送信する必要があったのですが、上記はリモートホストに対する設定がなかったり、そのほかリモートホスト対応のライブラリが見つからない(よくわからない)ので、syslogのプロトコルを自分で実装して、UDP送信するサンプルを作ってみました。
2.環境
$ uname -a Linux myhost 6.2.0-26-generic #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 16:27:29 UTC 2 x86_64 x86_64 x86_64 GNU/Linux $ elixir -v Erlang/OTP 24 [erts-12.3.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit] Elixir 1.13.4 (compiled with Erlang/OTP 24)
3.rsyslogdの設定変更
(1)既存の設定をバックアップ
$ sudo cp -p /etc/rsyslog.conf /etc/rsyslog.conf.original
(2)設定を変更
$ sudo nano /etc/rsyslog.conf
16行目付近のコメントを外す。 UDP
修正前
# provides TCP syslog reception #module(load="imtcp") #input(type="imtcp" port="514")
修正後
# provides UDP syslog reception module(load="imudp") input(type="imudp" port="514")
(3)設定の反映
$ sudo systemctl restart rsyslog.service $ systemctl status rsyslog.service
(4)送信テスト、受信内容の確認
$ logger "hello" -i -t "sample" -n localhost $ tail -10 /var/log/syslog
… Jul 31 16:42:44 myhost sample[507666] hello …
4.Elixirでの実装
(1)syslogプロトコル
goでの実装例を参考にしました。
golangでsyslogパケットを送る. 時にはsyslogサーバーのテストのためにsyslogパケットを送りたい時がある… | by sushiqueue | Medium
考え方
任意のホストのポート514に対し、下記の文字列をUDP(あるいはTCP)で送信
<priority>timestamp hostname tag: message
記号 | 内容 | 補足 |
---|---|---|
priority | 計算式:(facility << 3) | severity で算出 |
<> で囲むこと |
timestamp | タイムスタンプ | フォーマット例:Aug 27 22:09:46 |
hostname | 送信元ホスト名 | |
tag | 送信元のアプリケーション名 | 後述のサンプルではアプリの[PID] を付加 |
message | メッセージの本文 |
(2)実装例
プロジェクト
$ mix new syslogr $ cd syslogr #新たにソースファイルを作成 $ touch ./lib/rsyslog.ex
lib/rsyslog.ex
下記を記述
defmodule Rsyslog do @moduledoc """ Documentation for `rsyslog`. """ # 自分のホストの任意のポート番号 @portclient 32154 # タイムゾーン(ログの時刻に必要) @timezone "Japan" # 送信先ホスト(シングルクォートで囲むこと) # 例ではlocalhostですが、リモートでもOKです @host '127.0.0.1' # 送信先ポート @port 514 defp build_message(message, hostname, facility, severity) when is_bitstring(message) and is_bitstring(hostname) and is_integer(facility) and is_integer(severity) do # 時刻を指定のフォーマットに書き出し {:ok, timestring} = Timex.now(@timezone) |> Timex.format("{Mshort} {0D} {h24}:{m}:{s}") # 当アプリ名を取得 {:ok, tag} = :application.get_application(__MODULE__) # syslog用文字列の組み立て "#{priority(facility, severity)}#{timestring} #{hostname} #{tag}[#{System.pid()}]: #{message}" end defp priority(facility, severity) when is_integer(facility) and is_integer(severity) do # priorityの値の計算と文字列生成 # ルールに従いフォーマット use Bitwise "<#{inspect(facility <<< 3 ||| severity)}>" end @doc """ ログの送信 ## Parameters - message : 送信データ - host : 送信先ホスト - port : 送信先ポート - facility: 16 (local use 0 (local0)をデフォルトとしています) - severity: 6 (Infoをデフォルトとしています) ## Examples iex> Rsyslog.log("hoge") """ def log(message, severity \\ 6, host \\ @host, port \\ @port, facility \\ 16) when is_bitstring(message) and is_integer(facility) and is_integer(severity) and is_list(host) and is_integer(port) do # ホスト名の取得 {:ok, hostname} = :inet.gethostname() # 送信データの生成 mes = build_message(message, "#{hostname}", facility, severity) # 送信 {:ok, socket} = :gen_udp.open(@portclient, [:binary]) response = :gen_udp.send(socket, host, port, mes) :gen_udp.close(socket) {response, mes} end end
mix.exs
Timexを追加
… # Run "mix help deps" to learn about dependencies. defp deps do [ # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} {:timex, "~> 3.7"} ] end …
(3)送信テスト
ビルド
$ mix deps.get
$ iex -S mix
送信
iex> Rsyslog.log("hoge") {:ok, "<134>Aug 27 22:09:46 myhost syslogr[159869]: hoge"}
ログの状態
$ tail -10 /var/log/syslog
… Aug 27 22:09:46 myhost syslogr[160578]: hoge
参考資料
- 実装例
- ElixirでUDP通信
- rsyslogdの設定
- syslogのpriority対応表
- nc - 使いたいときに必ず忘れる便利コマンド