HaskellのロガーKatipを試す
モチベ
Haskell の実行時ログ出力を行うライブラリは monad-logger が一番有名っぽい。 これは Yesod 陣営が開発しているから安心感があるし、バックエンドが fast-logger なので速度も信頼できる。 ただ (自分の調べ方が悪いのかもしれないが) ちょっと自分の用途には機能が足りなかった。 具体的には以下の機能:
- ログにタイムスタンプを付記したい。
- ロガーに名前をつけたい。
- ファイルサイズか日付でログローテーションしたい。
Katip という別のロガーライブラリは機能が豊富のようなので今回はそれを試してみる。
(この記事のHaskell環境: lts-6.23)
Katipのおそらく最小の構成
とりあえず動かしてみる。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main where import System.IO (stdout) import Control.Monad.Trans (lift) import Katip main :: IO () main = do le <- initLogEnv "MyApp" "prod" -- (1) hs <- mkHandleScribe ColorIfTerminal stdout InfoS V0 -- (2) let le' = registerScribe "stdout scribe" hs le -- (3) runKatipContextT le' () "main" $ do -- (4) $logTM InfoS "ログ出力" -- (5) $logTM DebugS "これは出力されない" -- (6) lift $ putStrLn "ふつうの出力" return () return () -- > [2016-10-30 08:00:31][MyApp.main][Info][kaz-mba-lb8o][23644][ThreadId 7][main:Main app/Main.hs:17:5] ログ出力 -- > ふつうの出力
- 新しい
LogEnv
を作る。ルートロガー名と実行環境名を指定している。 - 新しい
Scribe
を作る。Scribe
というのはログの出力先とログレベルなどの設定を組にしたもの。Katip はログレベルとして severity level (InfoとかErrorとか) だけでなくverbosity level (V0とかV1とか) も指定できる。 LogEnv
にScribe
を登録する。- 作成した
LogEnv
でロガーのモナドを走らせる。 - Info レベルでログを出力する。
$
がついているのはTemplateHaskell
でソースファイルの行番号などをログに出力するため。 - Debug レベルでログを出力する。しかし
LogEnv
が Info レベルのScribe
しか持っていないので出力されない。
ロガーのモナドの下でログ出力を行うという点以外はかなり普通のロガーという感じだ。
ファイルに出力したいときは stdout
の代わりにファイルハンドルを渡せばいいし、
出力先を増やしたいときは Scribe
を追加すればいい。
ログローテーション
Katip は組込みのログローテーションの仕組みは持っていないようだが、 Katip の開発元の Soostone 社は Katip と別に rotating-log というライブラリを公開している。 rotating-log の説明文によると、どうやらファイルサイズベースのローテーションを提供しているらしい。
それならログローテーションはこれでやればいいのかな……と思いきや、
This is so that it can be used to log non-textual streams such as binary serialized or compressed content.
とのことなのでテキストログのローテーションで使うものではないらしい。
一応 rotating-log を Scribe
にしてテキストログのローテーションを試してみたが (以下のコード)
参考にしない方がいいかもしれない。
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main where import System.IO ( hSetBuffering, hIsTerminalDevice, stdout , BufferMode(..) , Handle ) import Control.Monad (when) import Control.Monad.Trans (lift) import Data.Monoid ((<>)) import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.IO as LT import qualified Data.Text.Lazy.Builder as LT import qualified Data.Text.Lazy.Encoding as LT import Katip import Katip.Scribes.Handle (formatItem) import System.RotatingLog ( RotatingLog , mkRotatingLog, archiveFile , rotatedWrite, rotatedClose ) mkRotatingLogScribe :: RotatingLog -> Severity -> Verbosity -> IO Scribe mkRotatingLogScribe rl sev verb = return $ Scribe $ \i -> do when (permitItem sev i) $ rotatedWrite rl $ T.encodeUtf8 $ LT.toStrict $ LT.toLazyText $ (formatItem False verb i <> "\n") main :: IO () main = do le <- initLogEnv "MyApp" "prod" rl <- mkRotatingLog "log/myapp" 256 LineBuffering (archiveFile "log/archive/") s <- mkRotatingLogScribe rl InfoS V0 let le' = registerScribe "rotating log scribe" s le runKatipContextT le' () "main" $ do sequence_ $ replicate 10 $ $logTM InfoS "ログ出力" -- > $ tree log -- > log -- > ├── archive -- > │ ├── myapp_2016_10_30_17_07_51.852205.log -- > │ ├── myapp_2016_10_30_17_07_51.852508.log -- > │ ├── myapp_2016_10_30_17_07_51.85274.log -- > │ └── myapp_2016_10_30_17_07_51.852956.log -- > └── myapp.log
ちなみに fast-logger はサイズベースのローテーションの仕組みを持っている。 その一方で monad-logger は fast-logger を使って実装されているにも関わらずローテーションのインターフェースを持っていない。 推測だが、monad-logger を開発している Yesod 陣営が Keter を使っているためではないかと思う。 Keter についてはよく分かっていないのだが、Web アプリケーションのデプロイをサポートするものとのことで、 Yesod アプリのデプロイなどに使われているらしい。 ドキュメントによると Keter はログローテーションのAPIを持っているので、 Yesod 陣営にとっては monad-logger にローテーションを実装する必要性がないのではないだろうか。
まとめ
Katip はタイムスタンプや名前空間など、一般的なロガーライブラリが持っているような機能は十分備えている。
ログローテーションの仕組みはないが、これは logrotate
コマンドなどを使えばなんとかなるので大きい問題ではないと思う。
なお Katip はこの記事で紹介した以外にも次のような機能がある。
- JSON形式データの出力
- 他のモナドへの組込み
- Elasticsearch へのログ出力 (katip-elasticsearch)