Servant + monad-logger でログを吐いてみる

[Servant](http://hackage.haskell.org/package/servant-server)はHaskellのWebフレームワークです。型でAPIを記述するのが特徴でAPIの型さえ書いてしまえばサーバーの実装は最小限の関数を書くだけで良くなります。(参考: [【型レベルWeb DSL】 Servantの紹介](http://qiita.com/lotz/items/883b41fa79f060e59efa))

アプリケーションを作る時にログを吐くようにしておくことは大事です。[monad-logger](http://hackage.haskell.org/package/monad-logger)はDebugやWarnなどのログレベルに応じて出力を制御できたりTemplate Haskellを使ってログが出力されているコードの位置を出力したり出来てとても便利です。

Servant 0.12 以降を対象としています。0.11 以前のバージョンについては[以前の版](https://qiita.com/lotz/items/c357a41d4432942d8054/revisions/0)を参照してください。

今回はServantとmonad-loggerを組み合わせて使う方法を紹介します。Servantはサーバーを実装するときに`Handler a`(`ExceptT ServantErr IO a`のnewtype)の値を返すように作る必要がありますがこれだと`LoggingT`が入っていないのでそのままではログを吐くことが出来ません。そのためにまずは別のモナドトランスフォーマーで実装した後にモナド準同型と呼ばれる自然変換を利用して型を合わせるという技を使います。文字だけだとわけがわからないと思うので順を追って説明していきます。

今回作るWebサーバーは以下の様なものです

```hs
type API = Capture "anypath" Text :> Get '[PlainText] Text
```

`/:anypath` にアクセスすると何かの`plain text`が返って来ます。この時アクセスされた`:anypath`を標準出力にログとして出していくことにします。

```hs
loggingServer :: ServerT API (LoggingT Handler)
loggingServer anypath = do
    $(logInfo) anypath
    pure "success"
```

これがサーバーの実装です。`$(logInfo) anypath`でログを吐き、`pure "success"`で`"success"`という文字列を返しています。大事なのは型です。通常は`Server API`(`ServerT API Handler`のエイリアス)のような型を使うと思いますが今回は`LoggingT`を使いたかったので`ServerT Api (LoggingT Handler)`のようにしています。これでdo構文の中は`LoggingT Handler a`と思って実装すれば良くなります。

実際にサーバーを動かすためには`Server API`という型が必要ですが手元にはまだ`ServerT API (LoggingT Handler)`しかありません。そこで自然変換を用います。自然変換は圏論に出てくる概念で関手(Functor)から関手への射になります(参考: [自然変換](https://www.wikiwand.com/ja/%E8%87%AA%E7%84%B6%E5%A4%89%E6%8F%9B))。これをHaskellで表すと`forall a. f a -> g a`となります。

Servantでは[`hoistServer :: HasServer api '[] => Proxy api -> (forall x. m x -> n x) -> ServerT api m -> ServerT api n`](https://hackage.haskell.org/package/servant-server/docs/Servant-Server.html#v:hoistServer)が用意されています。この第2引数が自然変換になっているのでここに適切な関数を渡せばよいことになります。例えば[`runStdoutLoggingT :: MonadIO m => LoggingT m a -> m a`](https://hackage.haskell.org/package/monad-logger/docs/Control-Monad-Logger.html#v:runStdoutLoggingT)がちょうどその型に一致します。 :clap: 

```hs
main :: IO ()
main = do
    putStrLn "Listening on port 8080"
    let server = hoistServer api runStdoutLoggingT loggingServer
    Warp.run 8080 $ serve api server
```

これがmain関数になります。

実際に動かしてみましょう！

<img width="532" alt="スクリーンショット 2016-02-21 14.37.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/17134/9eaf1456-fc8f-eb11-0b2f-dca58d79b342.png">

Google Chrome が favicon.ico を読みに行ってるとこまでバッチリわかりますね！
以下に今回作ったコードの全体を載せておきます。

```hs:app/Main.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Monad.Logger
import Data.Text (Text)
import qualified Network.Wai.Handler.Warp as Warp
import Servant

type API = Capture "anypath" Text :> Get '[PlainText] Text

api :: Proxy API
api = Proxy

loggingServer :: ServerT API (LoggingT Handler)
loggingServer anypath = do
    $(logInfo) anypath
    pure "success"

main :: IO ()
main = do
    putStrLn "Listening on port 8080"
    let server = hoistServer api runStdoutLoggingT loggingServer
    Warp.run 8080 $ serve api server
```

```yaml:servant-logger-example.cabal
name:                servant-logger-example
version:             0.1.0.0
build-type:          Simple
cabal-version:       >=1.10

executable app
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , text
                     , monad-logger
                     , warp
                     , servant-server >= 0.12
  default-language:    Haskell2010
```

ログを取るためにモナド準同型や自然変換といった概念が登場してきましたがモナドを乗り越えた人なら分かるとおり数学はプログラマの味方です。使いこなせればよりシンプルに強力なコードを書けるようになるでしょう。気になる人は[mmorph](http://hackage.haskell.org/package/mmorph)というライブラリもチェックしてみて下さい！
この記事は[第29回Haskellもくもく会@朝日ネット](http://haskellmokumoku.connpass.com/event/26602/)での成果を元に書かれました。