add Logger to scaffolding

This commit is contained in:
Greg Weber 2011-08-04 13:06:39 -07:00
parent ef82be8f23
commit 4b6147aa1a
11 changed files with 88 additions and 35 deletions

View File

@ -17,6 +17,7 @@ module Yesod.Core
-- * Logging -- * Logging
, LogLevel (..) , LogLevel (..)
, formatLogMessage , formatLogMessage
, fileLocationToString
, logDebug , logDebug
, logInfo , logInfo
, logWarn , logWarn

View File

@ -23,6 +23,7 @@ module Yesod.Internal.Core
-- * Logging -- * Logging
, LogLevel (..) , LogLevel (..)
, formatLogMessage , formatLogMessage
, fileLocationToString
, messageLoggerHandler , messageLoggerHandler
-- * Misc -- * Misc
, yesodVersion , yesodVersion
@ -71,7 +72,6 @@ import Data.List (foldl')
import qualified Network.HTTP.Types as H import qualified Network.HTTP.Types as H
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO import qualified Data.Text.Lazy.IO
import qualified System.IO
import qualified Data.Text.Lazy.Builder as TB import qualified Data.Text.Lazy.Builder as TB
import Language.Haskell.TH.Syntax (Loc (..), Lift (..)) import Language.Haskell.TH.Syntax (Loc (..), Lift (..))
import Text.Blaze (preEscapedLazyText) import Text.Blaze (preEscapedLazyText)
@ -257,7 +257,7 @@ class RenderRoute (Route a) => Yesod a where
-> IO () -> IO ()
messageLogger _ loc level msg = messageLogger _ loc level msg =
formatLogMessage loc level msg >>= formatLogMessage loc level msg >>=
Data.Text.Lazy.IO.hPutStrLn System.IO.stderr Data.Text.Lazy.IO.putStrLn
messageLoggerHandler :: (Yesod m, MonadIO mo) messageLoggerHandler :: (Yesod m, MonadIO mo)
=> Loc -> LogLevel -> Text -> GGHandler s m mo () => Loc -> LogLevel -> Text -> GGHandler s m mo ()
@ -283,14 +283,23 @@ formatLogMessage loc level msg = do
now <- getCurrentTime now <- getCurrentTime
return $ TB.toLazyText $ return $ TB.toLazyText $
TB.fromText (TS.pack $ show now) TB.fromText (TS.pack $ show now)
`mappend` TB.fromText ": " `mappend` TB.fromText " ["
`mappend` TB.fromText (TS.pack $ show level) `mappend` TB.fromText (TS.pack $ drop 5 $ show level)
`mappend` TB.fromText "@(" `mappend` TB.fromText "] "
`mappend` TB.fromText (TS.pack $ loc_filename loc)
`mappend` TB.fromText ":"
`mappend` TB.fromText (TS.pack $ show $ fst $ loc_start loc)
`mappend` TB.fromText ") "
`mappend` TB.fromText msg `mappend` TB.fromText msg
`mappend` TB.fromText " @("
`mappend` TB.fromText (TS.pack $ fileLocationToString loc)
`mappend` TB.fromText ") "
-- taken from file-location package
-- turn the TH Loc loaction information into a human readable string
-- leaving out the loc_end parameter
fileLocationToString :: Loc -> String
fileLocationToString loc = (loc_package loc) ++ ':' : (loc_module loc) ++
' ' : (loc_filename loc) ++ ':' : (line loc) ++ ':' : (char loc)
where
line = show . fst . loc_start
char = show . snd . loc_start
defaultYesodRunner :: Yesod master defaultYesodRunner :: Yesod master
=> a => a

View File

@ -9,21 +9,26 @@ module Yesod.Logger
, makeLogger , makeLogger
, flushLogger , flushLogger
, timed , timed
, logText
, logLazyText
, logString
) where ) where
import Control.Monad (forever) import Control.Monad (forever)
import Control.Monad.Trans (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Applicative ((<$>), (<*>)) import Control.Applicative ((<$>), (<*>))
import Control.Concurrent (forkIO) import Control.Concurrent (forkIO)
import Control.Concurrent.Chan.Strict (Chan, newChan, readChan, writeChan) import Control.Concurrent.Chan.Strict (Chan, newChan, readChan, writeChan)
import Control.Concurrent.MVar.Strict (MVar, newEmptyMVar, takeMVar, putMVar) import Control.Concurrent.MVar.Strict (MVar, newEmptyMVar, takeMVar, putMVar)
import Text.Printf (printf) import Text.Printf (printf)
import Data.Text
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO
import Data.Time (getCurrentTime, diffUTCTime) import Data.Time (getCurrentTime, diffUTCTime)
data Logger = Logger data Logger = Logger
{ loggerChan :: Chan (Maybe String) -- Nothing marks the end { loggerChan :: Chan (Maybe TL.Text) -- Nothing marks the end
, loggerSync :: MVar () -- Used for sync on quit , loggerSync :: MVar () -- Used for sync on quit
} }
makeLogger :: IO Logger makeLogger :: IO Logger
@ -38,7 +43,7 @@ makeLogger = do
-- Stop: sync -- Stop: sync
Nothing -> putMVar (loggerSync logger) () Nothing -> putMVar (loggerSync logger) ()
-- Print and continue -- Print and continue
Just m -> putStrLn m Just m -> Data.Text.Lazy.IO.putStrLn m
-- | Flush the logger (blocks until flushed) -- | Flush the logger (blocks until flushed)
-- --
@ -49,15 +54,21 @@ flushLogger logger = do
return () return ()
-- | Send a raw message to the logger -- | Send a raw message to the logger
-- -- Native format is lazy text
message :: Logger -> String -> IO () logLazyText :: Logger -> TL.Text -> IO ()
message logger = writeChan (loggerChan logger) . Just logLazyText logger = writeChan (loggerChan logger) . Just
logText :: Logger -> Text -> IO ()
logText logger = logLazyText logger . TL.fromStrict
logString :: Logger -> String -> IO ()
logString logger = logLazyText logger . TL.pack
-- | Execute a monadic action and log the duration -- | Execute a monadic action and log the duration
-- --
timed :: MonadIO m timed :: MonadIO m
=> Logger -- ^ Logger => Logger -- ^ Logger
-> String -- ^ Message -> Text -- ^ Message
-> m a -- ^ Action -> m a -- ^ Action
-> m a -- ^ Timed and logged action -> m a -- ^ Timed and logged action
timed logger msg action = do timed logger msg action = do
@ -66,6 +77,6 @@ timed logger msg action = do
stop <- liftIO getCurrentTime stop <- liftIO getCurrentTime
let diff = fromEnum $ diffUTCTime stop start let diff = fromEnum $ diffUTCTime stop start
ms = diff `div` 10 ^ (9 :: Int) ms = diff `div` 10 ^ (9 :: Int)
formatted = printf " [%4dms] %s" ms msg formatted = printf " [%4dms] %s" ms (unpack msg)
liftIO $ message logger formatted liftIO $ logString logger formatted
return result return result

View File

@ -51,10 +51,14 @@ library
, case-insensitive >= 0.2 && < 0.4 , case-insensitive >= 0.2 && < 0.4
, parsec >= 2 && < 3.2 , parsec >= 2 && < 3.2
, directory >= 1 && < 1.2 , directory >= 1 && < 1.2
-- for logger. Probably logger should be a separate package
, strict-concurrency >= 0.2.4 && < 0.2.5
exposed-modules: Yesod.Content exposed-modules: Yesod.Content
Yesod.Core Yesod.Core
Yesod.Dispatch Yesod.Dispatch
Yesod.Handler Yesod.Handler
Yesod.Logger
Yesod.Request Yesod.Request
Yesod.Widget Yesod.Widget
Yesod.Message Yesod.Message

View File

@ -1,6 +1,7 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Controller module Controller
( with~sitearg~ ( with~sitearg~
@ -11,6 +12,7 @@ import ~sitearg~
import Settings import Settings
import Yesod.Static import Yesod.Static
import Yesod.Auth import Yesod.Auth
import Yesod.Logger (makeLogger, flushLogger, Logger)
import Database.Persist.GenericSql import Database.Persist.GenericSql
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Dynamic (Dynamic, toDyn) import Data.Dynamic (Dynamic, toDyn)
@ -35,11 +37,11 @@ getRobotsR = return $ RepPlain $ toContent ("User-agent: *" :: ByteString)
-- performs initialization and creates a WAI application. This is also the -- performs initialization and creates a WAI application. This is also the
-- place to put your migrate statements to have automatic database -- place to put your migrate statements to have automatic database
-- migrations handled by Yesod. -- migrations handled by Yesod.
with~sitearg~ :: AppConfig -> (Application -> IO a) -> IO a with~sitearg~ :: AppConfig -> Logger -> (Application -> IO a) -> IO a
with~sitearg~ conf f = do with~sitearg~ conf logger f = do
Settings.withConnectionPool conf $ \p -> do Settings.withConnectionPool conf $ \p -> do
runConnectionPool (runMigration migrateAll) p runConnectionPool (runMigration migrateAll) p
let h = ~sitearg~ conf s p let h = ~sitearg~ conf logger s p
toWaiApp h >>= f toWaiApp h >>= f
where where
s = static Settings.staticDir s = static Settings.staticDir
@ -47,7 +49,10 @@ with~sitearg~ conf f = do
with~sitearg~LoadConfig :: Settings.AppEnvironment -> (Application -> IO a) -> IO a with~sitearg~LoadConfig :: Settings.AppEnvironment -> (Application -> IO a) -> IO a
with~sitearg~LoadConfig env f = do with~sitearg~LoadConfig env f = do
conf <- Settings.loadConfig env conf <- Settings.loadConfig env
with~sitearg~ conf f logger <- makeLogger
r <- with~sitearg~ conf logger f
flushLogger logger
return r
-- for yesod devel -- for yesod devel
withDevelApp :: Dynamic withDevelApp :: Dynamic

View File

@ -10,6 +10,7 @@ module Controller
import ~sitearg~ import ~sitearg~
import Settings import Settings
import Yesod.Static import Yesod.Static
import Yesod.Logger (makeLogger, flushLogger, Logger)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Network.Wai (Application) import Network.Wai (Application)
import Data.Dynamic (Dynamic, toDyn) import Data.Dynamic (Dynamic, toDyn)
@ -34,9 +35,9 @@ getRobotsR = return $ RepPlain $ toContent ("User-agent: *" :: ByteString)
-- performs initialization and creates a WAI application. This is also the -- performs initialization and creates a WAI application. This is also the
-- place to put your migrate statements to have automatic database -- place to put your migrate statements to have automatic database
-- migrations handled by Yesod. -- migrations handled by Yesod.
with~sitearg~ :: AppConfig -> (Application -> IO a) -> IO a with~sitearg~ :: AppConfig -> Logger -> (Application -> IO a) -> IO a
with~sitearg~ conf f = do with~sitearg~ conf logger f = do
let h = ~sitearg~ conf s let h = ~sitearg~ conf logger s
toWaiApp h >>= f toWaiApp h >>= f
where where
s = static Settings.staticDir s = static Settings.staticDir
@ -44,7 +45,11 @@ with~sitearg~ conf f = do
with~sitearg~LoadConfig :: Settings.AppEnvironment -> (Application -> IO a) -> IO a with~sitearg~LoadConfig :: Settings.AppEnvironment -> (Application -> IO a) -> IO a
with~sitearg~LoadConfig env f = do with~sitearg~LoadConfig env f = do
conf <- Settings.loadConfig env conf <- Settings.loadConfig env
with~sitearg~ conf f logger <- makeLogger
r <- with~sitearg~ conf logger f
flushLogger logger
return r
withDevelApp :: Dynamic withDevelApp :: Dynamic
withDevelApp = do withDevelApp = do

View File

@ -15,6 +15,7 @@ module ~sitearg~
import Yesod.Core import Yesod.Core
import Yesod.Static import Yesod.Static
import Yesod.Logger (Logger, logLazyText)
import qualified Settings import qualified Settings
import System.Directory import System.Directory
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
@ -32,6 +33,7 @@ import Web.ClientSession (getKey)
-- access to the data present here. -- access to the data present here.
data ~sitearg~ = ~sitearg~ data ~sitearg~ = ~sitearg~
{ settings :: Settings.AppConfig { settings :: Settings.AppConfig
, getLogger :: Logger
, getStatic :: Static -- ^ Settings for static file serving. , getStatic :: Static -- ^ Settings for static file serving.
} }
@ -85,6 +87,9 @@ instance Yesod ~sitearg~ where
Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s
urlRenderOverride _ _ = Nothing urlRenderOverride _ _ = Nothing
messageLogger y loc level msg =
formatLogMessage loc level msg >>= logLazyText (getLogger y)
-- This function creates static content files in the static folder -- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows -- and names them based on a hash of their content. This allows
-- expiration dates to be set far in the future without worry of -- expiration dates to be set far in the future without worry of

View File

@ -43,11 +43,15 @@ executable ~project~
else else
ghc-options: -Wall -threaded ghc-options: -Wall -threaded
if os(windows)
cpp-options: -DWINDOWS
main-is: main.hs main-is: main.hs
hs-source-dirs: ., config hs-source-dirs: ., config
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 0.9 && < 0.10 , yesod >= 0.9 && < 0.10
, yesod-core
, yesod-auth , yesod-auth
, yesod-static , yesod-static
, blaze-html , blaze-html

View File

@ -5,15 +5,15 @@ import Controller (with~sitearg~)
import Network.Wai.Handler.Warp (run) import Network.Wai.Handler.Warp (run)
import System.Console.CmdArgs hiding (args) import System.Console.CmdArgs hiding (args)
import Data.Char (toUpper, toLower) import Data.Char (toUpper, toLower)
import Yesod.Logger (logString, logLazyText, flushLogger, makeLogger)
#if PRODUCTION #ifndef PRODUCTION
#else import Network.Wai.Middleware.Debug (debugHandle)
import System.IO (hPutStrLn, stderr)
import Network.Wai.Middleware.Debug (debug)
#endif #endif
main :: IO () main :: IO ()
main = do main = do
logger <- makeLogger
args <- cmdArgs argConfig args <- cmdArgs argConfig
env <- getAppEnv args env <- getAppEnv args
config <- Settings.loadConfig env config <- Settings.loadConfig env
@ -21,9 +21,12 @@ main = do
#if PRODUCTION #if PRODUCTION
with~sitearg~ c $ run (appPort c) with~sitearg~ c $ run (appPort c)
#else #else
hPutStrLn stderr $ (show env) ++ " application launched, listening on port " ++ show (appPort c) logString logger $ (show env) ++ " application launched, listening on port " ++ show (appPort c)
with~sitearg~ c $ run (appPort c) . debug with~sitearg~ c logger $ run (appPort c) . debugHandle (logHandle logger)
flushLogger logger
#endif #endif
where
logHandle logger msg = logLazyText logger msg >> flushLogger logger
data ArgConfig = ArgConfig {environment :: String, port :: Int} data ArgConfig = ArgConfig {environment :: String, port :: Int}
deriving (Show, Data, Typeable) deriving (Show, Data, Typeable)

View File

@ -20,6 +20,7 @@ import Yesod.Static
import Yesod.Auth import Yesod.Auth
import Yesod.Auth.OpenId import Yesod.Auth.OpenId
import Yesod.Auth.Email import Yesod.Auth.Email
import Yesod.Logger (Logger, logLazyText)
import qualified Settings import qualified Settings
import System.Directory import System.Directory
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
@ -43,6 +44,7 @@ import Text.Hamlet (html)
-- access to the data present here. -- access to the data present here.
data ~sitearg~ = ~sitearg~ data ~sitearg~ = ~sitearg~
{ settings :: Settings.AppConfig { settings :: Settings.AppConfig
, getLogger :: Logger
, getStatic :: Static -- ^ Settings for static file serving. , getStatic :: Static -- ^ Settings for static file serving.
, connPool :: Settings.ConnectionPool -- ^ Database connection pool. , connPool :: Settings.ConnectionPool -- ^ Database connection pool.
} }
@ -100,6 +102,9 @@ instance Yesod ~sitearg~ where
-- The page to be redirected to when authentication is required. -- The page to be redirected to when authentication is required.
authRoute _ = Just $ AuthR LoginR authRoute _ = Just $ AuthR LoginR
messageLogger y loc level msg =
formatLogMessage loc level msg >>= logLazyText (getLogger y)
-- This function creates static content files in the static folder -- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows -- and names them based on a hash of their content. This allows
-- expiration dates to be set far in the future without worry of -- expiration dates to be set far in the future without worry of
@ -119,6 +124,7 @@ instance Yesod ~sitearg~ where
unless exists $ liftIO $ L.writeFile fn' content' unless exists $ liftIO $ L.writeFile fn' content'
return $ Just $ Right (StaticR $ StaticRoute ["tmp", T.pack fn] [], []) return $ Just $ Right (StaticR $ StaticRoute ["tmp", T.pack fn] [], [])
-- How to run database actions. -- How to run database actions.
instance YesodPersist ~sitearg~ where instance YesodPersist ~sitearg~ where
type YesodDB ~sitearg~ = SqlPersist type YesodDB ~sitearg~ = SqlPersist

View File

@ -11,7 +11,7 @@ cabal clean && cabal install && cabal sdist
for f in $(ls -1rt dist/*.tar.gz | tail -1) for f in $(ls -1rt dist/*.tar.gz | tail -1)
do do
tar -xzvf $f && cd `basename $f .tar.gz` tar -xzvf $f && cd `basename $f .tar.gz`
shelltest ../tests/scaffold.shelltest $@ shelltest ../tests/scaffold.shelltest -c $@
cd .. cd ..
rm -r `basename $f .tar.gz` rm -r `basename $f .tar.gz`
done done