{-# LANGUAGE CPP #-}
module System.Log.Handler.Syslog(
SyslogHandler,
openlog,
#ifndef mingw32_HOST_OS
openlog_local,
#endif
openlog_remote,
openlog_generic,
Facility(..),
Option(..)
) where
import qualified Control.Exception as E
import System.Log
import System.Log.Formatter
import System.Log.Handler
import Data.Bits
import qualified Network.Socket as S
import qualified Network.Socket.ByteString as SBS
import qualified Network.BSD as S
import Data.List (genericDrop)
#ifndef mingw32_HOST_OS
import System.Posix.Process(getProcessID)
#endif
import System.IO
import Control.Monad (void, when)
import UTF8
send :: S.Socket -> String -> IO Int
send s = SBS.send s . toUTF8BS
sendTo :: S.Socket -> String -> S.SockAddr -> IO Int
sendTo s str = SBS.sendTo s (toUTF8BS str)
code_of_pri :: Priority -> Int
code_of_pri p = case p of
EMERGENCY -> 0
ALERT -> 1
CRITICAL -> 2
ERROR -> 3
WARNING -> 4
NOTICE -> 5
INFO -> 6
DEBUG -> 7
data Facility =
KERN
| USER
| MAIL
| DAEMON
| AUTH
| SYSLOG
| LPR
| NEWS
| UUCP
| CRON
| AUTHPRIV
| FTP
| LOCAL0
| LOCAL1
| LOCAL2
| LOCAL3
| LOCAL4
| LOCAL5
| LOCAL6
| LOCAL7
deriving (Eq, Show, Read)
code_of_fac :: Facility -> Int
code_of_fac f = case f of
KERN -> 0
USER -> 1
MAIL -> 2
DAEMON -> 3
AUTH -> 4
SYSLOG -> 5
LPR -> 6
NEWS -> 7
UUCP -> 8
CRON -> 9
AUTHPRIV -> 10
FTP -> 11
LOCAL0 -> 16
LOCAL1 -> 17
LOCAL2 -> 18
LOCAL3 -> 19
LOCAL4 -> 20
LOCAL5 -> 21
LOCAL6 -> 22
LOCAL7 -> 23
makeCode :: Facility -> Priority -> Int
makeCode fac pri =
let faccode = code_of_fac fac
pricode = code_of_pri pri in
(faccode `shiftL` 3) .|. pricode
data Option = PID
| PERROR
deriving (Eq,Show,Read)
data SyslogHandler = SyslogHandler {options :: [Option],
facility :: Facility,
identity :: String,
logsocket :: S.Socket,
address :: S.SockAddr,
sock_type :: S.SocketType,
priority :: Priority,
formatter :: LogFormatter SyslogHandler
}
openlog :: String
-> [Option]
-> Facility
-> Priority
-> IO SyslogHandler
#ifdef mingw32_HOST_OS
openlog = openlog_remote S.AF_INET "localhost" 514
#elif darwin_HOST_OS
openlog = openlog_local "/var/run/syslog"
#else
openlog = openlog_local "/dev/log"
#endif
#ifndef mingw32_HOST_OS
openlog_local :: String
-> String
-> [Option]
-> Facility
-> Priority
-> IO SyslogHandler
openlog_local fifopath ident options' fac pri =
do (s, t) <- do
s <- S.socket S.AF_UNIX S.Stream 0
tryStream s `E.catch` (onIOException (fallbackToDgram s))
openlog_generic s (S.SockAddrUnix fifopath) t ident options' fac pri
where onIOException :: IO a -> E.IOException -> IO a
onIOException a _ = a
tryStream :: S.Socket -> IO (S.Socket, S.SocketType)
tryStream s =
do S.connect s (S.SockAddrUnix fifopath)
return (s, S.Stream)
fallbackToDgram :: S.Socket -> IO (S.Socket, S.SocketType)
fallbackToDgram s =
do S.close s
d <- S.socket S.AF_UNIX S.Datagram 0
return (d, S.Datagram)
#endif
openlog_remote :: S.Family
-> S.HostName
-> S.PortNumber
-> String
-> [Option]
-> Facility
-> Priority
-> IO SyslogHandler
openlog_remote fam hostname port ident options' fac pri =
do
he <- S.getHostByName hostname
s <- S.socket fam S.Datagram 0
let addr = S.SockAddrInet port (head (S.hostAddresses he))
openlog_generic s addr S.Datagram ident options' fac pri
openlog_generic :: S.Socket
-> S.SockAddr
-> S.SocketType
-> String
-> [Option]
-> Facility
-> Priority
-> IO SyslogHandler
openlog_generic sock addr sock_t ident opt fac pri =
return (SyslogHandler {options = opt,
facility = fac,
identity = ident,
logsocket = sock,
address = addr,
sock_type = sock_t,
priority = pri,
formatter = syslogFormatter
})
syslogFormatter :: LogFormatter SyslogHandler
syslogFormatter sh (p,msg) logname =
let format = "[$loggername/$prio] $msg"
in varFormatter [] format sh (p,msg) logname
instance LogHandler SyslogHandler where
setLevel sh p = sh{priority = p}
getLevel sh = priority sh
setFormatter sh f = sh{formatter = f}
getFormatter sh = formatter sh
emit sh (prio, msg) _ = do
when (elem PERROR (options sh)) (hPutStrLn stderr msg)
pidPart <- getPidPart
void $ sendstr (toSyslogFormat msg pidPart)
where
sendstr :: String -> IO String
sendstr [] = return []
sendstr omsg = do
sent <- case sock_type sh of
S.Datagram -> sendTo (logsocket sh) omsg (address sh)
S.Stream -> send (logsocket sh) omsg
sendstr (genericDrop sent omsg)
toSyslogFormat msg' pidPart =
"<" ++ code ++ ">" ++ identity' ++ pidPart ++ ": " ++ msg' ++ "\0"
code = show $ makeCode (facility sh) prio
identity' = identity sh
getPidPart = if elem PID (options sh)
then getPid >>= \pid -> return ("[" ++ pid ++ "]")
else return ""
getPid :: IO String
getPid =
#ifndef mingw32_HOST_OS
getProcessID >>= return . show
#else
return "windows"
#endif
close sh = S.close (logsocket sh)