packages feed

opentracing-jaeger-0.2.0: src/OpenTracing/Jaeger/CollectorReporter.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE ViewPatterns      #-}

module OpenTracing.Jaeger.CollectorReporter
    ( JaegerCollectorOptions
    , jaegerCollectorOptions
    , jcoManager
    , jcoServiceName
    , jcoServiceTags
    , jcoAddr
    , jcoErrorLog

    , defaultJaegerCollectorAddr

    , JaegerCollector
    , newJaegerCollector
    , closeJaegerCollector
    , withJaegerCollector

    , jaegerCollectorReporter

    , jaegerPropagation

    , newManager
    , defaultManagerSettings
    )
where

import           Control.Lens                   (makeLenses, set, view)
import           Control.Monad                  (unless)
import           Control.Monad.Catch
import           Control.Monad.IO.Class
import           Data.ByteString.Lazy           (fromStrict)
import           Data.ByteString.Builder
import           Data.Text                      (Text)
import           Data.Vector                    (fromList)
import qualified Jaeger.Types                   as Thrift
import           Network.HTTP.Client
import           Network.HTTP.Types.Status
import           OpenTracing.Jaeger.Propagation (jaegerPropagation)
import           OpenTracing.Jaeger.Thrift
import           OpenTracing.Reporting
import           OpenTracing.Span
import           OpenTracing.Tags
import           OpenTracing.Types
import qualified Pinch

newtype JaegerCollector = JaegerCollector { fromJaegerCollector :: BatchEnv }

data JaegerCollectorOptions = JaegerCollectorOptions
    { _jcoManager     :: Manager
    , _jcoServiceName :: Text
    , _jcoServiceTags :: Tags
    , _jcoAddr        :: Addr 'HTTP
    , _jcoErrorLog    :: Builder -> IO ()
    }

makeLenses ''JaegerCollectorOptions

jaegerCollectorOptions :: Manager -> Text -> JaegerCollectorOptions
jaegerCollectorOptions mgr srv = JaegerCollectorOptions
    { _jcoManager     = mgr
    , _jcoServiceName = srv
    , _jcoServiceTags = mempty
    , _jcoAddr        = defaultJaegerCollectorAddr
    , _jcoErrorLog    = defaultErrorLog
    }

defaultJaegerCollectorAddr :: Addr 'HTTP
defaultJaegerCollectorAddr = HTTPAddr "127.0.0.1" 14268 False

newJaegerCollector :: JaegerCollectorOptions -> IO JaegerCollector
newJaegerCollector opt@JaegerCollectorOptions{..} = do
    rq <- mkReq
    fmap JaegerCollector
        . newBatchEnv
        . set boptErrorLog _jcoErrorLog . batchOptions
        $ reporter _jcoManager _jcoErrorLog rq tproc
  where
    mkReq = do
        rq <- parseRequest
                    $ "http://" <> view (jcoAddr . addrHostName) opt
                   <> ":"
                   <> show (view (jcoAddr . addrPort) opt)
                   <> "/api/traces?format=jaeger.thrift"
        pure rq { method = "POST", secure = view (jcoAddr . addrSecure) opt }

    tproc = toThriftProcess _jcoServiceName _jcoServiceTags


closeJaegerCollector :: JaegerCollector -> IO ()
closeJaegerCollector = closeBatchEnv . fromJaegerCollector

withJaegerCollector
    :: ( MonadIO   m
       , MonadMask m
       )
    => JaegerCollectorOptions
    -> (JaegerCollector -> m a)
    -> m a
withJaegerCollector opts =
    bracket (liftIO $ newJaegerCollector opts) (liftIO . closeJaegerCollector)


jaegerCollectorReporter :: MonadIO m => JaegerCollector -> FinishedSpan -> m ()
jaegerCollectorReporter = batchReporter . fromJaegerCollector


reporter
    :: Manager
    -> (Builder -> IO ())
    -> Request
    -> Thrift.Process
    -> [FinishedSpan]
    -> IO ()
reporter mgr errlog rq tproc (fromList -> spans) = do
    rs <- responseStatus <$> httpLbs rq { requestBody = body } mgr
    unless (statusIsSuccessful rs) $
        errlog $ shortByteString "Error from Jaeger Collector: "
              <> intDec (statusCode rs)
              <> char8 '\n'
  where
    body = RequestBodyLBS . fromStrict . serializeBatch $ toThriftBatch tproc spans

    -- nb. collector accepts 'BinaryProtocol', but agent 'CompactProtocol'
    serializeBatch = Pinch.encode Pinch.binaryProtocol