--------------------------------------------------------------------------------
-- Logstash client for Haskell                                                --
--------------------------------------------------------------------------------
-- This source code is licensed under the MIT license found in the LICENSE    --
-- file in the root directory of this source tree.                            --
--------------------------------------------------------------------------------

-- | This module implements a logstash client for the tcp input plugin:
-- https://www.elastic.co/guide/en/logstash/7.10/plugins-inputs-tcp.html
module Logstash.TCP (
    LogstashTcpConfig(..),
    logstashTcp,
    logstashTcpPool,
    logstashTls,
    logstashTlsPool
) where 

--------------------------------------------------------------------------------

import Control.Monad.IO.Class

import Data.Acquire
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Default.Class
import Data.Time
import Data.Pool

import Network.Socket
import Network.Socket.ByteString (sendAll)
import Network.TLS

import Logstash.Connection

--------------------------------------------------------------------------------

-- | Represents configurations for Logstash TCP inputs.
data LogstashTcpConfig = LogstashTcpConfig {
    -- | The hostname of the server to connect to.
    LogstashTcpConfig -> String
logstashTcpHost :: String,
    -- | The port of the server to connect to.
    LogstashTcpConfig -> Int
logstashTcpPort :: Int
} deriving (LogstashTcpConfig -> LogstashTcpConfig -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: LogstashTcpConfig -> LogstashTcpConfig -> Bool
$c/= :: LogstashTcpConfig -> LogstashTcpConfig -> Bool
== :: LogstashTcpConfig -> LogstashTcpConfig -> Bool
$c== :: LogstashTcpConfig -> LogstashTcpConfig -> Bool
Eq, Int -> LogstashTcpConfig -> ShowS
[LogstashTcpConfig] -> ShowS
LogstashTcpConfig -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [LogstashTcpConfig] -> ShowS
$cshowList :: [LogstashTcpConfig] -> ShowS
show :: LogstashTcpConfig -> String
$cshow :: LogstashTcpConfig -> String
showsPrec :: Int -> LogstashTcpConfig -> ShowS
$cshowsPrec :: Int -> LogstashTcpConfig -> ShowS
Show)

instance Default LogstashTcpConfig where 
    def :: LogstashTcpConfig
def = LogstashTcpConfig{
        logstashTcpHost :: String
logstashTcpHost = String
"127.0.0.1",
        logstashTcpPort :: Int
logstashTcpPort = Int
5000
    }

-- | `connectTCP` @config@ establishes a TCP socket connection to the server
-- configured by @config@.
connectTcpSocket 
    :: MonadIO m 
    => LogstashTcpConfig 
    -> m Socket
connectTcpSocket :: forall (m :: * -> *). MonadIO m => LogstashTcpConfig -> m Socket
connectTcpSocket LogstashTcpConfig{Int
String
logstashTcpPort :: Int
logstashTcpHost :: String
logstashTcpPort :: LogstashTcpConfig -> Int
logstashTcpHost :: LogstashTcpConfig -> String
..} = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. IO a -> IO a
withSocketsDo forall a b. (a -> b) -> a -> b
$ do
    -- initialise a TCP socket for the given host and port
    let hints :: AddrInfo
hints = AddrInfo
defaultHints{ addrSocketType :: SocketType
addrSocketType = SocketType
Stream }
    AddrInfo
addr <- forall a. [a] -> a
head forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe AddrInfo -> Maybe String -> Maybe String -> IO [AddrInfo]
getAddrInfo 
        (forall a. a -> Maybe a
Just AddrInfo
hints) (forall a. a -> Maybe a
Just String
logstashTcpHost) (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show Int
logstashTcpPort)
    Socket
sock <- Family -> SocketType -> ProtocolNumber -> IO Socket
socket (AddrInfo -> Family
addrFamily AddrInfo
addr) (AddrInfo -> SocketType
addrSocketType AddrInfo
addr) (AddrInfo -> ProtocolNumber
addrProtocol AddrInfo
addr)
    Socket -> SockAddr -> IO ()
connect Socket
sock forall a b. (a -> b) -> a -> b
$ AddrInfo -> SockAddr
addrAddress AddrInfo
addr

    -- return the socket
    forall (f :: * -> *) a. Applicative f => a -> f a
pure Socket
sock

-- | `createTcpConnection` @config@ establishes a `LogstashConnection` via
-- TCP to the server configured by @config@.
createTcpConnection 
    :: MonadIO m 
    => LogstashTcpConfig 
    -> m LogstashConnection
createTcpConnection :: forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> m LogstashConnection
createTcpConnection LogstashTcpConfig
cfg = do 
    -- establish a TCP connection
    Socket
