module Colog.Syslog.Handler
( SyslogHandler (shClose)
, mkSyslogHandler
, withSyslog
, withSyslogGeneric
, logSyslogMessage
) where
import Universum
import Colog.Core.Action (LogAction (..))
import Colog.Syslog.Config (SyslogConfig (..), Collector (..))
import Colog.Syslog.Message (Message (..))
import Colog.Syslog.Priority (Priority (..))
import Control.Exception (IOException)
import Network.Socket hiding (send, sendTo, recv, recvFrom)
import Network.Socket.ByteString
import System.Info (os)
import System.Posix.Process (getProcessID)
import Fmt ((+|), (|+), (|++|), (+||), (||+))
import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp)
data SyslogHandler = SyslogHandler
{ shSend :: Message -> IO ()
, shClose :: IO ()
}
mkSyslogHandler :: SyslogConfig -> IO SyslogHandler
mkSyslogHandler SyslogConfig {..} = do
(skt, sAddr, sType) <- openSyslogSocket collector
let shSend = \Message {..} -> do
pid <- if
| onWindows -> return "[windows]"
| otherwise -> getProcessID >>= \p -> return ("["+||p||+"]" :: Text)
let priority = Priority facility msgSeverity
msg = encodeUtf8
(""+|priority|++|appName|++|pid|+": "+|msgContent|+"\0" :: Text)
void $ case sType of
Datagram -> sendTo skt msg sAddr
_ -> send skt msg
shClose = close skt
return $ SyslogHandler {..}
openSyslogSocket :: Collector -> IO (Socket, SockAddr, SocketType)
openSyslogSocket = \case
AutoLocal -> if
| onWindows -> openSyslogSocket $ Remote AF_INET "localhost" 514
| onMacOs -> openSyslogSocket $ Local "/var/run/syslog"
| otherwise -> openSyslogSocket $ Local "/dev/log"
Local path -> if
| onWindows -> fail "Local is not supported on Windows, you'll \
\probably want to use AutoLocal instead"
| otherwise -> do
let unixAddr = SockAddrUnix path
skt <- socket AF_UNIX Stream defaultProtocol
tryRes <- try $ connect skt unixAddr
case tryRes :: Either IOException () of
Right _ -> return (skt, unixAddr, Stream)
Left _ -> do
close skt
dskt <- socket AF_UNIX Datagram defaultProtocol
return (dskt, unixAddr, Datagram)
Remote family hostName port -> do
let hints = defaultHints
{ addrFlags = [AI_NUMERICSERV]
, addrFamily = family
, addrSocketType = Datagram
}
addrInfo:_ <- getAddrInfo (Just hints) (Just hostName) (Just $ show port)
skt <- socket family Datagram defaultProtocol
return (skt, addrAddress addrInfo, Datagram)
withSyslog :: SyslogConfig -> (SyslogHandler -> IO r) -> IO r
withSyslog config = bracket (mkSyslogHandler config) shClose
withSyslogGeneric :: MonadBaseControl IO m => SyslogConfig -> (SyslogHandler -> m r) -> m r
withSyslogGeneric config = liftBaseOp (bracket (mkSyslogHandler config) shClose)
logSyslogMessage :: MonadIO m => SyslogHandler -> LogAction m Message
logSyslogMessage SyslogHandler{..} = LogAction $ liftIO . shSend
onWindows :: Bool
onWindows = os == "mingw32"
onMacOs :: Bool
onMacOs = os == "darwin"