packages feed

servant-tracing 0.1.0.1 → 0.1.0.2

raw patch · 8 files changed

+146/−11 lines, 8 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Tracing.DataDog: DataDogSpan :: Span -> DataDogSpan
+ Tracing.DataDog: instance Data.Aeson.Types.ToJSON.ToJSON Tracing.DataDog.DataDogSpan
+ Tracing.DataDog: newtype DataDogSpan
+ Tracing.DataDog: publishDataDog :: MonadIO m => String -> Manager -> [Span] -> m (Maybe (Response Text))
- Servant.Tracing: TracingInstructions :: !TraceId -> !SpanId -> !SpanId -> !Bool -> !Bool -> TracingInstructions
+ Servant.Tracing: TracingInstructions :: !TraceId -> !SpanId -> !(Maybe SpanId) -> !Bool -> !Bool -> TracingInstructions
- Servant.Tracing: [$sel:parentSpanId:TracingInstructions] :: TracingInstructions -> !SpanId
+ Servant.Tracing: [$sel:parentSpanId:TracingInstructions] :: TracingInstructions -> !(Maybe SpanId)
- Tracing.Core: TracingInstructions :: !TraceId -> !SpanId -> !SpanId -> !Bool -> !Bool -> TracingInstructions
+ Tracing.Core: TracingInstructions :: !TraceId -> !SpanId -> !(Maybe SpanId) -> !Bool -> !Bool -> TracingInstructions
- Tracing.Core: [$sel:parentSpanId:TracingInstructions] :: TracingInstructions -> !SpanId
+ Tracing.Core: [$sel:parentSpanId:TracingInstructions] :: TracingInstructions -> !(Maybe SpanId)

Files

