packages feed

hs-opentelemetry-instrumentation-http-client 0.0.2.0 → 0.1.0.0

raw patch · 7 files changed

+245/−182 lines, 7 filesdep +unordered-containersdep ~hs-opentelemetry-apidep ~hs-opentelemetry-instrumentation-conduitsetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: unordered-containers

Dependency ranges changed: hs-opentelemetry-api, hs-opentelemetry-instrumentation-conduit

API changes (from Hackage documentation)

+ OpenTelemetry.Instrumentation.HttpClient: strippedHostName :: String -> String
- OpenTelemetry.Instrumentation.HttpClient: httpLbs :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response ByteString)
+ OpenTelemetry.Instrumentation.HttpClient: httpLbs :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response ByteString)
- OpenTelemetry.Instrumentation.HttpClient: httpNoBody :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response ())
+ OpenTelemetry.Instrumentation.HttpClient: httpNoBody :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response ())
- OpenTelemetry.Instrumentation.HttpClient: responseOpen :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response BodyReader)
+ OpenTelemetry.Instrumentation.HttpClient: responseOpen :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> Manager -> m (Response BodyReader)
- OpenTelemetry.Instrumentation.HttpClient: withResponse :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> Manager -> (Response BodyReader -> m a) -> m a
+ OpenTelemetry.Instrumentation.HttpClient: withResponse :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> Manager -> (Response BodyReader -> m a) -> m a
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpBS :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> m (Response ByteString)
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpBS :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> m (Response ByteString)
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpJSON :: (MonadUnliftIO m, FromJSON a) => HttpClientInstrumentationConfig -> Request -> m (Response a)
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpJSON :: (MonadUnliftIO m, FromJSON a, HasCallStack) => HttpClientInstrumentationConfig -> Request -> m (Response a)
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpJSONEither :: (FromJSON a, MonadUnliftIO m) => HttpClientInstrumentationConfig -> Request -> m (Response (Either JSONException a))
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpJSONEither :: (FromJSON a, MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> m (Response (Either JSONException a))
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpLBS :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> m (Response ByteString)
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpLBS :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> m (Response ByteString)
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpNoBody :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> m (Response ())
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpNoBody :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> m (Response ())
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpSink :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> (Response () -> ConduitM ByteString Void m a) -> m a
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpSink :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> (Response () -> ConduitM ByteString Void m a) -> m a
- OpenTelemetry.Instrumentation.HttpClient.Simple: httpSource :: (MonadUnliftIO m, MonadResource m) => HttpClientInstrumentationConfig -> Request -> (Response (ConduitM i ByteString m ()) -> ConduitM i o m r) -> ConduitM i o m r
+ OpenTelemetry.Instrumentation.HttpClient.Simple: httpSource :: (MonadUnliftIO m, MonadResource m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> (Response (ConduitM i ByteString m ()) -> ConduitM i o m r) -> ConduitM i o m r
- OpenTelemetry.Instrumentation.HttpClient.Simple: withResponse :: MonadUnliftIO m => HttpClientInstrumentationConfig -> Request -> (Response (ConduitM i ByteString m ()) -> m a) -> m a
+ OpenTelemetry.Instrumentation.HttpClient.Simple: withResponse :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Request -> (Response (ConduitM i ByteString m ()) -> m a) -> m a

Files

ChangeLog.md view
@@ -1,3 +1,13 @@ # Changelog for hs-opentelemetry-instrumentation-http-client  ## Unreleased changes++## 0.1.0.0++### Breaking changes++- Use `HashMap Text Attribute` instead of `[(Text, Attribute)]` as attributes++## 0.0.2.0++- Added option to name an http request, falling back to the URL as the name of the span if left unnamed
Setup.hs view
@@ -1,2 +1,4 @@ import Distribution.Simple++ main = defaultMain
hs-opentelemetry-instrumentation-http-client.cabal view
@@ -1,20 +1,20 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack -name:           hs-opentelemetry-instrumentation-http-client-version:        0.0.2.0-description:    Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/http-client#readme>-homepage:       https://github.com/iand675/hs-opentelemetry#readme-bug-reports:    https://github.com/iand675/hs-opentelemetry/issues-author:         Ian Duncan-maintainer:     ian@iankduncan.com-copyright:      2021 Ian Duncan-license:        BSD3-license-file:   LICENSE-build-type:     Simple+name:               hs-opentelemetry-instrumentation-http-client+version:            0.1.0.0+description:        Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/http-client#readme>+homepage:           https://github.com/iand675/hs-opentelemetry#readme+bug-reports:        https://github.com/iand675/hs-opentelemetry/issues+author:             Ian Duncan, Jade Lovelace+maintainer:         ian@iankduncan.com+copyright:          2023 Ian Duncan, Mercury Technologies+license:            BSD3+license-file:       LICENSE+build-type:         Simple extra-source-files:     README.md     ChangeLog.md@@ -39,14 +39,15 @@     , bytestring     , case-insensitive     , conduit-    , hs-opentelemetry-api ==0.0.3.*-    , hs-opentelemetry-instrumentation-conduit ==0.0.1.*+    , hs-opentelemetry-api ==0.1.*+    , hs-opentelemetry-instrumentation-conduit >=0.0.1 && <0.2     , http-client     , http-client-tls     , http-conduit     , http-types     , text     , unliftio+    , unordered-containers   default-language: Haskell2010  test-suite hs-opentelemetry-instrumentation-http-client-test@@ -63,8 +64,8 @@     , bytestring     , case-insensitive     , conduit-    , hs-opentelemetry-api ==0.0.3.*-    , hs-opentelemetry-instrumentation-conduit ==0.0.1.*+    , hs-opentelemetry-api ==0.1.*+    , hs-opentelemetry-instrumentation-conduit >=0.0.1 && <0.2     , hs-opentelemetry-instrumentation-http-client     , http-client     , http-client-tls@@ -72,4 +73,5 @@     , http-types     , text     , unliftio+    , unordered-containers   default-language: Haskell2010
src/OpenTelemetry/Instrumentation/HttpClient.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+ {- | Offer a few options for HTTP instrumentation  - Add attributes via 'Request' and 'Response' to an existing span (Best)@@ -6,58 +7,69 @@ - Provide a middleware to pull from the thread-local state (okay) - Modify the global manager to pull from the thread-local state (least good, can't be helped sometimes) -}-module OpenTelemetry.Instrumentation.HttpClient -  ( withResponse-  , httpLbs-  , httpNoBody-  , responseOpen-  , httpClientInstrumentationConfig-  , HttpClientInstrumentationConfig(..)-  , module X-  ) where+module OpenTelemetry.Instrumentation.HttpClient (+  withResponse,+  httpLbs,+  httpNoBody,+  responseOpen,+  httpClientInstrumentationConfig,+  HttpClientInstrumentationConfig (..),+  module X,+) where++import Control.Monad.IO.Class (MonadIO (..)) import qualified Data.ByteString.Lazy as L-import Control.Monad.IO.Class ( MonadIO(..) )+import GHC.Stack+import Network.HTTP.Client as X hiding (httpLbs, httpNoBody, responseOpen, withResponse)+import qualified Network.HTTP.Client as Client import OpenTelemetry.Context.ThreadLocal-import OpenTelemetry.Trace.Core-    ( defaultSpanArguments,-      SpanArguments(kind),-      SpanKind(Client),-      inSpan',-    )+import OpenTelemetry.Instrumentation.HttpClient.Raw (+  HttpClientInstrumentationConfig (..),+  httpClientInstrumentationConfig,+  httpTracerProvider,+  instrumentRequest,+  instrumentResponse,+ )+import OpenTelemetry.Trace.Core (+  SpanArguments (kind),+  SpanKind (Client),+  addAttributesToSpanArguments,+  callerAttributes,+  defaultSpanArguments,+  inSpan'',+ )+import UnliftIO (MonadUnliftIO, askRunInIO) -import Network.HTTP.Client as X hiding (withResponse, httpLbs, httpNoBody, responseOpen)-import qualified Network.HTTP.Client as Client-import OpenTelemetry.Instrumentation.HttpClient.Raw-    ( HttpClientInstrumentationConfig(..),-      instrumentRequest,-      instrumentResponse, httpClientInstrumentationConfig, httpTracerProvider )-import UnliftIO ( MonadUnliftIO, askRunInIO )  spanArgs :: SpanArguments-spanArgs = defaultSpanArguments { kind = Client }+spanArgs = defaultSpanArguments {kind = Client} --- | Instrumented variant of @Network.HTTP.Client.withResponse@------ Perform a @Request@ using a connection acquired from the given @Manager@,--- and then provide the @Response@ to the given function. This function is--- fully exception safe, guaranteeing that the response will be closed when the--- inner function exits. It is defined as:------ > withResponse req man f = bracket (responseOpen req man) responseClose f------ It is recommended that you use this function in place of explicit calls to--- 'responseOpen' and 'responseClose'.------ You will need to use functions such as 'brRead' to consume the response--- body.-withResponse :: (MonadUnliftIO m) => HttpClientInstrumentationConfig-             -> Client.Request-             -> Client.Manager-             -> (Client.Response Client.BodyReader -> m a)-             -> m a++{- | Instrumented variant of @Network.HTTP.Client.withResponse@++ Perform a @Request@ using a connection acquired from the given @Manager@,+ and then provide the @Response@ to the given function. This function is+ fully exception safe, guaranteeing that the response will be closed when the+ inner function exits. It is defined as:++ > withResponse req man f = bracket (responseOpen req man) responseClose f++ It is recommended that you use this function in place of explicit calls to+ 'responseOpen' and 'responseClose'.++ You will need to use functions such as 'brRead' to consume the response+ body.+-}+withResponse+  :: (MonadUnliftIO m, HasCallStack)+  => HttpClientInstrumentationConfig+  -> Client.Request+  -> Client.Manager+  -> (Client.Response Client.BodyReader -> m a)+  -> m a withResponse httpConf req man f = do   t <- httpTracerProvider-  inSpan' t "withResponse" spanArgs $ \_wrSpan -> do+  inSpan'' t "withResponse" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_wrSpan -> do     ctxt <- getContext     -- TODO would like to capture the req/resp time specifically     -- inSpan "http.request" (defaultSpanArguments { startingKind = Client }) $ \httpReqSpan -> do@@ -67,15 +79,17 @@       _ <- instrumentResponse httpConf ctxt resp       runInIO $ f resp --- | A convenience wrapper around 'withResponse' which reads in the entire--- response body and immediately closes the connection. Note that this function--- performs fully strict I\/O, and only uses a lazy ByteString in its response--- for memory efficiency. If you are anticipating a large response body, you--- are encouraged to use 'withResponse' and 'brRead' instead.-httpLbs :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response L.ByteString)++{- | A convenience wrapper around 'withResponse' which reads in the entire+ response body and immediately closes the connection. Note that this function+ performs fully strict I\/O, and only uses a lazy ByteString in its response+ for memory efficiency. If you are anticipating a large response body, you+ are encouraged to use 'withResponse' and 'brRead' instead.+-}+httpLbs :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response L.ByteString) httpLbs httpConf req man = do   t <- httpTracerProvider-  inSpan' t "httpLbs" spanArgs $ \_ -> do+  inSpan'' t "httpLbs" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- liftIO $ Client.httpLbs req' man@@ -83,51 +97,54 @@     pure resp  --- | A convenient wrapper around 'withResponse' which ignores the response--- body. This is useful, for example, when performing a HEAD request.-httpNoBody :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response ())+{- | A convenient wrapper around 'withResponse' which ignores the response+ body. This is useful, for example, when performing a HEAD request.+-}+httpNoBody :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response ()) httpNoBody httpConf req man = do   t <- httpTracerProvider-  inSpan' t "httpNoBody" spanArgs $ \_ -> do+  inSpan'' t "httpNoBody" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- liftIO $ Client.httpNoBody req' man     _ <- instrumentResponse httpConf ctxt resp     pure resp --- | The most low-level function for initiating an HTTP request.------ The first argument to this function gives a full specification--- on the request: the host to connect to, whether to use SSL,--- headers, etc. Please see 'Request' for full details.  The--- second argument specifies which 'Manager' should be used.------ This function then returns a 'Response' with a--- 'BodyReader'.  The 'Response' contains the status code--- and headers that were sent back to us, and the--- 'BodyReader' contains the body of the request.  Note--- that this 'BodyReader' allows you to have fully--- interleaved IO actions during your HTTP download, making it--- possible to download very large responses in constant memory.------ An important note: the response body returned by this function represents a--- live HTTP connection. As such, if you do not use the response body, an open--- socket will be retained indefinitely. You must be certain to call--- 'responseClose' on this response to free up resources.------ This function automatically performs any necessary redirects, as specified--- by the 'redirectCount' setting.------ When implementing a (reverse) proxy using this function or relating--- functions, it's wise to remove Transfer-Encoding:, Content-Length:,--- Content-Encoding: and Accept-Encoding: from request and response--- headers to be relayed.-responseOpen :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response Client.BodyReader)++{- | The most low-level function for initiating an HTTP request.++ The first argument to this function gives a full specification+ on the request: the host to connect to, whether to use SSL,+ headers, etc. Please see 'Request' for full details.  The+ second argument specifies which 'Manager' should be used.++ This function then returns a 'Response' with a+ 'BodyReader'.  The 'Response' contains the status code+ and headers that were sent back to us, and the+ 'BodyReader' contains the body of the request.  Note+ that this 'BodyReader' allows you to have fully+ interleaved IO actions during your HTTP download, making it+ possible to download very large responses in constant memory.++ An important note: the response body returned by this function represents a+ live HTTP connection. As such, if you do not use the response body, an open+ socket will be retained indefinitely. You must be certain to call+ 'responseClose' on this response to free up resources.++ This function automatically performs any necessary redirects, as specified+ by the 'redirectCount' setting.++ When implementing a (reverse) proxy using this function or relating+ functions, it's wise to remove Transfer-Encoding:, Content-Length:,+ Content-Encoding: and Accept-Encoding: from request and response+ headers to be relayed.+-}+responseOpen :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response Client.BodyReader) responseOpen httpConf req man = do   t <- httpTracerProvider-  inSpan' t "responseOpen" spanArgs $ \_ -> do+  inSpan'' t "responseOpen" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- liftIO $ Client.responseOpen req' man-    _ <-instrumentResponse httpConf ctxt resp+    _ <- instrumentResponse httpConf ctxt resp     pure resp
src/OpenTelemetry/Instrumentation/HttpClient/Raw.hs view
@@ -1,52 +1,64 @@+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-}+ module OpenTelemetry.Instrumentation.HttpClient.Raw where+ import Control.Applicative ((<|>))-import Control.Monad.IO.Class-import OpenTelemetry.Context (Context, lookupSpan)-import OpenTelemetry.Context.ThreadLocal-import OpenTelemetry.Trace.Core-import OpenTelemetry.Propagator-import Network.HTTP.Client-import Network.HTTP.Types import Control.Monad (forM_, when)+import Control.Monad.IO.Class import qualified Data.ByteString.Char8 as B-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import Data.Foldable (Foldable(toList)) import Data.CaseInsensitive (foldedCase)+import qualified Data.HashMap.Strict as H+import Data.Maybe (mapMaybe) import qualified Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Network.HTTP.Client+import Network.HTTP.Types+import OpenTelemetry.Context (Context, lookupSpan)+import OpenTelemetry.Context.ThreadLocal+import OpenTelemetry.Propagator+import OpenTelemetry.Trace.Core + data HttpClientInstrumentationConfig = HttpClientInstrumentationConfig   { requestName :: Maybe T.Text   , requestHeadersToRecord :: [HeaderName]   , responseHeadersToRecord :: [HeaderName]   } + instance Semigroup HttpClientInstrumentationConfig where-  l <> r = HttpClientInstrumentationConfig-    { requestName = requestName r <|> requestName l -- flipped on purpose: last writer wins-    , requestHeadersToRecord = requestHeadersToRecord l <> requestHeadersToRecord r-    , responseHeadersToRecord = responseHeadersToRecord l <> responseHeadersToRecord r-    }+  l <> r =+    HttpClientInstrumentationConfig+      { requestName = requestName r <|> requestName l -- flipped on purpose: last writer wins+      , requestHeadersToRecord = requestHeadersToRecord l <> requestHeadersToRecord r+      , responseHeadersToRecord = responseHeadersToRecord l <> responseHeadersToRecord r+      } + instance Monoid HttpClientInstrumentationConfig where-  mempty = HttpClientInstrumentationConfig-    { requestName = Nothing-    , requestHeadersToRecord = mempty-    , responseHeadersToRecord = mempty-    }+  mempty =+    HttpClientInstrumentationConfig+      { requestName = Nothing+      , requestHeadersToRecord = mempty+      , responseHeadersToRecord = mempty+      } + httpClientInstrumentationConfig :: HttpClientInstrumentationConfig httpClientInstrumentationConfig = mempty -  -- TODO see if we can avoid recreating this on each request without being more invasive with the interface-httpTracerProvider :: MonadIO m => m Tracer++-- TODO see if we can avoid recreating this on each request without being more invasive with the interface+httpTracerProvider :: (MonadIO m) => m Tracer httpTracerProvider = do   tp <- getGlobalTracerProvider   pure $ makeTracer tp "hs-opentelemetry-instrumentation-http-client" tracerOptions + instrumentRequest-  :: MonadIO m+  :: (MonadIO m)   => HttpClientInstrumentationConfig   -> Context   -> Request@@ -56,35 +68,40 @@   forM_ (lookupSpan ctxt) $ \s -> do     let url =           T.decodeUtf8-          ((if secure req then "https://" else "http://") <> host req <> ":" <> B.pack (show $ port req) <> path req <> queryString req)+            ((if secure req then "https://" else "http://") <> host req <> ":" <> B.pack (show $ port req) <> path req <> queryString req)     updateName s $ Data.Maybe.fromMaybe url $ requestName conf-    addAttributes s-      [ ( "http.method", toAttribute $ T.decodeUtf8 $ method req)-      , ( "http.url", toAttribute url)-      , ( "http.target", toAttribute $ T.decodeUtf8 (path req <> queryString req))-      , ( "http.host", toAttribute $ T.decodeUtf8 $ host req)-      , ( "http.scheme", toAttribute $ TextAttribute $ if secure req then "https" else "http")-      , ( "http.flavor"+    addAttributes+      s+      [ ("http.method", toAttribute $ T.decodeUtf8 $ method req)+      , ("http.url", toAttribute url)+      , ("http.target", toAttribute $ T.decodeUtf8 (path req <> queryString req))+      , ("http.host", toAttribute $ T.decodeUtf8 $ host req)+      , ("http.scheme", toAttribute $ TextAttribute $ if secure req then "https" else "http")+      ,+        ( "http.flavor"         , toAttribute $ case requestVersion req of             (HttpVersion major minor) -> T.pack (show major <> "." <> show minor)         )-      , ( "http.user_agent"+      ,+        ( "http.user_agent"         , toAttribute $ maybe "" T.decodeUtf8 (lookup hUserAgent $ requestHeaders req)         )       ]-    addAttributes s $-      concatMap-        (\h -> toList $ (\v -> ("http.request.header." <> T.decodeUtf8 (foldedCase h), toAttribute (T.decodeUtf8 v))) <$> lookup h (requestHeaders req)) $-        requestHeadersToRecord conf+    addAttributes s+      $ H.fromList+      $ mapMaybe+        (\h -> (\v -> ("http.request.header." <> T.decodeUtf8 (foldedCase h), toAttribute (T.decodeUtf8 v))) <$> lookup h (requestHeaders req))+      $ requestHeadersToRecord conf    hdrs <- inject (getTracerProviderPropagators $ getTracerTracerProvider tp) ctxt $ requestHeaders req-  pure $ req-    { requestHeaders = hdrs-    }+  pure $+    req+      { requestHeaders = hdrs+      }   instrumentResponse-  :: MonadIO m+  :: (MonadIO m)   => HttpClientInstrumentationConfig   -> Context   -> Response a@@ -96,7 +113,8 @@   forM_ (lookupSpan ctxt') $ \s -> do     when (statusCode (responseStatus resp) >= 400) $ do       setStatus s (Error "")-    addAttributes s+    addAttributes+      s       [ ("http.status_code", toAttribute $ statusCode $ responseStatus resp)       -- TODO       -- , ("http.request_content_length",	_)@@ -108,7 +126,8 @@       -- , ("net.peer.ip")       -- , ("net.peer.port")       ]-    addAttributes s $-      concatMap-        (\h -> toList $ (\v -> ("http.response.header." <> T.decodeUtf8 (foldedCase h), toAttribute (T.decodeUtf8 v))) <$> lookup h (responseHeaders resp)) $-        responseHeadersToRecord conf+    addAttributes s+      $ H.fromList+      $ mapMaybe+        (\h -> (\v -> ("http.response.header." <> T.decodeUtf8 (foldedCase h), toAttribute (T.decodeUtf8 v))) <$> lookup h (responseHeaders resp))+      $ responseHeadersToRecord conf
src/OpenTelemetry/Instrumentation/HttpClient/Simple.hs view
@@ -1,107 +1,119 @@ {-# LANGUAGE OverloadedStrings #-}-module OpenTelemetry.Instrumentation.HttpClient.Simple -  ( httpBS-  , httpLBS-  , httpNoBody-  , httpJSON-  , httpJSONEither-  , httpSink-  , httpSource-  , withResponse-  , httpClientInstrumentationConfig-  , HttpClientInstrumentationConfig(..)-  , module X-  ) where-import qualified Network.HTTP.Simple as Simple-import Network.HTTP.Simple as X hiding (httpBS, httpLBS, httpNoBody, httpJSON, httpJSONEither, httpSink, httpSource, withResponse)++module OpenTelemetry.Instrumentation.HttpClient.Simple (+  httpBS,+  httpLBS,+  httpNoBody,+  httpJSON,+  httpJSONEither,+  httpSink,+  httpSource,+  withResponse,+  httpClientInstrumentationConfig,+  HttpClientInstrumentationConfig (..),+  module X,+) where++import Conduit (MonadResource, lift)+import Data.Aeson (FromJSON) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import Data.Conduit (ConduitM, Void)+import GHC.Stack+import Network.HTTP.Simple as X hiding (httpBS, httpJSON, httpJSONEither, httpLBS, httpNoBody, httpSink, httpSource, withResponse)+import qualified Network.HTTP.Simple as Simple import OpenTelemetry.Context.ThreadLocal-import OpenTelemetry.Trace.Core-import OpenTelemetry.Instrumentation.HttpClient.Raw import qualified OpenTelemetry.Instrumentation.Conduit as Conduit+import OpenTelemetry.Instrumentation.HttpClient.Raw+import OpenTelemetry.Trace.Core import UnliftIO-import Data.Aeson (FromJSON)-import Conduit (MonadResource, lift) + spanArgs :: SpanArguments-spanArgs = defaultSpanArguments { kind = Client }+spanArgs = defaultSpanArguments {kind = Client} -httpBS :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response B.ByteString)++httpBS :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response B.ByteString) httpBS httpConf req = do   t <- httpTracerProvider-  inSpan' t "httpBS" spanArgs $ \_s -> do+  inSpan' t "httpBS" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- Simple.httpBS req'     _ <- instrumentResponse httpConf ctxt resp     pure resp -httpLBS :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response L.ByteString)++httpLBS :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response L.ByteString) httpLBS httpConf req = do   t <- httpTracerProvider-  inSpan' t "httpLBS" spanArgs $ \_s -> do+  inSpan' t "httpLBS" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- Simple.httpLBS req'     _ <- instrumentResponse httpConf ctxt resp     pure resp -httpNoBody :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response ())++httpNoBody :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response ()) httpNoBody httpConf req = do   t <- httpTracerProvider-  inSpan' t "httpNoBody" spanArgs $ \_s -> do+  inSpan' t "httpNoBody" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- Simple.httpNoBody req'     _ <- instrumentResponse httpConf ctxt resp     pure resp -httpJSON :: (MonadUnliftIO m, FromJSON a) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response a)++httpJSON :: (MonadUnliftIO m, FromJSON a, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response a) httpJSON httpConf req = do   t <- httpTracerProvider-  inSpan' t "httpJSON" spanArgs $ \_s -> do+  inSpan' t "httpJSON" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- Simple.httpJSON req'     _ <- instrumentResponse httpConf ctxt resp     pure resp -httpJSONEither :: (FromJSON a, MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response (Either Simple.JSONException a))++httpJSONEither :: (FromJSON a, MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> m (Simple.Response (Either Simple.JSONException a)) httpJSONEither httpConf req = do   t <- httpTracerProvider-  inSpan' t "httpJSONEither" spanArgs $ \_s -> do+  inSpan' t "httpJSONEither" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     resp <- Simple.httpJSONEither req'     _ <- instrumentResponse httpConf ctxt resp     pure resp -httpSink :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response () -> ConduitM B.ByteString Void m a) -> m a++httpSink :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response () -> ConduitM B.ByteString Void m a) -> m a httpSink httpConf req f = do   t <- httpTracerProvider-  inSpan' t "httpSink" spanArgs $ \_s -> do +  inSpan' t "httpSink" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     Simple.httpSink req' $ \resp -> do       _ <- instrumentResponse httpConf ctxt resp       f resp -httpSource :: (MonadUnliftIO m, MonadResource m) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response (ConduitM i B.ByteString m ()) -> ConduitM i o m r) -> ConduitM i o m r++httpSource :: (MonadUnliftIO m, MonadResource m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response (ConduitM i B.ByteString m ()) -> ConduitM i o m r) -> ConduitM i o m r httpSource httpConf req f = do   t <- httpTracerProvider-  Conduit.inSpan t "httpSource" spanArgs $ \_s -> do+  Conduit.inSpan t "httpSource" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- lift getContext     req' <- instrumentRequest httpConf ctxt req     Simple.httpSource req' $ \resp -> do       _ <- instrumentResponse httpConf ctxt resp       f resp -withResponse :: (MonadUnliftIO m) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response (ConduitM i B.ByteString m ()) -> m a) -> m a++withResponse :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Simple.Request -> (Simple.Response (ConduitM i B.ByteString m ()) -> m a) -> m a withResponse httpConf req f = do   t <- httpTracerProvider-  inSpan' t "withResponse" spanArgs $ \_s -> do+  inSpan' t "withResponse" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_s -> do     ctxt <- getContext     req' <- instrumentRequest httpConf ctxt req     Simple.withResponse req' $ \resp -> do
test/Spec.hs view
@@ -1,2 +1,3 @@+ main :: IO () main = putStrLn "Test suite not yet implemented"