[Syslog TCP client stuff John Goerzen **20080207062649] { hunk ./en/ch31-sockets.xml 263 - + + + TCP Syslog Client + + Now, let's write a client for our TCP syslog protocol. This + client will be similar to the UDP client, but there are a + couple of changes. First, since TCP is a streaming protocol, + we can send data using a &Handle; rather than using the + lower-level socket operations. Secondly, we no longer need to + store the destination address in the + SyslogHandle since we will be using + connect to establish the TCP connection. + Finally, we need a way to know where one message ends and the + next begins. With UDP, that was easy because each message was + a discrete logical packet. With TCP, we'll just use the + newline character '\n' as the + end-of-message marker, though that means that no individual + message may contain the newline. Here's our code: + + &syslogtcpclient.hs:all; + + We can try it out under &ghci;. If you still have the TCP + server running from earlier, your session might look something + like this: + + +Prelude> :l syslogtcpclient.hs +Loading package base ... linking ... done. +[1 of 2] Compiling SyslogTypes ( SyslogTypes.hs, interpreted ) +[2 of 2] Compiling Main ( syslogtcpclient.hs, interpreted ) +Ok, modules loaded: Main, SyslogTypes. +*Main> openlog "localhost" "10514" "tcptest" +Loading package parsec-2.1.0.0 ... linking ... done. +Loading package network-2.1.0.0 ... linking ... done. +*Main> sl <- openlog "localhost" "10514" "tcptest" +*Main> syslog sl USER INFO "This is my TCP message" +*Main> syslog sl USER INFO "This is my TCP message again" +*Main> closelog sl + + + Over on the server, you'll see something like this: + + +From 127.0.0.1:46319: syslogtcpserver.hs: client connnected +From 127.0.0.1:46319: <9>tcptest: This is my TCP message +From 127.0.0.1:46319: <9>tcptest: This is my TCP message again +From 127.0.0.1:46319: syslogtcpserver.hs: client disconnected + + + The <9> is the priority and facility + code being sent along, just as it was with UDP. + + hunk ./examples/ch31/syslogclient.ghci 5 +closelog h addfile ./examples/ch31/syslogtcpclient.hs hunk ./examples/ch31/syslogtcpclient.hs 1 - +{-- snippet all --} +-- ch31/syslogtcpclient.hs + +import Data.Bits +import Network.Socket +import Network.BSD +import Data.List +import SyslogTypes +import System.IO + +data SyslogHandle = + SyslogHandle {slHandle :: Handle, + slProgram :: String} + +openlog :: HostName -- ^ Remote hostname, or localhost + -> String -- ^ Port number or name; 514 is default + -> String -- ^ Name to log under + -> IO SyslogHandle -- ^ Handle to use for logging +openlog hostname port progname = + do -- Look up the hostname and port. Either raises an exception + -- or returns a nonempty list. First element in that list + -- is supposed to be the best option. + addrinfos <- getAddrInfo Nothing (Just hostname) (Just port) + let serveraddr = head addrinfos + + -- Establish a socket for communication + sock <- socket (addrFamily serveraddr) Stream defaultProtocol + + -- Mark the socket for keep-alive handling since it may be idle + -- for long periods of time + setSocketOption sock KeepAlive 1 + + -- Connect to server + connect sock (addrAddress serveraddr) + + -- Make a Handle out of it for convenience + h <- socketToHandle sock WriteMode + + -- We're going to set buffering to BlockBuffering and then + -- explicitly call hFlush after each message, below, so that + -- messages get logged immediately + hSetBuffering h (BlockBuffering Nothing) + + -- Save off the socket, program name, and server address in a handle + return $ SyslogHandle h progname + +syslog :: SyslogHandle -> Facility -> Priority -> String -> IO () +syslog syslogh fac pri msg = + do hPutStrLn (slHandle syslogh) sendmsg + -- Make sure that we send data immediately + hFlush (slHandle syslogh) + where code = makeCode fac pri + sendmsg = "<" ++ show code ++ ">" ++ (slProgram syslogh) ++ + ": " ++ msg + +closelog :: SyslogHandle -> IO () +closelog syslogh = hClose (slHandle syslogh) + +{- | Convert a facility and a priority into a syslog code -} +makeCode :: Facility -> Priority -> Int +makeCode fac pri = + let faccode = codeOfFac fac + pricode = fromEnum pri + in + (faccode `shiftL` 3) .|. pricode +{-- /snippet all --} }