{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}

module System.Log.MonadLogger.Syslog
       ( runSyslogLoggingT
       , runCustomSyslogLoggingT
       , System.Posix.Syslog.Facility(..)
       )
       where

import Control.Monad.Logger
import Data.Text             ( unpack )
import System.Log.FastLogger ( fromLogStr )
import System.Posix.Syslog

#if MIN_VERSION_hsyslog(5,0,0)
import qualified Data.ByteString.Unsafe as BSU
#else
import qualified Data.ByteString.Char8 as BS8
#endif

-- | Runs a 'LoggingT' action, sending its output to Syslog.
-- Logging will use service name (logger "tag") 'hslogger',
-- and Syslog facility 'User'. Log lines will have the same
-- format as 'runStdoutLoggingT', but the 'LogLevel' will
-- be converted to the matching Syslog priority value, and
-- will be included in the logged message.
runSyslogLoggingT :: LoggingT m a -> m a
#if MIN_VERSION_hsyslog(5,0,0)
runSyslogLoggingT = runCustomSyslogLoggingT "hslogger" User
#else
runSyslogLoggingT = runCustomSyslogLoggingT "hslogger" USER
#endif

-- | Like 'runSyslogLoggingT', but specifying desired service
-- name -- (logger "tag") and Syslog facility.
runCustomSyslogLoggingT :: String          -- ^ Logger tag.
                        -> Facility        -- ^ Syslog facility.
                        -> LoggingT m a
                        -> m a
runCustomSyslogLoggingT n f = (`runLoggingT` defaultSyslogOutput n f)

-- This invokes 'formattedSyslogOutput' with 'defaultLogStr'.
-- This means that the resulting log messages are the same as
-- the default format used by "Control.Monad.Logger".
defaultSyslogOutput :: String              -- Logger tag
                    -> Facility            -- Syslog facility
                    -> Loc
                    -> LogSource
                    -> LogLevel
                    -> LogStr
                    -> IO ()
defaultSyslogOutput n f = formattedSyslogOutput n f defaultLogStr

-- Given a "Control.Monad.Logger" log formatter, this writes
-- the log to the syslog,
formattedSyslogOutput :: String            -- Logger tag
                      -> Facility          -- Syslog facility
                      -> (Loc -> LogSource -> LogLevel -> LogStr -> LogStr)
                      -> Loc
                      -> LogSource
                      -> LogLevel
                      -> LogStr
                      -> IO ()
formattedSyslogOutput name facility f l s level msg =
#if MIN_VERSION_hsyslog(5,0,0)
    withSyslog name [DelayedOpen] facility $
    BSU.unsafeUseAsCStringLen (fromLogStr $ f l s level msg) $
    syslog
      (Just facility)
      (levelToPriority level)
#else
    withSyslog defaultConfig { identifier      = BS8.pack name
                             , defaultFacility = facility
                             } $ \syslog ->
        syslog facility
            (levelToPriority level)
            (fromLogStr $ f l s level msg)
#endif

levelToPriority :: LogLevel -> Priority
levelToPriority LevelDebug = Debug
levelToPriority LevelInfo  = Info
levelToPriority LevelWarn  = Warning
levelToPriority LevelError = Error
levelToPriority (LevelOther level) =
    case level of
        "Emergency" -> Emergency
        "Alert"     -> Alert
        "Critical"  -> Critical
        "Notice"    -> Notice
        _           -> error $ "unknown log level: " ++ unpack level