opentracing-zipkin-v1-0.1.0.0: src/OpenTracing/Zipkin/V1/Thrift.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
module OpenTracing.Zipkin.V1.Thrift
( toThriftSpan
, thriftEncodeSpan
, thriftEncodeSpans
)
where
import Control.Lens
import Data.Bifunctor
import Data.Bits
import Data.ByteString.Builder
import qualified Data.ByteString.Lazy as Lazy
import Data.ByteString.Lens
import Data.Foldable (foldl', toList)
import qualified Data.HashMap.Strict as HashMap
import Data.Int
import qualified Data.IP as IP
import Data.List.NonEmpty (NonEmpty (..))
import Data.Semigroup ((<>))
import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
import qualified Data.Vector as Vector
import OpenTracing.Log
import OpenTracing.Span
import OpenTracing.Tags
import OpenTracing.Time
import OpenTracing.Types
import OpenTracing.Zipkin.Types (Endpoint (..))
import qualified Thrift
import Thrift.Protocol.Binary
import Thrift.Transport.Empty
import Thrift.Types
import qualified ZipkinCore_Consts as Thrift
import ZipkinCore_Types
( Annotation (..)
, BinaryAnnotation (..)
, Span (..)
, endpoint_ipv4
, endpoint_ipv6
, endpoint_port
, endpoint_service_name
)
import qualified ZipkinCore_Types as Thrift
toThriftSpan
:: Endpoint
-> LogFieldsFormatter
-> FinishedSpan
-> Thrift.Span
toThriftSpan (toThriftEndpoint -> loc) logfmt s = Thrift.Span
{ span_trace_id = view (spanContext . to traceIdLo') s
, span_trace_id_high = view (spanContext . to traceIdHi') s
, span_name = view (spanOperation . lazy) s
, span_id = view (spanContext . to ctxSpanID') s
, span_parent_id = view (spanContext . to ctxParentSpanID') s
, span_annotations = annotations
, span_binary_annotations = binaryAnnotations
, span_debug = Nothing
, span_timestamp = Just tstart
, span_duration = view (spanDuration . to micros . re _Just) s
}
where
tstart = view (spanStart . to micros) s
(annotations, binaryAnnotations)
= bimap Vector.fromList Vector.fromList
. first (<> annFromLogs (view spanLogs s))
$ annFromTags (view spanTags s)
annFromTags :: Tags -> ([Thrift.Annotation], [Thrift.BinaryAnnotation])
annFromTags = perhapsLocal . foldl' go ([],[]) . HashMap.toList . fromTags
where
go acc (SpanKind sk) =
let ann = Thrift.Annotation
{ annotation_timestamp = tstart
, annotation_host = Just loc
, annotation_value = case sk of
RPCClient -> Thrift.cLIENT_SEND
RPCServer -> Thrift.sERVER_RECV
Producer -> Thrift.mESSAGE_SEND
Consumer -> Thrift.mESSAGE_RECV
}
in first (ann:) acc
go acc (k,v) =
let (anntyp, annval) = toThriftTag v
ann = Thrift.BinaryAnnotation
{ binaryAnnotation_key = view lazy k
, binaryAnnotation_value = annval
, binaryAnnotation_annotation_type = anntyp
, binaryAnnotation_host = Just loc
}
in second (ann:) acc
-- if we don't have a 'SpanKind', we're supposed to tell Zipkin about us
-- via a 'BinaryAnnotation'
perhapsLocal ([],bs) = ([],) . (:bs) $ Thrift.BinaryAnnotation
{ binaryAnnotation_key = Thrift.lOCAL_COMPONENT
, binaryAnnotation_value = encodeUtf8 $ endpoint_service_name loc
, binaryAnnotation_annotation_type = Thrift.STRING
, binaryAnnotation_host = Just loc
}
perhapsLocal xs = xs
annFromLogs :: [LogRecord] -> [Thrift.Annotation]
annFromLogs = foldl' go []
where
go acc (LogRecord t fs) = Thrift.Annotation
{ annotation_timestamp = micros t
, annotation_host = Just loc
, annotation_value = case fs of
(Event x :| []) -> view lazy x -- proper zipkin annotation
fields -> decodeUtf8 . toLazyByteString $ logfmt fields
}
: acc
thriftEncodeSpan :: Thrift.Span -> Lazy.ByteString
thriftEncodeSpan = Thrift.encode_Span (BinaryProtocol EmptyTransport)
thriftEncodeSpans :: Traversable t => t Thrift.Span -> Lazy.ByteString
thriftEncodeSpans
= thriftEncodeVal
. TList (T_STRUCT Thrift.typemap_Span)
. toList
. fmap Thrift.from_Span
thriftEncodeVal :: ThriftVal -> Lazy.ByteString
thriftEncodeVal = Thrift.serializeVal (BinaryProtocol EmptyTransport)
toThriftTag :: TagVal -> (Thrift.AnnotationType, Lazy.ByteString)
toThriftTag (BoolT v) = (Thrift.BOOL, if v then "1" else "0")
toThriftTag (StringT v) = (Thrift.STRING, view (lazy . to encodeUtf8) v)
toThriftTag (IntT v) = (Thrift.I64, toLazyByteString . int64BE $ v)
toThriftTag (DoubleT v) = (Thrift.DOUBLE, toLazyByteString . doubleBE $ v)
toThriftTag (BinaryT v) = (Thrift.BYTES, v)
toThriftEndpoint :: Endpoint -> Thrift.Endpoint
toThriftEndpoint Endpoint{..} = Thrift.Endpoint
{ endpoint_ipv4 = packIPv4 $ fromIPv4 ipv4
, endpoint_port = maybe 0 (fromIntegral . fromPort) port
, endpoint_service_name = view lazy serviceName
, endpoint_ipv6 = packIPv6 . fromIPv6 <$> ipv6
}
where
packIPv4 :: IP.IPv4 -> Int32
packIPv4 ip =
let [a,b,c,d] = IP.fromIPv4 ip
in fromIntegral $ a `shiftL` 24 .|. b `shiftL` 16 .|. c `shiftL` 8 .|. d
packIPv6 :: IP.IPv6 -> Lazy.ByteString
packIPv6 = view packedBytes . map fromIntegral . IP.fromIPv6b
traceIdLo' :: SpanContext -> Int64
traceIdLo' = fromIntegral . traceIdLo . ctxTraceID
traceIdHi' :: SpanContext -> Maybe Int64
traceIdHi' = fmap fromIntegral . traceIdHi . ctxTraceID
ctxSpanID' :: SpanContext -> Int64
ctxSpanID' = fromIntegral . ctxSpanID
ctxParentSpanID' :: SpanContext -> Maybe Int64
ctxParentSpanID' = fmap fromIntegral . ctxParentSpanID