packages feed

natskell-0.0.0.1: internal/Lib/Logger.hs

module Lib.Logger
  ( module Lib.Logger.Types
  , defaultLogContext
  , newLogContext
  , updateLogContext
  , renderLogEntry
  , defaultLogger
  , withLogLock
  , loggerApi
  ) where

import           Control.Concurrent.STM
import           Control.Exception
import           Control.Monad
import           Control.Monad.Reader
import           Data.Maybe             (catMaybes, fromMaybe)
import           Lib.Logger.Types
import           Lib.LoggerAPI          (LoggerAPI (LoggerAPI))

defaultLogContext :: LogContext
defaultLogContext = LogContext Nothing Nothing Nothing

newLogContext :: IO (TVar LogContext)
newLogContext = newTVarIO defaultLogContext

updateLogContext :: TVar LogContext -> (LogContext -> LogContext) -> IO ()
updateLogContext ctx f = atomically $ modifyTVar' ctx f

instance MonadWithLogger AppM where
  runWithLogger cfg ctx = flip runReaderT (LoggerEnv cfg ctx) . runAppM

instance MonadLogger AppM where
  logEntry entry = AppM . ReaderT $ \(LoggerEnv (LoggerConfig minLvl out lock) _) ->
    when (leLevel entry >= minLvl) (withLogLock lock (out entry))
  getLogContext = AppM . ReaderT $ \(LoggerEnv _ ctxVar) ->
    readTVarIO ctxVar
  logMessage lvl msg = AppM . ReaderT $ \(LoggerEnv (LoggerConfig minLvl out lock) ctxVar) ->
    when (lvl >= minLvl) $ do
      ctx <- readTVarIO ctxVar
      withLogLock lock $
        out LogEntry
          { leLevel = lvl
          , leMessage = msg
          , leClientId = lcClientId ctx
          , leConnectName = lcConnectName ctx
          , leServer = lcServer ctx
          }

renderLogEntry :: LogEntry -> String
renderLogEntry entry =
  let cidStr = maybe "unset" show (leClientId entry)
      cnStr  = fromMaybe "unset" (leConnectName entry)
      label  = "cid-" ++ cidStr ++ ":cn-" ++ cnStr
      ctxParts = catMaybes
        [ Just ("client=" ++ label)
        , fmap ("server=" ++) (leServer entry)
        ]
      ctxBlock = "[" ++ unwords ctxParts ++ "]"
  in "[" ++ levelTag (leLevel entry) ++ "] " ++ ctxBlock ++ " " ++ leMessage entry

levelTag :: LogLevel -> String
levelTag Debug = "D"
levelTag Info  = "I"
levelTag Warn  = "W"
levelTag Error = "E"
levelTag Fatal = "F"

defaultLogger :: IO LoggerConfig
defaultLogger = do
  lock <- newTMVarIO ()
  pure $ LoggerConfig Info (putStrLn . renderLogEntry) lock

withLogLock :: TMVar () -> IO a -> IO a
withLogLock lock =
  bracket_ (atomically $ takeTMVar lock) (atomically $ putTMVar lock ())

loggerApi :: LoggerAPI
loggerApi = LoggerAPI runWithLogger updateLogContext logMessage