README.md view
@@ -2,7 +2,7 @@  [![Build Status](https://travis-ci.org/ChrisCoffey/haskell-opentracing-light.svg?branch=master)](https://travis-ci.org/ChrisCoffey/haskell-opentracing-light) -This repository is the minimum required for publishing trace data to Zipkin or Jaeger. It adheres to the [Open Tracing Standard] (https://github.com/opentracing/specification/blob/master/specification.md) but is not complete. See the documentation on Hackage for module-level details.+This repository is the minimum required for publishing trace data to Zipkin or Jaeger. It adheres to the [Open Tracing Standard] (https://github.com/opentracing/specification) but is not complete. See the documentation on Hackage for module-level details.   ### Using the library@@ -24,7 +24,7 @@  ### Testing Locally with the Demo App -You can start up a compatible server for (Zipkin)[] or (Jaeger)[] via a standalone docker container. From there its a matter of seting the following environment variables:+You can start up a compatible server for [Zipkin](https://zipkin.io/pages/quickstart.html) or [Jaeger](https://jaegertracing.netlify.com/docs/deployment/) via a standalone docker container. From there its a matter of seting the following environment variables: - *TRACING_ENDPOINT*: a `String` with the fully url to a running tracing server - *TRACING_SERVICE*: a `String` name for your service 
servant-tracing.cabal view
@@ -1,5 +1,5 @@ name:           servant-tracing-version:        0.1.0.1+version:        0.1.0.2 description:    Please see the README on Github at <https://github.com/ChrisCoffey/haskell-opentracing-light#readme> homepage:       https://github.com/ChrisCoffey/haskell-opentracing-light#readme bug-reports:    https://github.com/ChrisCoffey/haskell-opentracing-light/issues@@ -48,6 +48,7 @@   exposed-modules:                 Tracing.Core               , Tracing.Zipkin+              , Tracing.DataDog               , Servant.Tracing   -- other-modules:   default-language: Haskell2010@@ -105,5 +106,6 @@       Servant.TracingSpec,       Instances,       Zipkin.ClientSpec,+      DataDog.ClientSpec,       Tracing.CoreSpec   default-language: Haskell2010
src/Servant/Tracing.hs view
@@ -9,6 +9,7 @@  import Tracing.Core (Tracer, TraceId(..), SpanId(..), MonadTracer, TracingInstructions(..)) +import Control.Arrow (first) import Control.Monad.Trans (liftIO, MonadIO) import qualified Data.Text as T import qualified Data.ByteString.Char8 as BS@@ -30,10 +31,14 @@ -- | Jaeger format: http://jaeger.readthedocs.io/en/latest/client_libraries/#propagation-format -- This allows the trace backend to reassemble downstream traces. instructionsToHeader :: TracingInstructions -> T.Text-instructionsToHeader TracingInstructions {traceId=(TraceId tid), spanId=SpanId sid, parentSpanId=SpanId pid, sample, debug} =-    toField tid<>":"<> toField  sid <> ":"<> toField pid <> ":" <> (T.pack $ show setFlags)+instructionsToHeader TracingInstructions {traceId=(TraceId tid), spanId, parentSpanId, sample, debug} =+    toField tid<>":"<>+    (toField $ unSpanId spanId) <> ":"<>+    (fromMaybe "" $ (toField . unSpanId) <$> parentSpanId) <> ":" <>+    (T.pack $ show setFlags)     where-        toField = T.pack . BS.unpack . fromMaybe "0" . BS.packHexadecimal+        unSpanId (SpanId sid) = sid+        toField = T.pack . BS.unpack . fromMaybe "" . BS.packHexadecimal         setFlags :: Int         setFlags = (if debug then 2 else 0) .|. (if sample then 1 else 0) .|. 0 @@ -48,7 +53,10 @@                 res = do                     traceId <- TraceId . fromIntegral . fst <$> hexadecimal rawTraceId                     spanId <- SpanId . fromIntegral . fst <$> hexadecimal rawSpanId-                    parentId <- SpanId . fromIntegral . fst <$> if T.null rawParentId then pure (0 :: Integer, "") else hexadecimal rawParentId+                    let resolvedPid = if T.null rawParentId+                                      then pure (Nothing, "")+                                      else first Just <$> hexadecimal rawParentId+                    parentId <- fmap (SpanId . fromIntegral) . fst <$> resolvedPid                     flagField <- fromIntegral . fst <$> hexadecimal flags                     let [sample, debug]= [sampleFlag, debugFlag] <*> [flagField]                     pure TracingInstructions {@@ -85,7 +93,7 @@     pure TracingInstructions {         traceId = TraceId newTraceId,         spanId = SpanId newSpanId,-        parentSpanId = SpanId 0,+        parentSpanId = Nothing,         debug,         sample = sample == (1::Int)         }
src/Tracing/Core.hs view
@@ -130,7 +130,7 @@     TracingInstructions {         traceId :: !TraceId,         spanId :: !SpanId,-        parentSpanId :: !SpanId,+        parentSpanId :: !(Maybe SpanId),         debug :: !Bool,         sample :: !Bool         } deriving (Eq, Show)
+ src/Tracing/DataDog.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Tracing.DataDog (+    publishDataDog,+    DataDogSpan(..)+    ) where++import Tracing.Core (Span(..), SpanId(..), OpName(..), TraceId(..), SpanContext(..),+    SpanRelation(..), SpanTag(..))++import Control.Monad.Trans (liftIO, MonadIO)+import Control.Monad (void)+import Data.Monoid ((<>), mempty)+import Data.Aeson+import Data.Maybe (fromMaybe)+import Data.Int (Int64)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString.Lex.Integral as BS+import qualified Data.HashMap.Strict as HM+import Network.HTTP.Client+++-- | Publish 'Span' in the <https://docs.datadoghq.com/api/?lang=bash#send-traces DataDog format> . No call is made+-- on an empty span list+publishDataDog :: MonadIO m =>+    String -- ^ The address of the backend server+    -> Manager+    -> [Span] -- ^ The traced spans to send to a DataDog backend+    -> m (Maybe (Response T.Text))+publishDataDog _ _ [] = pure Nothing+publishDataDog destination manager spans =+    liftIO . fmap (Just . fmap decode) $ httpLbs zipkinReq manager+    where+        decode = T.decodeUtf8 . LBS.toStrict+        req = parseRequest_ destination+        body = RequestBodyLBS . encode $ DataDogSpan <$> spans+        zipkinReq = req { method = "POST", requestBody = body, requestHeaders = [("content-type", "application/json")]}++newtype DataDogSpan = DataDogSpan Span+instance ToJSON DataDogSpan where+    toJSON (DataDogSpan span) = object $ [+        "trace_id" .= (unTrace . traceId $ context span),+        "span_id" .= (unSpan . spanId $ context span),+        "name" .=  unOp (operationName span),+        "resource" .= unOp (operationName span),+        "start" .= (floor . toNanos $ timestamp span :: Int64),+        "type" .= ("web"::T.Text),+        "duration" .= (toNanos $ duration span),+        "service" .= (serviceName span),+        "meta" .= (unTag <$> tags span)+        ] <>+        parentId (relations span)+        where+            toNanos = (*) 1000000000+            unOp (OpName n) = n+            unSpan (SpanId sid) = sid+            unTrace (TraceId tid) = tid+            parentId :: [SpanRelation] -> [(T.Text, Value)]+            parentId (ChildOf ctx:_) = ["parent_id" .= (unSpan $ spanId ctx)]+            parentId (FollowsFrom ctx:_) = ["parent_id" .= (unSpan $ spanId ctx)]+            parentId _ = []+            padLeft 0 txt = txt+            padLeft n txt+                | T.length txt < n = padLeft n ("0"<>txt)+                | otherwise = txt+            unTag (TagString a) = toJSON a+            unTag (TagBool a) = toJSON a+            unTag (TagInt a) = toJSON a+            unTag (TagDouble a) = toJSON a
+ test/DataDog/ClientSpec.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE NamedFieldPuns, OverloadedStrings #-}++module DataDog.ClientSpec (+    ddSpec+    ) where++import Tracing.DataDog+import Tracing.Core+import Instances()++import Data.Maybe (fromMaybe, maybe, isJust)+import Data.Aeson+import Data.Aeson.Types (parseMaybe)+import qualified Data.Text as T+import Test.HUnit+import Test.QuickCheck hiding (sample)+import Test.Tasty+import Test.Tasty.HUnit (testCase)+import Test.Tasty.QuickCheck (testProperty)+import Web.HttpApiData (parseUrlPiece)+import Data.Int++ddSpec :: TestTree+ddSpec = testGroup "DataDog Spec" $ [+    testCase "start is in nanoseconds" $ do+        s <- generate arbitrary+        let fromNanos = maybe 0 (/ 1000000000) . parseMaybe ( withObject "" (.: "start") ) $ toJSON (DataDogSpan (s :: Span))+        fromNanos @=? timestamp s+    , testCase "parentId set when a child Relation is present" $ do+        s <- generate arbitrary :: IO Span+        c <- generate arbitrary :: IO SpanContext+        let s' = s {relations = [ChildOf c]}+        let isPresent = maybe False (> (0:: Int64)) . parseMaybe ( withObject "" (.: "parent_id") ) $ toJSON (DataDogSpan s')+        isPresent @=? True+    , testCase "parentId set when a FollowsFrom Relation is present" $ do+        s <- generate arbitrary :: IO Span+        c <- generate arbitrary :: IO SpanContext+        let s' = s {relations = [FollowsFrom c]}+        let isPresent = maybe False (> (0::Int64)) . parseMaybe ( withObject "" (.: "parent_id") ) $ toJSON (DataDogSpan s')+        isPresent @=? True+    , testCase "parentId unset when no Relation is present" $ do+        s <- generate arbitrary :: IO Span+        let s' = s {relations = []}+        let isPresent = maybe False (> (0::Int64)) . parseMaybe ( withObject "" (.: "parent_id") ) $ toJSON (DataDogSpan s')+        isPresent @=? False+    ]
test/Servant/TracingSpec.hs view
@@ -45,11 +45,16 @@     testCase "4:3:4:3 == TracingInstructions 4 3 4 True True" $ let         inst = parseUrlPiece "4:3:4:3"         in case inst of-            Right inst -> inst @=? (TracingInstructions (TraceId 4) (SpanId 3) (SpanId 4) True True)+            Right inst -> inst @=? (TracingInstructions (TraceId 4) (SpanId 3) (Just $ SpanId 4) True True)+            Left _ -> assertFailure $ "Failed: "++ show inst,+    testCase "4:3::3 == TracingInstructions 4 3 Nothing True True" $ let+        inst = parseUrlPiece "4:3::3"+        in case inst of+            Right inst -> inst @=? (TracingInstructions (TraceId 4) (SpanId 3) Nothing True True)             Left _ -> assertFailure $ "Failed: "++ show inst     ]     where-        dummyInst1 = TracingInstructions (TraceId 1) (SpanId 1) (SpanId 0) True True+        dummyInst1 = TracingInstructions (TraceId 1) (SpanId 1) Nothing True True         extractId = maybe (-1) fst . toMaybe . T.hexadecimal         extractFields = maybe (-1) fst . toMaybe . T.hexadecimal . last . T.splitOn ":"         toMaybe (Left _) = Nothing
test/Spec.hs view
@@ -1,6 +1,7 @@  import Servant.TracingSpec (tracingProps, tracingSpecs) import Zipkin.ClientSpec (zipkinProps, zipkinSpec)+import DataDog.ClientSpec (ddSpec) import Tracing.CoreSpec (coreSpec, coreProps) import Test.Tasty @@ -10,5 +11,6 @@     tracingSpecs,     zipkinProps,     zipkinSpec,+    ddSpec,     coreSpec     ]