sock <- forall (m :: * -> *). MonadIO m => LogstashTcpConfig -> m Socket
connectTcpSocket LogstashTcpConfig
cfg

    -- return the Logstash connection
    forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ LogstashConnection{
        writeData :: ByteString -> IO ()
writeData = Socket -> ByteString -> IO ()
sendAll Socket
sock forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
BSL.toStrict,
        closeConnection :: IO ()
closeConnection = Socket -> IO ()
close Socket
sock
    }

-- | `logstashTcp` @config@ produces an `Acquire` for establishing
-- `LogstashConnection` values for the given TCP @config@.
logstashTcp 
    :: LogstashTcpConfig 
    -> Acquire LogstashConnection
logstashTcp :: LogstashTcpConfig -> Acquire LogstashConnection
logstashTcp LogstashTcpConfig
cfg = forall a. IO a -> (a -> IO ()) -> Acquire a
mkAcquire (forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> m LogstashConnection
createTcpConnection LogstashTcpConfig
cfg) LogstashConnection -> IO ()
closeConnection 

-- | `logstashTcpPool` @config stripes ttl resources@ produces a `Pool`
-- of `LogstashConnection` values for the given TCP @config@. The other
-- parameters are passed on directly to `createPool`.
logstashTcpPool 
    :: LogstashTcpConfig
    -> Int 
    -> NominalDiffTime
    -> Int 
    -> IO LogstashPool
logstashTcpPool :: LogstashTcpConfig
-> Int -> NominalDiffTime -> Int -> IO LogstashPool
logstashTcpPool LogstashTcpConfig
cfg = forall a.
IO a
-> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a)
createPool (forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> m LogstashConnection
createTcpConnection LogstashTcpConfig
cfg) LogstashConnection -> IO ()
closeConnection

--------------------------------------------------------------------------------

-- | `createTlsConnection` @config params@ establishes a `LogstashConnection` via
-- TLS to the server configured by @config@ and the TLS configuration given by
-- @params@.
createTlsConnection 
    :: MonadIO m 
    => LogstashTcpConfig 
    -> ClientParams
    -> m LogstashConnection
createTlsConnection :: forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> ClientParams -> m LogstashConnection
createTlsConnection LogstashTcpConfig
cfg ClientParams
params = do 
    -- establish a TCP connection
    Socket
sock <- forall (m :: * -> *). MonadIO m => LogstashTcpConfig -> m Socket
connectTcpSocket LogstashTcpConfig
cfg

    -- establish a TLS connection on top
    Context
ctx <- forall (m :: * -> *) backend params.
(MonadIO m, HasBackend backend, TLSParams params) =>
backend -> params -> m Context
contextNew Socket
sock ClientParams
params
    forall (m :: * -> *). MonadIO m => Context -> m ()
handshake Context
ctx

    -- return the Logstash connection
    forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ LogstashConnection{
        writeData :: ByteString -> IO ()
writeData = forall (m :: * -> *). MonadIO m => Context -> ByteString -> m ()
sendData Context
ctx,
        closeConnection :: IO ()
closeConnection = do 
            forall (m :: * -> *). MonadIO m => Context -> m ()
bye Context
ctx 
            Socket -> IO ()
close Socket
sock
    }

-- | `logstashTls` @config params@ produces an `Acquire` for establishing
-- `LogstashConnection` values for the given TCP @config@ and TLS @params@.
logstashTls 
    :: LogstashTcpConfig 
    -> ClientParams 
    -> Acquire LogstashConnection 
logstashTls :: LogstashTcpConfig -> ClientParams -> Acquire LogstashConnection
logstashTls LogstashTcpConfig
cfg ClientParams
params = 
    forall a. IO a -> (a -> IO ()) -> Acquire a
mkAcquire (forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> ClientParams -> m LogstashConnection
createTlsConnection LogstashTcpConfig
cfg ClientParams
params) LogstashConnection -> IO ()
closeConnection 

-- | `logstashTlsPool` @config params stripes ttl resources@ produces a 
-- `Pool` of `LogstashConnection` values for the given TCP @config@ and TLS
-- @params@. The other parameters are passed on directly to `createPool`.
logstashTlsPool
    :: LogstashTcpConfig 
    -> ClientParams 
    -> Int 
    -> NominalDiffTime
    -> Int
    -> IO LogstashPool
logstashTlsPool :: LogstashTcpConfig
-> ClientParams -> Int -> NominalDiffTime -> Int -> IO LogstashPool
logstashTlsPool LogstashTcpConfig
cfg ClientParams
params = 
    forall a.
IO a
-> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a)
createPool (forall (m :: * -> *).
MonadIO m =>
LogstashTcpConfig -> ClientParams -> m LogstashConnection
createTlsConnection LogstashTcpConfig
cfg ClientParams
params) LogstashConnection -> IO ()
closeConnection 

--------------------------------------------------------------------------------