Search code examples
haskellloggingservant

How can I use log-warper with Servant?


I have an application built on top of Servant, and now I want to add logging to the application. I skimmed through Haskell log packages, and I assume this one provides what I need: https://github.com/serokell/log-warper/blob/master/log-warper/examples/HowTo.md

One of the 'design patterns' often used for building apps with Servant is to use Reader monad, so I use this approach for the app: I have AppEnv which contains AppConfig. Usually, I could add something like a 'logger' entity to the AppEnv and so use it in handlers. Although, log-warper doesn't provide 'a logger', but it uses different approach instead (seems to be another monad, I assume; please, see the example on the link above). And so I can't figure out how to use this logger with Servant.

Here is my Servant-based app (using recent version of Servant, basing on examples from the doc: http://haskell-servant.readthedocs.io/en/stable/tutorial/Server.html#welcome-hoistserver):

data AppEnv = AppEnv { config :: Config }
type MyHandler = ReaderT AppEnv (ExceptT ServantErr IO)

startApp :: AppEnv -> IO ()
startApp env = do
  run 16384 (app env)

app :: AppEnv -> Application
app env = serve readerAPI (readerServer env)

readerAPI :: Proxy ReaderAPI
readerAPI = Proxy

readerToHandler :: AppEnv -> Reader AppEnv a -> Handler a
readerToHandler env r = return (runReader r env)

readerServer :: AppEnv -> Server ReaderAPI
readerServer env = hoistServer readerAPI (readerToHandler env) readerServerT

b :: Reader AppEnv Bool
b = do
  c <- config <$> ask
  let
    s = getServerConfig c
    p = getServerPort s
  return (p == 1)

getServerConfig :: Config -> ServerConfig
getServerConfig (Config s _) = s

getServerPort :: ServerConfig -> Int
getServerPort (ServerConfig _ p) = p

readerServerT :: ServerT ReaderAPI (Reader AppEnv)
readerServerT = a :<|> b where
    a :: Reader AppEnv Int
    a = return 1797

And here is the main function:

main :: IO ()
main = do
  config <- loadYamlSettings ["etc/config.yaml"] [] useEnv
  print (config :: Config)
  let
    env = AppEnv config
  startApp env

Now, how can I add log-warper to the application so I could initialize the logger (with launchFromFile I assume), and then use logging (logInfo, logError, etc.) in the app (in particular, in handlers, but possibly in other functions as well)?

Thanks


Solution

  • General logging

    If you want a generic logging tool with some sophisticated options katip looks like a good choice. There is even a small discussion about how to use it with servant. You just need to add a couple of parameters for katip to your Config type, initialize them, then you can log in your handlers.

    Request logging

    servant-server is built on top of wai and warp so you can reuse a lot of there tools. If you are just interested in logging data about requests to servant, you can use wai-logger without changing any of your types.

    startApp would look something like this.

    startApp :: AppEnv -> IO ()
    startApp env = do
      withStdoutLogger $ \logger -> 
        runSettings (setPort 16384 $ setLogger logger $ defaultSettings) $ app env