{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances, RecordWildCards #-}
module System.Log.Heavy.Format
( LogMessageWithTime (..),
defaultLogFormat,
formatLogMessage,
formatLogMessage'
) where
import Control.Applicative
import Control.Monad
import Data.List (intercalate)
import Data.Maybe
import Data.Monoid
import Data.Default
import qualified Data.Map as M
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as Builder
import qualified Data.Text.Lazy.Builder.Int as Builder
import System.Log.FastLogger
import qualified Data.Text.Format.Heavy as F
import qualified Data.Text.Format.Heavy.Parse as PF
import Data.Text.Format.Heavy.Formats (Conversion (..))
import Data.Text.Format.Heavy.Build (convertText)
import Data.Attoparsec.Text
import Language.Haskell.TH.Syntax (Loc (..))
import Prelude hiding (takeWhile)
import System.Log.Heavy.Types
import System.Log.Heavy.Level
data LogMessageWithTime = LogMessageWithTime FormattedTime LogMessage
data LevelFormatSelector = ShowLevelName | ShowLevelValue | ShowSyslog
deriving (Eq, Show)
data LevelFormat = LevelFormat {
lfSelector :: LevelFormatSelector
, lfConvert :: Maybe Conversion
}
deriving (Eq, Show)
instance Default LevelFormat where
def = LevelFormat ShowLevelName Nothing
instance F.IsVarFormat LevelFormat where
parseVarFormat text = either (Left . show) Right $ doParse $ TL.toStrict text
where
pFormat :: Parser LevelFormat
pFormat = do
mbSelector <- optionMaybe (pSelector <?> "level detail selector")
let selector = fromMaybe ShowLevelName mbSelector
mbConvert <- optionMaybe (pConvert <?> "conversion specification")
return $ LevelFormat selector mbConvert
optionMaybe p = option Nothing (Just <$> p)
pSelector :: Parser LevelFormatSelector
pSelector =
try (string "value" >> return ShowLevelValue) <|>
try (string "syslog" >> return ShowSyslog) <|>
(string "name" >> return ShowLevelName)
pConvert :: Parser Conversion
pConvert = do
char '~'
conv <- satisfy (`elem` ['u', 'l', 't'])
case conv of
'u' -> return UpperCase
'l' -> return LowerCase
't' -> return TitleCase
doParse text = parseOnly pFormat text
instance F.Formatable Level where
formatVar Nothing level = Right $ Builder.fromText (levelName level)
formatVar (Just fmt) level = do
lf <- F.parseVarFormat fmt
let text = case lfSelector lf of
ShowLevelName -> Builder.fromText (levelName level)
ShowLevelValue -> Builder.decimal (levelInt level)
ShowSyslog -> Builder.fromString (show $ levelToPriority level)
Right $ convertText (lfConvert lf) text
instance F.VarContainer LogMessageWithTime where
lookupVar name (LogMessageWithTime ftime (LogMessage {..})) =
case lookup name stdVariables of
Just value -> Just value
Nothing -> Just $ fromMaybe (F.Variable TL.empty) $ msum $ map (lookup name) contextVariables
where
stdVariables :: [(TL.Text, F.Variable)]
stdVariables = [("level", F.Variable lmLevel),
("source", F.Variable $ intercalate "." lmSource),
("location", F.Variable $ show $ loc_start lmLocation),
("line", F.Variable $ fst $ loc_start lmLocation),
("file", F.Variable $ loc_filename lmLocation),
("package", F.Variable $ loc_package lmLocation),
("time", F.Variable ftime),
("message", F.Variable formattedMessage),
("fullcontext", F.Variable fullContext)]
contextVariables :: [[(TL.Text, F.Variable)]]
contextVariables = map lcfVariables lmContext
fullContext :: TL.Text
fullContext = TL.concat $ map showContextVar $ M.assocs $ M.fromList $ concat contextVariables
showContextVar :: (TL.Text, F.Variable) -> TL.Text
showContextVar (name, value) = name <> "=" <> formatVar value <> "; "
formatVar :: F.Variable -> TL.Text
formatVar var = either error Builder.toLazyText $ F.formatVar Nothing var
formattedMessage =
let fmt = PF.parseFormat' lmFormatString
in F.format fmt lmFormatVars
defaultLogFormat :: F.Format
defaultLogFormat = PF.parseFormat' "{time} [{level}] {source}: {message}\n"
formatLogMessage :: F.Format -> LogMessage -> FormattedTime -> LogStr
formatLogMessage fmt msg ftime = toLogStr $ formatLogMessage' fmt msg ftime
formatLogMessage' :: F.Format -> LogMessage -> FormattedTime -> TL.Text
formatLogMessage' fmt msg ftime = F.format fmt $ LogMessageWithTime ftime msg