読者です 読者をやめる 読者になる 読者になる

taketoncheir.log

Like the Decatoncheir by Poseidon Industrial, This blog is Yet Another Storage for My Long Term Memories.

Yesod1.1のLogging

Haskell Yesod

Yesod1.1のLogging

MonadLogger

@rf0444と、Yesodのログ周りを見てました。
参考にしたのは、SnoymanさんのエントリーYesod's new logging system

とりあえず、Yesod1.1で。

getHomeR = do
    $(logInfo) "That's it!!"   -- Infoレベルでログ
    defaultLayout $ do
        setTitle "Welcome To Yesod with Logging!"
        $(widgetFile "homepage")

これでStdOutにログが出ます。アクセスしてみましょう。

GET /
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
14/Aug/2012:23:57:16 +0900 [Info] That's it! @(adam-0.0.0:Handler.Home ./Handler/Home.hs:15:7)
Status: 200 OK. /

おぉ!ログが出ている!
(*追記: 上記のコードにはmoduleのimportと.cabalへの依存追加が必要です)

-- Import.hsに書いておくと至る所に書かなくて済む
import System.IO (openFile, IOMode (..))
import System.Log.FastLogger (mkLogger)
build-depends:  fast-logger

ログの出力先を変更

いまは標準出力に出ています。おそらくRequestLogger経由のHTTPプロトコルも標準出力に出ていて、サーバー側とアプリ側のログが混じってしまい綺麗じゃないです。
class YesodにgetLoggerが定義されています。
デフォルト実装は

getLogger :: a -> IO Logger
getLogger _ = mkLogger True stdout

となっており、標準出力(stdout)に出す仕様になっています。これを書き換えてあげましょう。

Foundation.hsにInstance Yesod appを定義している部分があります。ここに、getLoggerを定義してやりましょう。
getLoggerの中で呼んでいるmkLoggerの型は以下です。

-- module System.Log.FastLogger
mkLogger :: Bool -> Handle -> IO Logger

このHandleってのはGHC.IO.Handleです。
なので、ファイルパスからハンドラーを取得してmkLoggerに渡してやります。

-- in Foundation.hs
instance Yesod App where

    getLogger app = do
        h <- openFile "./adam.log" AppendMode
        mkLogger True h

これでまたHomeRにアクセスしてみましょう。
プロジェクトディレクトリ直下にadam.logが出来てるはず!

設定を外出し

ハードコーディングしているファイルパスを外出ししましょう。
Yesodにはconfig/settings.ymlという設定ファイルがデフォルトで設けられています。このファイルはどうやって読み込まれているのか。

Foundation.hsにて定義されているdata Appに、

settings :: AppConfig DefaultEnv Extra

というフィールドが宣言されているので、Yesod Appのコンストラクタでセットされるんだろうなと。
AppConfigはYesod.Default.Configモジュールにあるdata型です。
このAppConfig型はDefaultEnvとExtraをとるコンストラクタを持ち、appExtraというフィールドを持っています。
どうやら、config/settings.xmlにあるhostやport, approotといった情報はそれぞれappHost, appPort, appRootフィールドに格納され、それ以外の設定はappExtraに入るんだろうなと。
なので、appExtra $ settings appでExtra型の値が手に入るはず。
Extra型はどこで定義されているかというと、Settings.hsです。
あ、parseExtra関数があります。文字列をparseしてくれてるっぽいです。ここに
logfilepathを付け加えてやりましょう。

-- in config/settings.yml
Default: &defaults
  logfilepath: "./adam.log"
-- in Settings.hs
data Extra = Extra
    { extraCopyright :: Text
    , extraLogFilePath :: Text -- (追記)ここFilePathにしといた方がいい
    , extraAnalytics :: Maybe Text
    } deriving Show
	
parseExtra _ o = Extra$
    <$> o .:  "copyright"$
    <*> o .:  "logfilepath"$
    <*> o .:? "analytics"$

これで、extraLogFilePath $ appExtra $ settings appでText型のファイルパスが手に入るはず。
openFileはFilePath型を要求するので、TextをFilePathに変換するためにData.Text.unpackをかけてやります。
(* 追記:extraLogFilePathをFilePathで宣言しておけばunpackする必要ありません)
一連を纏めると、Foundation.hsは以下になります。

-- in Foundation.hs
import System.IO (openFile, IOMode (..))
import System.Log.FastLogger (mkLogger)
import qualified Data.Text as T

instance Yesod App where

    getLogger app = do
        handle <- openFile (T.unpack $ extraLogFilePath $ appExtra $ settings app) AppendMode
        mkLogger True handle

以上で、アプリ側のログ周りセッティングはいけそうです。

(* 追記:Foundation.hsの中にgetExtraって関数がある!でもHandler Extra型になるのでどうすればmkLoggerに設定情報渡せばいいのかわからない、、)