instana-haskell-trace-sdk 0.5.0.1 → 0.6.0.0
raw patch · 31 files changed
+975/−301 lines, 31 filesdep +case-insensitivenew-component:exe:downstream-target
Dependencies added: case-insensitive
Files
- CHANGELOG.md +59/−0
- CONTRIBUTING.md +1/−1
- ChangeLog.md +0/−42
- README.md +6/−4
- instana-haskell-trace-sdk.cabal +38/−3
- src/Instana/SDK/Internal/AgentConnection/AgentReady.hs +1/−2
- src/Instana/SDK/Internal/AgentConnection/ConnectLoop.hs +6/−1
- src/Instana/SDK/Internal/Id.hs +9/−5
- src/Instana/SDK/Internal/WireSpan.hs +0/−1
- src/Instana/SDK/Internal/Worker.hs +10/−2
- src/Instana/SDK/SDK.hs +220/−68
- src/Instana/SDK/Span/SimpleSpan.hs +49/−0
- src/Instana/Wai/Middleware/Entry/Internal.hs +7/−18
- test/agent-stub/Instana/SDK/AgentStub/Util.hs +1/−1
- test/apps/downstream-target/Main.hs +180/−0
- test/apps/wai-with-middleware/Main.hs +10/−9
- test/apps/wai/Main.hs +18/−16
- test/integration/Instana/SDK/IntegrationTest/BracketApi.hs +13/−4
- test/integration/Instana/SDK/IntegrationTest/Connection.hs +15/−9
- test/integration/Instana/SDK/IntegrationTest/HttpHelper.hs +21/−27
- test/integration/Instana/SDK/IntegrationTest/HttpTracing.hs +42/−10
- test/integration/Instana/SDK/IntegrationTest/LowLevelApi.hs +8/−3
- test/integration/Instana/SDK/IntegrationTest/Metrics.hs +1/−1
- test/integration/Instana/SDK/IntegrationTest/Runner.hs +54/−18
- test/integration/Instana/SDK/IntegrationTest/ServiceName.hs +2/−0
- test/integration/Instana/SDK/IntegrationTest/Suite.hs +43/−3
- test/integration/Instana/SDK/IntegrationTest/TestHelper.hs +24/−15
- test/integration/Instana/SDK/IntegrationTest/TestSuites.hs +11/−5
- test/integration/Instana/SDK/IntegrationTest/WaiMiddleware.hs +41/−13
- test/unit/Instana/SDK/Internal/IdTest.hs +38/−12
- test/unit/Instana/SDK/Internal/SpanTest.hs +47/−8
+ CHANGELOG.md view
@@ -0,0 +1,59 @@+# Changelog for instana-haskell-trace-sdk++## 0.6.0.0+- Fix: Pass span ID of exit span downstream with X-INSTANA-S instead of the entry span's span ID.+- Fix: Capture HTTP status code in `withHttpEntry` (formely `withCorrelatedHttpEntry`).+- Breaking: Rename `Instana.SDK.SDK.withCorrelatedHttpEntry` to `withHttpEntry`. The motivation is that this function should be used by client code in almost all cases, so its name should suggest itself as the obvious choice for tracing HTTP entry spans.+- Breaking: Rename `Instana.SDK.SDK.withHttpEntry` to `withHttpEntry_`. See above.+- Breaking: Rename `Instana.SDK.SDK.currentTraceId` (with return type `Instana.SDK.Internal.Id.Id`) to `currentTraceIdInternal`. The function `Instana.SDK.SDK.currentTraceId` returns type `String` now.+- Provide `Instana.SDK.SDK.postProcessHttpRespons` for cases where `withHttpEntry_` needs to be used instead of `withHttpEntry`.+- Provide new convenience accessors `Instana.SDK.SDK`:+ - `currentSpan` (provides the currently active span in a simplified format),+ - `currentTraceId` (provides the trace ID of the currently active trace),+ - `currentSpanId` (provides the span ID of the currently active span), and+ - `currentParentId` (provides the parent ID of the currently active span).+- Remove deprecated attribute `span.ta`.++## 0.5.0.1+- No changes, only documentation updates.++## 0.5.0.0+- Add support for website monitoring back end correlation via Server-Timing.+- Add support for website monitoring back end correlation via X-INSTANA-L/correlationType/correlationId.++## 0.4.0.0+- Accomodate for breaking changes in `network-3.0.0.0`.++## 0.3.0.0+- Honor the environment variable `INSTANA_SERVICE_NAME` to override the default service name in Instana.+- Add a configuration option for overriding the default service name in Instana.+- Add `InstanaSDK.setServiceName` to override the default service name in Instana on a per-call basis.+- Fix: Send correct SDK spans. This is a breaking change. Several functions were renamed or have changed their signature.+In detail:++ - Use `InstanaSDK.addTag` instead of `InstanaSDK.addData` (for SDK+ spans).+ - Use `InstanaSDK.addTagAt` instead of `InstanaSDK.addDataAt` (for SDK spans).+ - For registered spans, replace `InstanaSDK.addData` and `InstanaSDK.addDataAt` with `InstanaSDK.addRegisteredData` and `InstanaSDK.addRegisteredDataAt`. Note that you should probably not create registered spans, but only use SDK spans.+ - Usages like `startEntry "some.span.name"` or `withEntry "some.span.name"` (that is, the span name is passed directly as a literal) will simply continue to work as expected but will require `OverloadedStrings` to be active.+ - Usages where the span name is stored in a `Data.Text` value first and then passed to `startEntry`/`withEntry`/etc. will break. You can fix those by importing+```+import qualified Instana.SDK.Span.SpanType as SpanType+```+and then wrapping the span name in `SpanType.SdkSpan`. For example:+```+spanName = T.pack "some.span.name"+InstanaSDK.withRootEntry instana spanName ...+```+becomes:+```+spanType = SpanType.SdkSpan $ T.pack "some.span.name"+InstanaSDK.withRootEntry instana spanType ...+```++## 0.2.0.0+- Add WAI middleware plug-in to trace HTTP entries automatically.++## 0.1.0.0+- Initial release+
CONTRIBUTING.md view
@@ -59,7 +59,7 @@ Publishing a New Release ------------------------ -* Make sure ChangeLog.md has an entry for the upcoming release.+* Make sure CHANGELOG.md has an entry for the upcoming release. * Bump the version in: * `README.md` (installation instructions/extra-deps) * `instana-haskell-trace-sdk.cabal`
− ChangeLog.md
@@ -1,42 +0,0 @@-# Changelog for instana-haskell-trace-sdk--## 0.5.0.0-- Add support for website monitoring back end correlation via Server-Timing.-- Add support for website monitoring back end correlation via X-INSTANA-L/correlationType/correlationId.--## 0.4.0.0-- Accomodate for breaking changes in `network-3.0.0.0`.--## 0.3.0.0-- Honor the environment variable `INSTANA_SERVICE_NAME` to override the default service name in Instana.-- Add a configuration option for overriding the default service name in Instana.-- Add `InstanaSDK.setServiceName` to override the default service name in Instana on a per-call basis.-- Fix: Send correct SDK spans. This is a breaking change. Several functions were renamed or have changed their signature.-In detail:-- - Use `InstanaSDK.addTag` instead of `InstanaSDK.addData` (for SDK- spans).- - Use `InstanaSDK.addTagAt` instead of `InstanaSDK.addDataAt` (for SDK spans).- - For registered spans, replace `InstanaSDK.addData` and `InstanaSDK.addDataAt` with `InstanaSDK.addRegisteredData` and `InstanaSDK.addRegisteredDataAt`. Note that you should probably not create registered spans, but only use SDK spans.- - Usages like `startEntry "some.span.name"` or `withEntry "some.span.name"` (that is, the span name is passed directly as a literal) will simply continue to work as expected but will require `OverloadedStrings` to be active.- - Usages where the span name is stored in a `Data.Text` value first and then passed to `startEntry`/`withEntry`/etc. will break. You can fix those by importing-```-import qualified Instana.SDK.Span.SpanType as SpanType-```-and then wrapping the span name in `SpanType.SdkSpan`. For example:-```-spanName = T.pack "some.span.name"-InstanaSDK.withRootEntry instana spanName ...-```-becomes:-```-spanType = SpanType.SdkSpan $ T.pack "some.span.name"-InstanaSDK.withRootEntry instana spanType ...-```--## 0.2.0.0-- Add WAI middleware plug-in to trace HTTP entries automatically.--## 0.1.0.0-- Initial release-
README.md view
@@ -20,7 +20,7 @@ ``` extra-deps:-- instana-haskell-trace-sdk-0.5.0.1+- instana-haskell-trace-sdk-0.6.0.0 ``` Depending on the stack resolver you use, you might also need to add `aeson-extra` to your extra-deps:@@ -32,6 +32,8 @@ Usage ----- +<i>Example app: Take a look at the [Monad Shop](https://github.com/instana/monad-shop) To see the Instana Haskell Trace SDK in action.</i>+ ### Initialization Before using the SDK, you need to initialize it once, usually during application startup.@@ -140,11 +142,11 @@ * `withRootEntry`: Creates an entry span that is the root of a trace (it has no parent span). * `withEntry`: Creates an entry span that has a parent span.-* `withCorrelatedHttpEntry`: A convenience function that examines an incoming HTTP request for Instana tracing headers and creates an entry span. It will automatically add the correct metadata to the span. It will also add (or append to) the HTTP response header (`Server-Timing`) that is used for website monitoring back end correlation. (The latter part is the difference to `withHttpEntry`, plus the slightly different type signature.) This function should be preferred over `withHttpEntry`. You do not need to handle incoming HTTP requests at all when using the Instana WAI middleware plug-in (see above).-* `withHttpEntry`: A convenience function that examines an incoming HTTP request for Instana tracing headers and creates an entry span. It will automatically add the correct metadata to the span. It is recommended to use `withCorrelatedHttpEntry` instead of this function to also automatically add the HTTP response header for website monitoring back end correlation. Alternatively you can also call `addWebsiteMonitoringBackEndCorrelation` with the WAI Response value before handing it off to WAI's `respond` function. You do not need to handle incoming HTTP requests at all when using the Instana WAI middleware plug-in (see above).+* `withHttpEntry`: A convenience function that examines an incoming HTTP request for Instana tracing headers and creates an entry span. It will automatically add the correct metadata to the span. Note that you do not need to handle incoming HTTP requests at all when using the Instana WAI middleware plug-in (see above).+* `withHttpEntry_`: A variant of `withHttpEntry` with a more general type signature, but less features. It will automatically continue the trace from incoming headers just like `withHttpEntry` does, but it will not capture the status code of the HTTP response or add the response header for website monitoring back end correlation (Server-Timing). It is recommended to use `withHttpEntry` instead of this function, if possible. Alternatively, you can also call `postProcessHttpResponse` inside the `withHttpEntry_` block to cover the two missing features mentioned above. Note that you do not need to handle incoming HTTP requests at all when using the Instana WAI middleware plug-in. * `withExit`: Creates an exit span. This can only be called inside a `withRootEntry` or an `withEntry` call, as an exit span needs an entry span as its parent. * `withHttpExit`: Creates an exit span for a given HTTP client request. It will automatically add the correct metadata to the span so it should be preferred to `withExit` when tracing outgoing HTTP requests. It will also add HTTP headers to the request to propagate the trace context downstream.-* `addWebsiteMonitoringBackEndCorrelation`: Adds an additional HTTP response header (`Server-Timing`) to the given HTTP response. This header enables website monitoring back end correlation. In case the response already has a Server-Timing header, a value is appended to the existing Server-Timing list. Client code should rarely have the need to call this directly. Instead, capture incoming HTTP requests with 'withCorrelatedHttpEntry', which adds the response header automatically.+* `postProcessHttpResponse`: Processes the response of an HTTP entry. This function needs be called while the HTTP entry span is still active. It can be used inside a `withHttpEntry_` block or between `startHttpEntry` and `completeEntry`. This function accomplishes two things: It captures the HTTP status code from the response and adds it as an annotation to the currently active span. It also adds an additional HTTP response header (Server-Timing) to the given HTTP response that enables website monitoring back end correlation. Client code should rarely have the need to call this directly. Instead, capture incoming HTTP requests with `withHttpEntry`, which does both of these things automatically. #### Low Level API/Explicit Start And Complete
instana-haskell-trace-sdk.cabal view
@@ -1,5 +1,5 @@ name: instana-haskell-trace-sdk-version: 0.5.0.1+version: 0.6.0.0 synopsis: SDK for adding custom Instana tracing support to Haskell applications. description: Please also see the README on Github at <https://github.com/instana/haskell-trace-sdk#readme> homepage: https://www.instana.com/@@ -12,12 +12,13 @@ license-file: LICENSE build-type: Simple stability: experimental-cabal-version: >= 1.10+cabal-version: 2.0 extra-source-files:- ChangeLog.md+ CHANGELOG.md README.md CONTRIBUTING.md+ LICENSE source-repository head type: git@@ -73,6 +74,7 @@ Instana.SDK.Span.ExitSpan Instana.SDK.Span.NonRootEntry Instana.SDK.Span.RootEntry+ Instana.SDK.Span.SimpleSpan Instana.SDK.Span.Span Instana.SDK.Span.SpanType Instana.SDK.TracingHeaders@@ -106,10 +108,13 @@ Instana.SDK.Internal.Worker Instana.Wai.Middleware.Entry.Internal Paths_instana_haskell_trace_sdk+ autogen-modules:+ Paths_instana_haskell_trace_sdk default-language: Haskell2010 executable instana-haskell-example-exe+ -- scope: private main-is: Main.hs hs-source-dirs: example-app@@ -184,6 +189,7 @@ Instana.SDK.Internal.Secrets Instana.SDK.Internal.SpanStack Instana.SDK.Internal.ServerTiming+ Instana.SDK.Span.SimpleSpan Instana.SDK.Span.Span Instana.SDK.TracingHeaders -- dependencies of modules under test@@ -198,6 +204,7 @@ executable instana-haskell-agent-stub+ -- scope: private main-is: Main.hs hs-source-dirs: test/agent-stub, test/shared@@ -286,6 +293,7 @@ executable instana-haskell-test-wai-server+ -- scope: private main-is: Main.hs hs-source-dirs: test/apps/wai@@ -311,6 +319,7 @@ default-language: Haskell2010 executable instana-haskell-test-wai-with-middleware-server+ -- scope: private main-is: Main.hs hs-source-dirs: test/apps/wai-with-middleware@@ -330,6 +339,32 @@ , http-types , instana-haskell-trace-sdk , text+ , unix+ , wai+ , warp+ default-language: Haskell2010++executable downstream-target+ -- scope: private+ main-is: Main.hs+ hs-source-dirs:+ test/apps/downstream-target+ if flag(dev)+ ghc-options:+ -Wall -Werror -threaded -rtsopts "-with-rtsopts=-T -N "+ else+ ghc-options:+ -Wall -threaded -rtsopts "-with-rtsopts=-T -N "+ build-depends:+ base >=4.7 && <5+ , aeson+ , binary+ , bytestring+ , case-insensitive+ , containers+ , hslogger+ , http-client+ , http-types , unix , wai , warp
src/Instana/SDK/Internal/AgentConnection/AgentReady.hs view
@@ -131,8 +131,7 @@ STM.atomically $ STM.writeTVar (InternalContext.connectionState context) state infoM instanaLogger $- "🎉 agent connection established for process " ++ pidTranslationStr ++- " 🎉"+ "Agent connection established for process " ++ pidTranslationStr return () else do warningM instanaLogger $
src/Instana/SDK/Internal/AgentConnection/ConnectLoop.hs view
@@ -112,6 +112,10 @@ establishAgentConnection context processInfo = do currentState <- STM.atomically $ STM.readTVar (InternalContext.connectionState context)++ -- debugM instanaLogger $+ -- "Checking agent connection, current state is " ++ show currentState+ -- Do nothing if a connection attempt is already in progress or connection has -- already been established. if currentState /= Unconnected@@ -121,7 +125,8 @@ STM.atomically $ STM.writeTVar (InternalContext.connectionState context) AgentHostLookup- debugM instanaLogger $ "agent connection is not up, attempting reconnect"+ debugM instanaLogger $+ "Agent connection is not up, attempting (re)connect" -- Initial status: Unconnected -- step 1: do agent host looup (retry forever until an agent has -- been found)
src/Instana/SDK/Internal/Id.hs view
@@ -23,6 +23,7 @@ import qualified Data.Aeson as Aeson import Data.Aeson.Types (Parser) import qualified Data.ByteString.Char8 as BSC8+import qualified Data.String (IsString (..)) import Data.Text (Text) import qualified Data.Text as T import GHC.Generics@@ -53,6 +54,10 @@ Aeson.String . toText +instance Data.String.IsString Id where+ fromString = fromString++ appendAsHex :: Int -> String -> Int -> String appendAsHex noOfComponents accumulator intValue = appendPaddedHex accumulator intValue@@ -90,11 +95,6 @@ floor $ logBase (2 :: Double) $ fromIntegral (maxBound :: Int) --- |Converts a string into an ID.-fromString :: String -> Id-fromString = IdString-- -- |Converts an ID into a String toString :: Id -> String toString theId =@@ -109,6 +109,10 @@ (reverse intComponents) IdString string -> string++--- |Converts a string into an ID.+fromString :: String -> Id+fromString = IdString -- |Converts an ID into a Text
src/Instana/SDK/Internal/WireSpan.hs view
@@ -136,7 +136,6 @@ , "p" .= parentId span_ , "n" .= spanName span_ , "ts" .= timestamp span_- , "ta" .= ("haskell" :: String) , "d" .= duration span_ , "k" .= kind span_ , "ec" .= errorCount span_
src/Instana/SDK/Internal/Worker.hs view
@@ -306,7 +306,11 @@ _ -> 0 if statusCode == 404- then+ then do+ warningM instanaLogger $+ "Received HTTP 404 when sending spans to " +++ show traceEndpointUrl +++ ", resetting connection state to unconnected." resetToUnconnected context else do debugM instanaLogger $ show e@@ -430,7 +434,11 @@ _ -> 0 if statusCode == 404- then+ then do+ warningM instanaLogger $+ "Received HTTP 404 when sending metrics to " +++ show metricsEndpointUrl +++ ", resetting connection state to unconnected." resetToUnconnected context else do debugM instanaLogger $ show e
src/Instana/SDK/SDK.hs view
@@ -20,16 +20,23 @@ , addWebsiteMonitoringBackEndCorrelation , agentHost , agentPort+ , captureHttpStatus , completeEntry , completeExit+ , currentSpan+ , currentParentId+ , currentSpanId , currentTraceId+ , currentTraceIdInternal , defaultConfig , forceTransmissionAfter , forceTransmissionStartingAt , incrementErrorCount , initConfiguredInstana , initInstana+ , isConnected , maxBufferedSpans+ , postProcessHttpResponse , readHttpTracingHeaders , serviceName , setCorrelationId@@ -41,10 +48,10 @@ , startHttpExit , startRootEntry , withConfiguredInstana- , withCorrelatedHttpEntry , withEntry , withExit , withHttpEntry+ , withHttpEntry_ , withHttpExit , withInstana , withRootEntry@@ -55,7 +62,7 @@ import qualified Control.Concurrent as Concurrent import Control.Concurrent.STM (STM) import qualified Control.Concurrent.STM as STM-import Control.Monad (join)+import Control.Monad (join, when) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Aeson (Value, (.=)) import qualified Data.Aeson as Aeson@@ -93,13 +100,14 @@ import Instana.SDK.Internal.Util ((|>)) import qualified Instana.SDK.Internal.Worker as Worker import Instana.SDK.Span.EntrySpan (EntrySpan (..))-import qualified Instana.SDK.Span.EntrySpan as EntrySpan import Instana.SDK.Span.ExitSpan (ExitSpan (ExitSpan)) import qualified Instana.SDK.Span.ExitSpan as ExitSpan import Instana.SDK.Span.NonRootEntry (NonRootEntry (NonRootEntry)) import qualified Instana.SDK.Span.NonRootEntry as NonRootEntry import Instana.SDK.Span.RootEntry (RootEntry (RootEntry)) import qualified Instana.SDK.Span.RootEntry as RootEntry+import Instana.SDK.Span.SimpleSpan (SimpleSpan)+import qualified Instana.SDK.Span.SimpleSpan as SimpleSpan import Instana.SDK.Span.Span (Span (..), SpanKind (..)) import qualified Instana.SDK.Span.Span as Span import Instana.SDK.Span.SpanType (SpanType (RegisteredSpan))@@ -222,8 +230,7 @@ -- |Wraps an IO action in 'startRootEntry' and 'completeEntry'. For incoming--- HTTP requests, prefer 'withCorrelatedHttpEntry' or 'withHttpEntry'--- instead.+-- HTTP requests, prefer 'withHttpEntry' instead. withRootEntry :: MonadIO m => InstanaContext@@ -238,8 +245,7 @@ -- |Wraps an IO action in 'startEntry' and 'completeEntry'. For incoming HTTP--- requests, prefer 'withCorrelatedHttpEntry' or 'withHttpEntry'--- instead.+-- requests, prefer 'withHttpEntry' instead. withEntry :: MonadIO m => InstanaContext@@ -259,13 +265,14 @@ -- Instana tracing headers -- (https://docs.instana.io/core_concepts/tracing/#http-tracing-headers) -- and wraps the given IO action either in 'startRootEntry' or 'startEntry' and--- 'completeEntry', depending on the presence or absence of these headers.+-- 'completeEntry', depending on the presence or absence of these headers. It+-- will also capture the response HTTP status (and set the span's error count+-- if it is 5xx). Finally, it will add (or append to) the HTTP response header+-- (Server-Timing) that is used for website monitoring back end correlation.+-- (The latter part is the difference to 'withHttpEntry_', plus the slightly+-- different type signature.) ----- It is recommended to use 'withCorrelatedHttpEntry' instead of this--- function to also automatically add the HTTP response header for website--- monitoring back end correlation. Alternatively you can also call--- 'addWebsiteMonitoringBackEndCorrelation' with the WAI Response value before--- handing it off to WAI's 'respond' function.+-- This function should be preferred over 'withHttpEntry_'. -- -- You do not need to handle incoming HTTP requests at all when using the -- Instana WAI middleware plug-in.@@ -273,9 +280,36 @@ MonadIO m => InstanaContext -> Wai.Request+ -> m Wai.Response+ -> m Wai.Response+withHttpEntry+ context+ request+ io = do+ response <- withHttpEntry_ context request $ do+ io >>= postProcessHttpResponse context+ return response+++-- |A variant of 'withHttpEntry' with a more general type signature, but less+-- features. It will automatically continue the trace from incoming headers just+-- like withHttpEntry does, but it will not capture the status code of the HTTP+-- response or add the response header for website monitoring back end+-- correlation (Server-Timing).+--+-- It is recommended to use 'withHttpEntry' instead of this function, if+-- possible. Alternatively, you can also call 'postProcessHttpResponse' inside+-- the 'withHttpEntry_' block to cover the two missing features mentioned above.+--+-- Note that you do not need to handle incoming HTTP requests at all when using+-- the Instana WAI middleware plug-in.+withHttpEntry_ ::+ MonadIO m =>+ InstanaContext+ -> Wai.Request -> m a -> m a-withHttpEntry context request io = do+withHttpEntry_ context request io = do let spanType = (RegisteredSpan SpanType.HaskellWaiServer) tracingHeaders = readHttpTracingHeaders request@@ -308,34 +342,6 @@ io --- |A convenience function that examines the given incoming HTTP request for--- Instana tracing headers--- (https://docs.instana.io/core_concepts/tracing/#http-tracing-headers)--- and wraps the given IO action either in 'startRootEntry' or 'startEntry' and--- 'completeEntry', depending on the presence or absence of these headers. It--- will also add (or append to) the HTTP response header (Server-Timing) that is--- used for website monitoring back end correlation. (The latter part is the--- difference to 'withHttpEntry', plus the slightly different type signature.)------ This function should be preferred over 'withHttpEntry'.------ You do not need to handle incoming HTTP requests at all when using the--- Instana WAI middleware plug-in.-withCorrelatedHttpEntry ::- MonadIO m =>- InstanaContext- -> Wai.Request- -> m Wai.Response- -> m Wai.Response-withCorrelatedHttpEntry- context- request- io = do- response <- withHttpEntry context request $ do- io >>= addWebsiteMonitoringBackEndCorrelation context- return response-- -- |Takes an IO action and appends another side effecto to it that will add HTTP -- data from the given request to the current span. addDataFromRequest :: MonadIO m => InstanaContext -> Wai.Request -> m a -> m a@@ -542,31 +548,114 @@ ) +-- |Processes the response of an HTTP entry. This function needs be called while+-- the HTTP entry span is still active. It can be used inside a 'withHttpEntry_'+-- block or between 'startHttpEntry' and 'completeEntry'.+--+-- This function accomplishes two things:+-- * It captures the HTTP status code from the response and adds it as an+-- annotation to the currently active span.+-- * It adds an additional HTTP response header (Server-Timing) to the given HTTP+-- response that enables website monitoring back end correlation. In case the+-- response already has a Server-Timing header, a value is appended to the+-- existing Server-Timing list.+--+-- Client code should rarely have the need to call this directly. Instead,+-- capture incoming HTTP requests with 'withHttpEntry', which does+-- both of these things automatically.+postProcessHttpResponse ::+ MonadIO m =>+ InstanaContext+ -> Wai.Response+ -> m Wai.Response+postProcessHttpResponse context response = do+ liftIO $ do+ response' <- captureHttpStatusUnlifted context response+ addWebsiteMonitoringBackEndCorrelationUnlifted context response'+++-- |Captures the status code of the HTTP response and adds it to the currently+-- active span. If the status code is >= 500, the status message is also+-- captured. This function needs be called while the HTTP entry span is still+-- active. It can be used inside a 'withHttpEntry_' block or between+-- 'startHttpEntry' and 'completeEntry'.+--+-- Client code should rarely have the need to call this directly. Instead,+-- capture incoming HTTP requests with 'withHttpEntry', which captures the+-- status code automatically and also adds the Server-Timing header for back end+-- web site monitoring correlation. When not using 'withHttpEntry', the function+-- 'postProcessHttpResponse' should be preferred over this function, because it+-- does both (capture the status code and add the Server-Timing header).+captureHttpStatus ::+ MonadIO m =>+ InstanaContext+ -> Wai.Response+ -> m Wai.Response+captureHttpStatus context response = do+ liftIO $ captureHttpStatusUnlifted context response+++-- |Captures the status code of the HTTP response and adds it to the currently+-- active span. If the status code is >= 500, the status message is also+-- captured.+captureHttpStatusUnlifted ::+ InstanaContext+ -> Wai.Response+ -> IO Wai.Response+captureHttpStatusUnlifted context response = do+ let+ (HTTPTypes.Status statusCode statusMessage) =+ Wai.responseStatus response+ addRegisteredDataAt context "http.status" statusCode+ when+ (statusCode >= 500 )+ (addRegisteredDataAt context "http.message" $+ BSC8.unpack statusMessage+ )+ return response++ -- |Adds an additional HTTP response header (Server-Timing) to the given HTTP -- response that enables website monitoring back end correlation. In case the -- response already has a Server-Timing header, a value is appended to the--- existing Server-Timing list.+-- existing Server-Timing list. This function needs be called while the HTTP+-- entry span is still active. It can be used inside a 'withHttpEntry_' block or+-- between 'startHttpEntry' and 'completeEntry'. -- -- Client code should rarely have the need to call this directly. Instead,--- capture incoming HTTP requests with 'withCorrelatedHttpEntry', which adds the--- response header automatically.+-- capture incoming HTTP requests with 'withHttpEntry', which adds the+-- response header automatically and also captures the HTTP status code of the+-- response. When not using 'withHttpEntry', the function+-- 'postProcessHttpResponse' should be preferred over this function, because+-- it does both (capture the status code and add the Server-Timing header). addWebsiteMonitoringBackEndCorrelation :: MonadIO m => InstanaContext -> Wai.Response -> m Wai.Response addWebsiteMonitoringBackEndCorrelation context response = do- liftIO $ do- mt <- currentTraceId context- case mt of- Nothing -> return response- Just t ->- return $- Wai.mapResponseHeaders- (ServerTiming.addTraceIdToServerTiming t)- response+ liftIO $ addWebsiteMonitoringBackEndCorrelationUnlifted context response +-- |Adds an additional HTTP response header (Server-Timing) to the given HTTP+-- response that enables website monitoring back end correlation. In case the+-- response already has a Server-Timing header, a value is appended to the+-- existing Server-Timing list.+addWebsiteMonitoringBackEndCorrelationUnlifted ::+ InstanaContext+ -> Wai.Response+ -> IO Wai.Response+addWebsiteMonitoringBackEndCorrelationUnlifted context response = do+ traceIdMaybe <- currentTraceIdInternal context+ case traceIdMaybe of+ Nothing -> return response+ Just traceId ->+ return $+ Wai.mapResponseHeaders+ (ServerTiming.addTraceIdToServerTiming traceId)+ response++ -- |Creates a preliminary/incomplete exit span, which should later be completed -- with 'completeExit'. startExit ::@@ -633,11 +722,10 @@ -> HTTP.Request -> m HTTP.Request startHttpExit context request = do- request' <- addHttpTracingHeaders context request let- originalCheckResponse = HTTP.checkResponse request'- request'' =- request'+ originalCheckResponse = HTTP.checkResponse request+ request' =+ request { HTTP.checkResponse = (\req res -> do let status =@@ -661,6 +749,7 @@ url = protocol ++ host ++ port ++ path startExit context (RegisteredSpan SpanType.HaskellHttpClient)+ request'' <- addHttpTracingHeaders context request' addRegisteredData context (Aeson.object [ "http" .=@@ -914,27 +1003,42 @@ addHttpTracingHeaders context request = liftIO $ do suppressed <- isSuppressed context- entrySpan <- peekSpan context+ traceId <- currentTraceIdInternal context+ spanId <- currentSpanIdInternal context let originalHeaders = HTTP.requestHeaders request updatedRequest =- case (entrySpan, suppressed) of- (_, True) ->+ case (traceId, spanId, suppressed) of+ (_, _, True) -> request { HTTP.requestHeaders = ((TracingHeaders.levelHeaderName, "0") : originalHeaders) }- (Just (Entry currentEntrySpan), _) ->+ (Just tId, Just sId, False) -> request { HTTP.requestHeaders = (originalHeaders ++- [ (TracingHeaders.traceIdHeaderName, Id.toByteString $- EntrySpan.traceId currentEntrySpan)- , (TracingHeaders.spanIdHeaderName, Id.toByteString $- EntrySpan.spanId currentEntrySpan)+ [ (TracingHeaders.traceIdHeaderName, Id.toByteString tId)+ , (TracingHeaders.spanIdHeaderName, Id.toByteString sId) ] ) }+ (Just tId, Nothing, False) ->+ request {+ HTTP.requestHeaders =+ (originalHeaders +++ [ (TracingHeaders.traceIdHeaderName, Id.toByteString tId)+ ]+ )+ }+ (Nothing, Just sId, False) ->+ request {+ HTTP.requestHeaders =+ (originalHeaders +++ [ (TracingHeaders.spanIdHeaderName, Id.toByteString sId)+ ]+ )+ } _ -> request return updatedRequest@@ -1013,11 +1117,59 @@ return $ join spanMaybe --- |Retrieves the trace ID the current thread.-currentTraceId :: InstanaContext -> IO (Maybe Id)+-- |Checks whether the SDK has a connection to an Instana agent.+isConnected :: InstanaContext -> IO Bool+isConnected =+ InternalContext.isAgentConnectionEstablished+++-- |Provides the currently active span in a simple format fit for external use.+currentSpan :: InstanaContext -> IO (Maybe SimpleSpan)+currentSpan context = do+ span_ <- peekSpan context+ return $ SimpleSpan.convert <$> span_+++-- |Retrieves the trace ID of the currently active trace in the current thread.+currentTraceId :: InstanaContext -> IO (Maybe String) currentTraceId context = do+ traceIdMaybe <- currentTraceIdInternal context+ return $ Id.toString <$> traceIdMaybe+++-- |Retrieves the trace ID of the currently active trace in the current thread.+currentTraceIdInternal :: InstanaContext -> IO (Maybe Id)+currentTraceIdInternal context = do traceIdMaybe <- readFromSpanStack context SpanStack.readTraceId return $ join traceIdMaybe+++-- |Retrieves the span ID of the currently active span in the current thread.+currentSpanId :: InstanaContext -> IO (Maybe String)+currentSpanId context = do+ spanIdMaybe <- currentSpanIdInternal context+ return $ Id.toString <$> spanIdMaybe+++-- |Retrieves the span ID of the currently active span in the current thread.+currentSpanIdInternal :: InstanaContext -> IO (Maybe Id)+currentSpanIdInternal context = do+ span_ <- peekSpan context+ return $ Span.spanId <$> span_+++-- |Retrieves the parent ID of the currently active span in the current thread.+currentParentId :: InstanaContext -> IO (Maybe String)+currentParentId context = do+ parentIdMaybe <- currentParentIdInternal context+ return $ Id.toString <$> parentIdMaybe+++-- |Retrieves the parent ID of the currently active span in the current thread.+currentParentIdInternal :: InstanaContext -> IO (Maybe Id)+currentParentIdInternal context = do+ span_ <- peekSpan context+ return $ join $ Span.parentId <$> span_ -- |Checks if tracing is suppressed for the current thread.
+ src/Instana/SDK/Span/SimpleSpan.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-|+Module : Instana.SDK.Span.SimpleSpan+Description : A representation for spans for external use.+-}+module Instana.SDK.Span.SimpleSpan+ ( SimpleSpan(..)+ , convert+ ) where+++import qualified Data.Aeson as Aeson+import qualified Data.Text as T+import GHC.Generics++import qualified Instana.SDK.Internal.Id as Id+import Instana.SDK.Span.Span (SpanKind (..))+import Instana.SDK.Span.Span (Span)+import qualified Instana.SDK.Span.Span as Span+++-- |A representation of the span fit for external use by clients of the SDK.+data SimpleSpan = SimpleSpan+ { traceId :: String+ , spanId :: String+ , parentId :: Maybe String+ , spanName :: String+ , timestamp :: Int+ , kind :: SpanKind+ , errorCount :: Int+ , spanData :: Aeson.Value+ } deriving (Eq, Generic, Show)+++-- |Converts the internal span datastructure into the simplified format.+convert :: Span -> SimpleSpan+convert span_ =+ SimpleSpan+ { traceId = Id.toString $ Span.traceId span_+ , spanId = Id.toString $ Span.spanId span_+ , parentId = Id.toString <$> Span.parentId span_+ , spanName = T.unpack $ Span.spanName span_+ , timestamp = Span.timestamp span_+ , kind = Span.spanKind span_+ , errorCount = Span.errorCount span_+ , spanData = Span.spanData span_+ }+
src/Instana/Wai/Middleware/Entry/Internal.hs view
@@ -10,14 +10,10 @@ ) where -import Instana.SDK.Internal.Id (Id)-import qualified Instana.SDK.Internal.ServerTiming as ServerTiming-import Network.Wai (Middleware, Response)-import qualified Network.Wai as Wai+import Network.Wai (Middleware) -import Instana.SDK.SDK (InstanaContext,- currentTraceId,- withHttpEntry)+import Instana.SDK.SDK (InstanaContext, postProcessHttpResponse,+ withHttpEntry_) {-| Run the tracing middleware given an initialized Instana SDK context. The@@ -27,15 +23,8 @@ -} traceHttpEntries :: InstanaContext -> Middleware traceHttpEntries instana app request respond = do- withHttpEntry instana request $ do- traceIdMaybe <- currentTraceId instana- case traceIdMaybe of- Just traceId ->- app request $ respond . addHeader traceId- Nothing ->- app request respond-+ withHttpEntry_ instana request $ do+ app request $ \response -> do+ response' <- postProcessHttpResponse instana response+ respond $ response' -addHeader :: Id -> Response -> Response-addHeader traceId =- Wai.mapResponseHeaders $ ServerTiming.addTraceIdToServerTiming traceId
test/agent-stub/Instana/SDK/AgentStub/Util.hs view
@@ -23,5 +23,5 @@ -- Simulate connection loss after from 1500ms after server startup until -- 3500ms seconds after startup. Config.simulateConnectionLoss config &&- (now > startupTime + 1500 && now < startupTime + 3500)+ (now > startupTime + 3000 && now < startupTime + 6000)
+ test/apps/downstream-target/Main.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where+++import Control.Monad.IO.Class (liftIO)+import qualified Data.Aeson as Aeson+import qualified Data.Binary.Builder as Builder+import qualified Data.ByteString.Char8 as BSC8+import qualified Data.ByteString.Lazy.Char8 as LBSC8+import qualified Data.CaseInsensitive as CaseInsensitive+import qualified Data.Map as Map+import qualified Network.HTTP.Client as HTTP+import qualified Network.HTTP.Types as HTTPTypes+import qualified Network.Wai as Wai+import qualified Network.Wai.Handler.Warp as Warp+import System.Environment (lookupEnv)+import qualified System.Exit as Exit+import System.IO (Handle, stdout)+import System.Log.Formatter+import System.Log.Handler (setFormatter)+import System.Log.Handler.Simple (GenericHandler, fileHandler,+ streamHandler)+import System.Log.Logger (Priority (..), rootLoggerName,+ setHandlers, setLevel,+ updateGlobalLogger)+import System.Log.Logger (infoM)+import qualified System.Posix.Process as Posix+import System.Posix.Types (CPid)+++appLogger :: String+appLogger = "DownstreamTarget"+++application :: CPid -> Wai.Application+application pid request respond = do+ let+ route = Wai.pathInfo request+ method = Wai.requestMethod request+ case (method, route) of+ (_, []) ->+ root respond+ (_, ["ping"]) ->+ ping respond pid+ (_, ["echo"]) ->+ echoHeaders request respond+ ("POST", ["shutdown"]) ->+ shutDown respond+ _ ->+ respond404 respond+++root ::+ (Wai.Response -> IO Wai.ResponseReceived)+ -> IO Wai.ResponseReceived+root respond =+ respondWithPlainText+ respond+ "Downstream Target"+++ping ::+ (Wai.Response -> IO Wai.ResponseReceived)+ -> CPid+ -> IO Wai.ResponseReceived+ping respond pid = do+ respond $+ Wai.responseLBS HTTPTypes.status200 [] $ LBSC8.pack $ show pid+++echoHeaders ::+ Wai.Request+ -> (Wai.Response -> IO Wai.ResponseReceived)+ -> IO Wai.ResponseReceived+echoHeaders request respond = do+ let+ headers :: [(HTTPTypes.HeaderName, BSC8.ByteString)]+ headers = Wai.requestHeaders request+ mapped :: [(String, String)]+ mapped =+ map (+ \(headerName, value) ->+ (+ BSC8.unpack $ CaseInsensitive.original headerName,+ BSC8.unpack value+ )+ ) headers+ encodedHeaders = Aeson.encode $ Map.fromList mapped+ respond $+ Wai.responseLBS+ HTTPTypes.status200+ [("Content-Type", "application/json; charset=UTF-8")]+ encodedHeaders+++shutDown ::+ (Wai.Response -> IO Wai.ResponseReceived)+ -> IO Wai.ResponseReceived+shutDown respond = do+ liftIO $ infoM appLogger $ "Downstream target app shutdown requested"+ _ <-liftIO $ Posix.exitImmediately Exit.ExitSuccess+ respond $+ Wai.responseBuilder HTTPTypes.status204 [] Builder.empty+++respondWithPlainText ::+ (Wai.Response -> IO Wai.ResponseReceived)+ -> String+ -> IO Wai.ResponseReceived+respondWithPlainText respond content =+ respond $+ Wai.responseLBS+ HTTPTypes.status200+ [("Content-Type", "text/plain")]+ (LBSC8.pack content)+++respond404 ::+ (Wai.Response -> IO Wai.ResponseReceived)+ -> IO Wai.ResponseReceived+respond404 respond =+ respond $+ Wai.responseLBS HTTPTypes.status404 [] "not found"+++main :: IO ()+main = do+ initLogging+ runApp+++runApp :: IO ()+runApp = do+ pid <- Posix.getProcessID+ let+ host = "127.0.0.1"+ port = (1208 :: Int)+ warpSettings =+ ((Warp.setPort port) . (Warp.setHost "127.0.0.1")) Warp.defaultSettings+ infoM appLogger $+ "Starting downstream target app at " ++ host ++ ":" ++ show port +++ " (PID: " ++ show pid ++ ")."+ Warp.runSettings warpSettings $ application pid+++initLogging :: IO ()+initLogging = do+ logLevelEnvVar <- lookupEnv "APP_LOG_LEVEL"+ let+ logLevel =+ case logLevelEnvVar of+ Just "DEBUG" -> DEBUG+ _ -> INFO+ updateGlobalLogger appLogger $ setLevel logLevel+ appFileHandler <- fileHandler "downstream-target.log" logLevel+ appStreamHandler <- streamHandler stdout logLevel+ let+ formattedAppFileHandler = withFormatter appFileHandler+ formattedAppStreamHandler = withFormatter appStreamHandler+ updateGlobalLogger appLogger $+ setHandlers [ formattedAppFileHandler ]+ updateGlobalLogger rootLoggerName $+ setHandlers [ formattedAppStreamHandler ]+++withFormatter :: GenericHandler Handle -> GenericHandler Handle+withFormatter handler = setFormatter handler formatter+ where+ timeFormat = "%F %H:%M:%S.%4q %z"+ formatter = tfLogFormatter timeFormat "{$time $loggername $pid $prio} $msg"+++initHttpManager :: IO HTTP.Manager+initHttpManager =+ HTTP.newManager $+ HTTP.defaultManagerSettings+ { HTTP.managerConnCount = 5+ , HTTP.managerResponseTimeout = HTTP.responseTimeoutMicro $ 5000 * 1000+ }+
test/apps/wai-with-middleware/Main.hs view
@@ -75,21 +75,22 @@ -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived apiUnderTest instana httpManager _ respond = do- requestOut <-+ downstreamRequest <- HTTP.parseUrlThrow $- "http://127.0.0.1:1302/?some=query¶meters=2&pass=secret"- _ <- InstanaSDK.withHttpExit+ "http://127.0.0.1:1208/echo?some=query¶meters=2&pass=secret"+ downstreamResponse <- InstanaSDK.withHttpExit instana- requestOut+ downstreamRequest (\req -> do- _ <- HTTP.httpLbs req httpManager+ dowstreamRequest' <- HTTP.httpLbs req httpManager threadDelay $ 1000 -- make sure there is a duration > 0+ return dowstreamRequest' ) respond $- Wai.responseBuilder+ Wai.responseLBS HTTPTypes.status200 [("Content-Type", "application/json; charset=UTF-8")]- "{\"response\": \"ok\"}"+ (HTTP.responseBody downstreamResponse) shutDown ::@@ -138,13 +139,13 @@ host = "127.0.0.1" port = (1207 :: Int) warpSettings =- ((Warp.setPort 1207) . (Warp.setHost "127.0.0.1")) Warp.defaultSettings+ ((Warp.setPort port) . (Warp.setHost "127.0.0.1")) Warp.defaultSettings infoM appLogger $ "Starting Wai/Warp app (/w middleware) at " ++ host ++ ":" ++ show port ++ " (PID: " ++ show pid ++ ")." let app = application instana httpManager pid- appWithMiddleware = InstanaWaiMiddleware.traceHttpEntries instana $ app+ appWithMiddleware = InstanaWaiMiddleware.traceHttpEntries instana app Warp.runSettings warpSettings $ appWithMiddleware
test/apps/wai/Main.hs view
@@ -328,22 +328,23 @@ -> IO Wai.ResponseReceived httpBracketApi instana httpManager requestIn respond = do response <- do- InstanaSDK.withCorrelatedHttpEntry instana requestIn $ do- requestOut <-+ InstanaSDK.withHttpEntry instana requestIn $ do+ downstreamRequest <- HTTP.parseUrlThrow $- "http://127.0.0.1:1302/?some=query¶meters=2&pass=secret"- _ <- InstanaSDK.withHttpExit+ "http://127.0.0.1:1208/echo?some=query¶meters=2&pass=secret"+ downstreamResponse <- InstanaSDK.withHttpExit instana- requestOut+ downstreamRequest (\req -> do- _ <- HTTP.httpLbs req httpManager+ downstreamRespone' <- HTTP.httpLbs req httpManager threadDelay $ 1000 -- make sure there is a duration > 0+ return downstreamRespone' ) return $- Wai.responseBuilder+ Wai.responseLBS HTTPTypes.status200 [("Content-Type", "application/json; charset=UTF-8")]- "{\"response\": \"ok\"}"+ (HTTP.responseBody downstreamResponse) respond response @@ -355,20 +356,21 @@ -> IO Wai.ResponseReceived httpLowLevelApi instana httpManager requestIn respond = do InstanaSDK.startHttpEntry instana requestIn- requestOut <-+ downstreamRequest <- HTTP.parseUrlThrow $- "http://127.0.0.1:1302/?some=query¶meters=2&pass=secret"- requestOut' <- InstanaSDK.startHttpExit instana requestOut- _ <- HTTP.httpLbs requestOut' httpManager+ "http://127.0.0.1:1208/echo?some=query¶meters=2&pass=secret"+ downstreamRequest' <- InstanaSDK.startHttpExit instana downstreamRequest+ downstreamResponse <- HTTP.httpLbs downstreamRequest' httpManager threadDelay $ 1000 -- make sure there is a duration > 0 InstanaSDK.completeExit instana let response =- Wai.responseBuilder+ Wai.responseLBS HTTPTypes.status200 [("Content-Type", "application/json; charset=UTF-8")]- "{\"response\": \"ok\"}"- response' <- InstanaSDK.addWebsiteMonitoringBackEndCorrelation instana response+ (HTTP.responseBody downstreamResponse)+ response' <-+ InstanaSDK.postProcessHttpResponse instana response result <- respond response' InstanaSDK.completeEntry instana return result@@ -441,7 +443,7 @@ host = "127.0.0.1" port = (1207 :: Int) warpSettings =- ((Warp.setPort 1207) . (Warp.setHost "127.0.0.1")) Warp.defaultSettings+ ((Warp.setPort port) . (Warp.setHost "127.0.0.1")) Warp.defaultSettings infoM appLogger $ "Starting Wai/Warp app at " ++ host ++ ":" ++ show port ++ " (PID: " ++ show pid ++ ")."
test/integration/Instana/SDK/IntegrationTest/BracketApi.hs view
@@ -19,6 +19,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper @@ -76,7 +77,8 @@ createRootEntry :: IO String createRootEntry = do- response <- HttpHelper.doAppRequest "bracket/api/root" "POST" []+ response <-+ HttpHelper.doAppRequest Suite.testServer "bracket/api/root" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response @@ -135,7 +137,8 @@ createNonRootEntry :: IO String createNonRootEntry = do- response <- HttpHelper.doAppRequest "bracket/api/non-root" "POST" []+ response <-+ HttpHelper.doAppRequest Suite.testServer "bracket/api/non-root" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response @@ -236,7 +239,8 @@ createSpansWithTags :: IO String createSpansWithTags = do- response <- HttpHelper.doAppRequest "bracket/api/with-tags" "POST" []+ response <-+ HttpHelper.doAppRequest Suite.testServer "bracket/api/with-tags" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response @@ -314,6 +318,11 @@ createSpansWithServiceName :: IO String createSpansWithServiceName = do- response <- HttpHelper.doAppRequest "bracket/api/with-service-name" "POST" []+ response <-+ HttpHelper.doAppRequest+ Suite.testServer+ "bracket/api/with-service-name"+ "POST"+ [] return $ LBSC8.unpack $ HTTP.responseBody response
test/integration/Instana/SDK/IntegrationTest/Connection.hs view
@@ -16,6 +16,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper @@ -36,26 +37,30 @@ shouldReestablishLostConnection _ = applyLabel "shouldReestablishLostConnection" $ do - -- 0 ms: send span 1+ -- In this test scenario, the agent is instructed to no accept any data in+ -- the timespan from after 3000 ms after start until 6000 ms after start.++ -- ~0 ms: send span 1 recordSpan "haskell.dummy.connectionloss.entry-1"- threadDelay $ 2000 * 1000+ threadDelay $ 3500 * 1000 - -- 1500 ms: agent stub will switch into "connection loss simulation" mode+ -- 3000 ms: agent stub will switch into "connection loss simulation" mode -- (that is, spans and entity data will be rejected) - -- 2000 ms: send span 2, will be rejected, unless an entity data request- -- happened since 1500 ms and already triggered the reestablishing of the+ -- 3500 ms: send span 2, will be rejected, unless an entity data request+ -- happened since 3000 ms and already triggered the reestablishing of the -- connection. Otherwise this span will trigger a reconnection (but get lost -- in the process).+ -- 4500 ms: span 2 will be send latest (force transmission every second) recordSpan "haskell.dummy.connectionloss.entry-2"- threadDelay $ 2000 * 1000 - -- 3000 ms: span 2 will be send latest (force transmission every second)+ threadDelay $ 4500 * 1000 - -- 3500 ms: agent stub will switch off "connection loss simulation" mode+ -- 6000 ms: agent stub will switch off "connection loss simulation" mode - -- 4000 ms: send span 3+ -- 8000 ms: send span 3 recordSpan "haskell.dummy.connectionloss.entry-3"+ -- wait for span 3 to arrive, check that 1 and 3 have been received spansResult <- TestHelper.waitForSdkSpansMatching@@ -91,6 +96,7 @@ recordSpan spanName = do _ <- HttpHelper.doAppRequest+ Suite.testServer ("single?spanName=" ++ spanName) "POST" []
test/integration/Instana/SDK/IntegrationTest/HttpHelper.hs view
@@ -16,16 +16,17 @@ ) where -import Control.Monad.Catch (Handler)-import qualified Control.Retry as Retry-import qualified Data.Aeson as Aeson-import Data.ByteString (ByteString)-import qualified Data.ByteString.Lazy as LBS-import qualified Network.HTTP.Client as HTTP-import Network.HTTP.Types.Header (Header, HeaderName)-import Network.HTTP.Types.Method (Method)+import Control.Monad.Catch (Handler)+import qualified Control.Retry as Retry+import qualified Data.Aeson as Aeson+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as LBS+import qualified Network.HTTP.Client as HTTP+import Network.HTTP.Types.Header (Header, HeaderName)+import Network.HTTP.Types.Method (Method) -import Instana.SDK.IntegrationTest.Util (putStrFlush)+import qualified Instana.SDK.IntegrationTest.Suite as Suite+import Instana.SDK.IntegrationTest.Util (putStrFlush) -- 100 milliseconds@@ -91,18 +92,9 @@ appHost = "127.0.0.1" -appPort :: Int-appPort = 1207---appBaseUrl :: String-appBaseUrl =- "http://" ++ appHost ++ ":" ++ (show appPort) ++ "/"---appUrl :: String -> String-appUrl path =- appBaseUrl ++ path+appUrl :: Int -> String -> String+appUrl port path =+ "http://" ++ appHost ++ ":" ++ (show port) ++ "/" ++ path defaultHeaders :: [(HeaderName, ByteString)]@@ -123,12 +115,13 @@ requestAppAndParse :: Aeson.FromJSON a =>- String+ Suite.AppUnderTest+ -> String -> ByteString -> [Header] -> IO (Either String a)-requestAppAndParse path method headers =- requestAndParse path method headers appUrl+requestAppAndParse appUnderTest path method headers =+ requestAndParse path method headers (appUrl $ Suite.port appUnderTest) doAgentStubRequest ::@@ -140,12 +133,13 @@ doAppRequest ::- String+ Suite.AppUnderTest+ -> String -> Method -> [Header] -> IO (HTTP.Response LBS.ByteString)-doAppRequest path method headers =- doRequest path method headers appUrl+doAppRequest appUnderTest path method headers =+ doRequest path method headers (appUrl $ Suite.port appUnderTest) requestAndParse ::
test/integration/Instana/SDK/IntegrationTest/HttpTracing.hs view
@@ -16,6 +16,7 @@ import qualified Data.Aeson as Aeson import qualified Data.ByteString.Char8 as BSC8 import qualified Data.ByteString.Lazy.Char8 as LBSC8+import Data.List (isInfixOf) import qualified Data.List as List import Data.Maybe (isNothing, listToMaybe) import Instana.SDK.AgentStub.TraceRequest (From (..), Span)@@ -23,6 +24,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper import qualified Network.HTTP.Client as HTTP import Network.HTTP.Types (Header)@@ -113,9 +115,9 @@ runTest :: String -> String -> [Header] -> (Span -> [Assertion]) -> IO Test runTest pid urlPath headers extraAsserts = do response <-- HttpHelper.doAppRequest urlPath "GET" headers+ HttpHelper.doAppRequest Suite.testServer urlPath "GET" headers let- result = LBSC8.unpack $ HTTP.responseBody response+ responseBody = LBSC8.unpack $ HTTP.responseBody response from = Just $ From pid "agent-stub-id" responseHeaders = HTTP.responseHeaders response serverTimingTuple :: Maybe (Network.HTTP.Types.Header.HeaderName, BSC8.ByteString)@@ -146,16 +148,20 @@ Just entrySpan = maybeEntrySpan Just exitSpan = maybeExitSpan assertAllIO $- (commonAsserts entrySpan exitSpan result from serverTimingValue) +++ (commonAsserts entrySpan exitSpan responseBody from serverTimingValue) ++ (extraAsserts entrySpan) runSuppressedTest :: String -> IO Test runSuppressedTest urlPath = do response <-- HttpHelper.doAppRequest urlPath "GET" [("X-INSTANA-L", "0")]+ HttpHelper.doAppRequest+ Suite.testServer+ urlPath+ "GET"+ [("X-INSTANA-L", "0")] let- result = LBSC8.unpack $ HTTP.responseBody response+ responseBody = LBSC8.unpack $ HTTP.responseBody response -- wait a second, then check that no spans have been recorded threadDelay $ 10 * 1000 spansResults <-@@ -168,7 +174,15 @@ then failIO "spans have been recorded although they should have not" else assertAllIO- [ assertEqual "result" "{\"response\": \"ok\"}" result+ [ assertBool+ "downstream X-INSTANA-L"+ (isInfixOf "\"X-INSTANA-L\":\"0\"" responseBody)+ , assertBool+ "no downstream X-INSTANA-T"+ (not $ isInfixOf "X-INSTANA-T" responseBody)+ , assertBool+ "no downstream X-INSTANA-S"+ (not $ isInfixOf "X-INSTANA-S" responseBody) ] @@ -202,9 +216,8 @@ -> Maybe From -> Maybe String -> [Assertion]-commonAsserts entrySpan exitSpan result from serverTimingValue =- [ assertEqual "result" "{\"response\": \"ok\"}" result- , assertEqual "trace ID is consistent"+commonAsserts entrySpan exitSpan responseBody from serverTimingValue =+ [ assertEqual "trace ID is consistent" (TraceRequest.t exitSpan) (TraceRequest.t entrySpan) , assertEqual "Server-Timing header with trace ID is present"@@ -213,6 +226,23 @@ , assertEqual "exit parent ID" (Just $ TraceRequest.s entrySpan) (TraceRequest.p exitSpan)+ , assertBool+ ("wrong downstream X-INSTANA-T: " ++ responseBody +++ ", expected " ++ TraceRequest.t entrySpan)+ (isInfixOf+ ("\"X-INSTANA-T\":\"" ++ (TraceRequest.t entrySpan) ++ "\"")+ responseBody+ )+ , assertBool+ ("wrong downstream X-INSTANA-S: " ++ responseBody +++ ", expected " ++ TraceRequest.s exitSpan)+ (isInfixOf+ ("\"X-INSTANA-S\":\"" ++ (TraceRequest.s exitSpan) ++ "\"")+ responseBody+ )+ , assertBool+ "no downstream X-INSTANA-L"+ (not $ isInfixOf "X-INSTANA-L" responseBody) , assertBool "entry timestamp" $ TraceRequest.ts entrySpan > 0 , assertBool "entry duration" $ TraceRequest.d entrySpan > 0 , assertEqual "entry kind" 1 (TraceRequest.k entrySpan)@@ -227,7 +257,7 @@ ( Aeson.object [ "http" .= (Aeson.object [ "method" .= ("GET" :: String)- , "url" .= ("http://127.0.0.1:1302/" :: String)+ , "url" .= ("http://127.0.0.1:1208/echo" :: String) , "params" .= ("some=query¶meters=2" :: String) , "status" .= (200 :: Int) ]@@ -258,6 +288,7 @@ , "host" .= ("127.0.0.1:1207" :: String) , "url" .= ("/http/bracket/api" :: String) , "params" .= ("some=query¶meters=1" :: String)+ , "status" .= (200 :: Int) ] ) ]@@ -275,6 +306,7 @@ , "host" .= ("127.0.0.1:1207" :: String) , "url" .= ("/http/low/level/api" :: String) , "params" .= ("some=query¶meters=2" :: String)+ , "status" .= (200 :: Int) ] ) ]
test/integration/Instana/SDK/IntegrationTest/LowLevelApi.hs view
@@ -18,6 +18,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper @@ -79,7 +80,9 @@ createRootEntry :: IO String createRootEntry = do- response <- HttpHelper.doAppRequest "low/level/api/root" "POST" []+ response <-+ HttpHelper.doAppRequest+ Suite.testServer "low/level/api/root" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response @@ -139,7 +142,8 @@ createNonRootEntry :: IO String createNonRootEntry = do- response <- HttpHelper.doAppRequest "low/level/api/non-root" "POST" []+ response <-+ HttpHelper.doAppRequest Suite.testServer "low/level/api/non-root" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response @@ -243,6 +247,7 @@ createSpansWithTags :: IO String createSpansWithTags = do- response <- HttpHelper.doAppRequest "low/level/api/with-tags" "POST" []+ response <-+ HttpHelper.doAppRequest Suite.testServer "low/level/api/with-tags" "POST" [] return $ LBSC8.unpack $ HTTP.responseBody response
test/integration/Instana/SDK/IntegrationTest/Metrics.hs view
@@ -51,7 +51,7 @@ (EntityDataRequest.arguments entityData) , assertLabelIs "sensorVersion"- "0.5.0.1"+ "0.6.0.0" (EntityDataRequest.sensorVersion entityData) , assertCounterSatisfies "startTime"
test/integration/Instana/SDK/IntegrationTest/Runner.hs view
@@ -15,7 +15,8 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (mergeCounts) import Instana.SDK.IntegrationTest.Logging (testLogger)-import Instana.SDK.IntegrationTest.Suite (ConditionalSuite (..),+import Instana.SDK.IntegrationTest.Suite (AppUnderTest,+ ConditionalSuite (..), Suite) import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper@@ -101,22 +102,37 @@ , ("LOG_LEVEL", Just logLevel) ] "stack exec instana-haskell-agent-stub"- appCommand =- buildCommand- [ ("APP_LOG_LEVEL", Just logLevel)- , ("INSTANA_SERVICE_NAME", Suite.customServiceName options)- , ("INSTANA_LOG_LEVEL", Just logLevel)- ]- "stack exec " ++ Suite.appUnderTest options+ appCommands =+ map+ (\appUnderTest ->+ buildCommand+ [ ("APP_LOG_LEVEL", Just logLevel)+ , ("INSTANA_SERVICE_NAME", Suite.customServiceName options)+ , ("INSTANA_LOG_LEVEL", Just logLevel)+ ]+ "stack exec " ++ (Suite.executable appUnderTest)+ )+ (Suite.appsUnderTest options)+ startAllApplicationsUnderTest =+ sequence $+ map+ (\cmd -> do+ infoM testLogger $ "Running: " ++ cmd+ -- We should rather use withCreateProcess instead of createProcess+ -- for the apps under test, too - like for the agentStubCommand, see+ -- below. But how to do that in a loop for an arbitrary+ -- number of processes?+ Process.createProcess $ Process.shell cmd+ )+ appCommands + infoM testLogger $ "Running: " ++ agentStubCommand Process.withCreateProcess (Process.shell agentStubCommand) (\_ _ _ _ -> do- infoM testLogger $ "Running: " ++ appCommand- Process.withCreateProcess- (Process.shell appCommand)- (\_ _ _ _ -> runTests suite)+ _ <- startAllApplicationsUnderTest+ runTests suite ) @@ -125,12 +141,17 @@ infoM testLogger "⏱ waiting for agent stub to come up" _ <- HttpHelper.retryRequestRecovering TestHelper.pingAgentStub infoM testLogger "✅ agent stub is up"- infoM testLogger "⏱ waiting for app to come up"- appPingResponse <- HttpHelper.retryRequestRecovering TestHelper.pingApp+ appsWithPids <-+ sequence $ map pingApp (Suite.appsUnderTest $ Suite.options suite)++ -- We currently assume that there is exactly one app under test that also+ -- connects to the agent. let- appPingBody = HTTP.responseBody appPingResponse- appPid = (read (LBSC8.unpack appPingBody) :: Int)- infoM testLogger $ "✅ app is up, PID is " ++ (show appPid)+ appPid =+ snd $+ head $+ filter (\(app, _) -> Suite.connectsToAgent app) appsWithPids+ results <- waitForAgentConnectionAndRun suite appPid -- The withProcess calls that starts the agent stub and the external app@@ -145,8 +166,23 @@ -- sure the process instances get shut down, we send an extra HTTP request to -- ask the processes to terminate themselves. _ <- TestHelper.shutDownAgentStub- _ <- TestHelper.shutDownApp+ _ <- TestHelper.shutDownApps (Suite.appsUnderTest $ Suite.options suite) return results+++pingApp :: AppUnderTest -> IO (AppUnderTest, Int)+pingApp appUnderTest = do+ infoM testLogger $+ "⏱ waiting for app " ++ Suite.executable appUnderTest ++ " to come up"+ appPingResponse <-+ HttpHelper.retryRequestRecovering $ TestHelper.pingApp appUnderTest+ let+ appPingBody = HTTP.responseBody appPingResponse+ appPid = (read (LBSC8.unpack appPingBody) :: Int)+ infoM testLogger $+ "✅ app " ++ Suite.executable appUnderTest +++ " is up, PID is " ++ (show appPid)+ return (appUnderTest, appPid) -- |Waits for the app under test to establish a connection to the agent, then
test/integration/Instana/SDK/IntegrationTest/ServiceName.hs view
@@ -16,6 +16,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper @@ -97,6 +98,7 @@ createSpansWithServiceName = do response <- HttpHelper.doAppRequest+ Suite.testServer "bracket/api/with-service-name-exit-only" "POST" []
test/integration/Instana/SDK/IntegrationTest/Suite.hs view
@@ -1,9 +1,13 @@ module Instana.SDK.IntegrationTest.Suite- ( ConditionalSuite(..)+ ( AppUnderTest(..)+ , ConditionalSuite(..) , Suite(..) , SuiteOptions(..) , defaultOptions+ , downstreamTarget , isExclusive+ , testServer+ , testServerWithMiddleware , withConnectionLoss , withCustomServiceName , withPidTranslation@@ -29,19 +33,55 @@ { usePidTranslation :: Bool , startupDelay :: Bool , simulateConnectionLoss :: Bool- , appUnderTest :: String , customServiceName :: Maybe String+ , appsUnderTest :: [AppUnderTest] } +-- |Describes options for running a test suite.+data AppUnderTest =+ AppUnderTest+ { executable :: String+ , port :: Int+ , connectsToAgent :: Bool+ }++ defaultOptions :: SuiteOptions defaultOptions = SuiteOptions { usePidTranslation = False , startupDelay = False , simulateConnectionLoss = False- , appUnderTest = "instana-haskell-test-wai-server"+ , appsUnderTest = [testServer] , customServiceName = Nothing+ }+++testServer :: AppUnderTest+testServer =+ AppUnderTest+ { executable = "instana-haskell-test-wai-server"+ , port = 1207+ , connectsToAgent = True+ }+++testServerWithMiddleware :: AppUnderTest+testServerWithMiddleware =+ AppUnderTest+ { executable = "instana-haskell-test-wai-with-middleware-server"+ , port = 1207+ , connectsToAgent = True+ }+++downstreamTarget :: AppUnderTest+downstreamTarget =+ AppUnderTest+ { executable = "downstream-target"+ , port = 1208+ , connectsToAgent = True }
test/integration/Instana/SDK/IntegrationTest/TestHelper.hs view
@@ -10,7 +10,7 @@ , resetDiscoveries , resetSpans , shutDownAgentStub- , shutDownApp+ , shutDownApps , waitForEntityDataWithPid , waitForExternalAgentConnection , waitForDiscoveryWithPid@@ -38,6 +38,7 @@ import qualified Instana.SDK.AgentStub.TraceRequest as TraceRequest import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.Logging (testLogger)+import Instana.SDK.IntegrationTest.Suite (AppUnderTest) import Instana.SDK.IntegrationTest.Util ((|>)) @@ -57,9 +58,9 @@ HttpHelper.doAgentStubRequest "stub/ping" "GET" -pingApp :: IO (HTTP.Response LBS.ByteString)-pingApp = do- HttpHelper.doAppRequest "ping" "GET" [("X-INSTANA-L", "0")]+pingApp :: AppUnderTest -> IO (HTTP.Response LBS.ByteString)+pingApp appUnderTest = do+ HttpHelper.doAppRequest appUnderTest "ping" "GET" [("X-INSTANA-L", "0")] shutDownAgentStub :: IO ()@@ -75,17 +76,25 @@ (\ (_ :: HTTP.HttpException) -> return ()) -shutDownApp :: IO ()-shutDownApp = do- catch- ( HttpHelper.doAppRequest "shutdown" "POST" [("X-INSTANA-L", "0")]- >> return ()- )- -- Ignore all exceptions for the shutdown request. Either the app has- -- already been shut down (so the request results in a network error) or, if- -- it is successfull, it results in an HTTP 500 because the app process- -- terminates before responding.- (\ (_ :: HTTP.HttpException) -> return ())+shutDownApps :: [AppUnderTest] -> IO ()+shutDownApps appsUnderTest = do+ sequence_ $ map shutDownOneApp appsUnderTest+ where+ shutDownOneApp :: AppUnderTest -> IO ()+ shutDownOneApp appUnderTest =+ catch+ ( HttpHelper.doAppRequest+ appUnderTest+ "shutdown"+ "POST"+ [("X-INSTANA-L", "0")]+ >> return ()+ )+ -- Ignore all exceptions for the shutdown request. Either the app has+ -- already been shut down (so the request results in a network error) or, if+ -- it is successfull, it results in an HTTP 500 because the app process+ -- terminates before responding.+ (\ (_ :: HTTP.HttpException) -> return ()) waitForExternalAgentConnection :: Bool -> Int -> IO (Either String (DiscoveryRequest, String))
test/integration/Instana/SDK/IntegrationTest/TestSuites.hs view
@@ -132,7 +132,12 @@ , HttpTracing.shouldCreateNonRootEntryWithLowLevelApi pid , HttpTracing.shouldSuppressWithLowLevelApi ])- , Suite.options = Suite.defaultOptions+ , Suite.options = Suite.defaultOptions {+ Suite.appsUnderTest =+ [ Suite.testServer+ , Suite.downstreamTarget+ ]+ } } @@ -147,10 +152,11 @@ , WaiMiddleware.shouldAddWebsiteMonitoringCorrelation pid , WaiMiddleware.shouldSuppress ])- , Suite.options =- Suite.defaultOptions {- Suite.appUnderTest =- "instana-haskell-test-wai-with-middleware-server"+ , Suite.options = Suite.defaultOptions {+ Suite.appsUnderTest =+ [ Suite.testServerWithMiddleware+ , Suite.downstreamTarget+ ] } }
test/integration/Instana/SDK/IntegrationTest/WaiMiddleware.hs view
@@ -12,6 +12,7 @@ import qualified Data.Aeson as Aeson import qualified Data.ByteString.Char8 as BSC8 import qualified Data.ByteString.Lazy.Char8 as LBSC8+import Data.List (isInfixOf) import qualified Data.List as List import Data.Maybe (isNothing, listToMaybe) import Instana.SDK.AgentStub.TraceRequest (From (..), Span)@@ -19,6 +20,7 @@ import qualified Instana.SDK.IntegrationTest.HttpHelper as HttpHelper import Instana.SDK.IntegrationTest.HUnitExtra (applyLabel, applyLabel, assertAllIO, failIO)+import qualified Instana.SDK.IntegrationTest.Suite as Suite import qualified Instana.SDK.IntegrationTest.TestHelper as TestHelper import qualified Network.HTTP.Client as HTTP import Network.HTTP.Types (Header)@@ -66,9 +68,9 @@ runTest :: String -> String -> [Header] -> (Span -> [Assertion]) -> IO Test runTest pid urlPath headers extraAsserts = do response <-- HttpHelper.doAppRequest urlPath "GET" headers+ HttpHelper.doAppRequest Suite.testServerWithMiddleware urlPath "GET" headers let- result = LBSC8.unpack $ HTTP.responseBody response+ responseBody = LBSC8.unpack $ HTTP.responseBody response from = Just $ From pid "agent-stub-id" responseHeaders = HTTP.responseHeaders response serverTimingTuple :: Maybe (Network.HTTP.Types.Header.HeaderName, BSC8.ByteString)@@ -99,16 +101,20 @@ Just entrySpan = maybeEntrySpan Just exitSpan = maybeExitSpan assertAllIO $- (commonAsserts entrySpan exitSpan result from serverTimingValue) +++ (commonAsserts entrySpan exitSpan responseBody from serverTimingValue) ++ (extraAsserts entrySpan) runSuppressedTest :: String -> IO Test runSuppressedTest urlPath = do response <-- HttpHelper.doAppRequest urlPath "GET" [("X-INSTANA-L", "0")]+ HttpHelper.doAppRequest+ Suite.testServerWithMiddleware+ urlPath+ "GET"+ [("X-INSTANA-L", "0")] let- result = LBSC8.unpack $ HTTP.responseBody response+ responseBody = LBSC8.unpack $ HTTP.responseBody response -- wait a second, then check that no spans have been recorded threadDelay $ 10 * 1000 spansResults <-@@ -121,7 +127,15 @@ then failIO "spans have been recorded although they should have not" else assertAllIO- [ assertEqual "result" "{\"response\": \"ok\"}" result+ [ assertBool+ "downstream X-INSTANA-L"+ (isInfixOf "\"X-INSTANA-L\":\"0\"" responseBody)+ , assertBool+ "no downstream X-INSTANA-T"+ (not $ isInfixOf "X-INSTANA-T" responseBody)+ , assertBool+ "no downstream X-INSTANA-S"+ (not $ isInfixOf "X-INSTANA-S" responseBody) ] @@ -155,17 +169,30 @@ -> Maybe From -> Maybe String -> [Assertion]-commonAsserts entrySpan exitSpan result from serverTimingValue =- [ assertEqual "result" "{\"response\": \"ok\"}" result- , assertEqual "trace ID is consistent"- (TraceRequest.t exitSpan)- (TraceRequest.t entrySpan)- , assertEqual "Server-Timing header with trace ID is present"+commonAsserts entrySpan exitSpan responseBody from serverTimingValue =+ [ assertEqual "Server-Timing header with trace ID is present" (Just $ "intid;desc=" ++ (TraceRequest.t entrySpan)) (serverTimingValue) , assertEqual "exit parent ID" (Just $ TraceRequest.s entrySpan) (TraceRequest.p exitSpan)+ , assertBool+ ("wrong downstream X-INSTANA-T: " ++ responseBody +++ ", expected " ++ TraceRequest.t entrySpan)+ (isInfixOf+ ("\"X-INSTANA-T\":\"" ++ (TraceRequest.t entrySpan) ++ "\"")+ responseBody+ )+ , assertBool+ ("wrong downstream X-INSTANA-S: " ++ responseBody +++ ", expected " ++ TraceRequest.s exitSpan)+ (isInfixOf+ ("\"X-INSTANA-S\":\"" ++ (TraceRequest.s exitSpan) ++ "\"")+ responseBody+ )+ , assertBool+ "no downstream X-INSTANA-L"+ (not $ isInfixOf "X-INSTANA-L" responseBody) , assertBool "entry timestamp" $ TraceRequest.ts entrySpan > 0 , assertBool "entry duration" $ TraceRequest.d entrySpan > 0 , assertEqual "entry kind" 1 (TraceRequest.k entrySpan)@@ -180,7 +207,7 @@ ( Aeson.object [ "http" .= (Aeson.object [ "method" .= ("GET" :: String)- , "url" .= ("http://127.0.0.1:1302/" :: String)+ , "url" .= ("http://127.0.0.1:1208/echo" :: String) , "params" .= ("some=query¶meters=2" :: String) , "status" .= (200 :: Int) ]@@ -200,6 +227,7 @@ , "host" .= ("127.0.0.1:1207" :: String) , "url" .= ("/api" :: String) , "params" .= ("some=query¶meters=1" :: String)+ , "status" .= (200 :: Int) ] ) ]
test/unit/Instana/SDK/Internal/IdTest.hs view
@@ -1,9 +1,10 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Instana.SDK.Internal.IdTest (allTests) where import qualified Data.Aeson as Aeson-import qualified Data.ByteString.Lazy.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as LBSC8 import Test.HUnit import Instana.SDK.Internal.Id (Id)@@ -13,7 +14,13 @@ allTests :: Test allTests = TestList- [ TestLabel "shouldEncodeIntComponents" shouldEncodeIntComponents+ [ TestLabel+ "shouldConvertIntComponentsToString"+ shouldConvertIntComponentsToString+ , TestLabel "shouldEncodeIntComponents" shouldEncodeIntComponents+ , TestLabel+ "shouldConvertIntComponentsToString2"+ shouldConvertIntComponentsToString2 , TestLabel "shouldEncodeIntComponents2" shouldEncodeIntComponents2 , TestLabel "shouldEncodeStringId" shouldEncodeStringId , TestLabel "shouldDecodeStringId " shouldDecodeStringId@@ -21,24 +28,43 @@ ] +shouldConvertIntComponentsToString :: Test+shouldConvertIntComponentsToString =+ let+ asString = Id.toString $ Id.createFromIntsForTest [12345, 67890]+ in+ TestCase $+ assertEqual "as string" "0000303900010932" asString++ shouldEncodeIntComponents :: Test shouldEncodeIntComponents = let idFromInts = Id.createFromIntsForTest [12345, 67890]- encoded = BS.unpack . Aeson.encode $ idFromInts+ encoded = LBSC8.unpack . Aeson.encode $ idFromInts in TestCase $- assertEqual "encoded" "\"0000303900010932\"" encoded+ assertEqual "as string" "\"0000303900010932\"" encoded -shouldEncodeIntComponents2 :: Test-shouldEncodeIntComponents2 =+shouldConvertIntComponentsToString2 :: Test+shouldConvertIntComponentsToString2 = let -- minimum for Int type as per -- http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Int.html -- is from -2^29 to 29-1 -> -536870912 to 536870911+ asString =+ Id.toString $ Id.createFromIntsForTest [-536870912, 536870911]+ in+ TestCase $+ assertEqual "as string" "200000001fffffff" asString+++shouldEncodeIntComponents2 :: Test+shouldEncodeIntComponents2 =+ let idFromInts = Id.createFromIntsForTest [-536870912, 536870911]- encoded = BS.unpack . Aeson.encode $ idFromInts+ encoded = LBSC8.unpack . Aeson.encode $ idFromInts in TestCase $ assertEqual "encoded" "\"200000001fffffff\"" encoded@@ -47,8 +73,8 @@ shouldEncodeStringId :: Test shouldEncodeStringId = let- idFromString = Id.fromString "some string"- encoded = BS.unpack . Aeson.encode $ idFromString+ idFromString = ("some string" :: Id)+ encoded = LBSC8.unpack . Aeson.encode $ idFromString in TestCase $ assertEqual "encoded" "\"some string\"" encoded@@ -57,8 +83,8 @@ shouldDecodeStringId :: Test shouldDecodeStringId = let- expected = Id.fromString "some string"- maybeId = Aeson.decode (BS.pack "\"some string\"") :: Maybe Id+ expected = ("some string" :: Id)+ maybeId = Aeson.decode (LBSC8.pack "\"some string\"") :: Maybe Id Just decoded = maybeId in TestCase $ assertEqual "decoded" expected decoded@@ -70,7 +96,7 @@ do generated <- Id.generate let- encoded = BS.unpack . Aeson.encode $ generated+ encoded = LBSC8.unpack . Aeson.encode $ generated -- every encoded ID must be 16 chars wide + 2 chars for leading and -- trailing " assertEqual "generated" 18 (length encoded)
test/unit/Instana/SDK/Internal/SpanTest.hs view
@@ -3,16 +3,17 @@ module Instana.SDK.Internal.SpanTest (allTests) where -import Data.Aeson (Value, (.=))-import qualified Data.Aeson as Aeson+import Data.Aeson (Value, (.=))+import qualified Data.Aeson as Aeson import Test.HUnit -import qualified Instana.SDK.Internal.Id as Id-import Instana.SDK.Span.EntrySpan (EntrySpan (RootEntrySpan))-import Instana.SDK.Span.RootEntry (RootEntry (RootEntry))-import qualified Instana.SDK.Span.RootEntry as RootEntry-import Instana.SDK.Span.Span (Span (..))-import qualified Instana.SDK.Span.Span as Span+import qualified Instana.SDK.Internal.Id as Id+import Instana.SDK.Span.EntrySpan (EntrySpan (RootEntrySpan))+import Instana.SDK.Span.RootEntry (RootEntry (RootEntry))+import qualified Instana.SDK.Span.RootEntry as RootEntry+import qualified Instana.SDK.Span.SimpleSpan as SimpleSpan+import Instana.SDK.Span.Span (Span (..), SpanKind (..))+import qualified Instana.SDK.Span.Span as Span allTests :: Test@@ -26,6 +27,7 @@ , TestLabel "shouldAddBooleanDeeplyNested" shouldAddBooleanDeeplyNested , TestLabel "shouldAddMultipleRegistered" shouldAddMultipleRegistered , TestLabel "shouldAddMultipleTags" shouldAddMultipleTags+ , TestLabel "shouldConvertToSimpleSpanFormat" shouldConvertToSimpleSpanFormat ] @@ -216,6 +218,43 @@ ]) ] )+ spanData+++shouldConvertToSimpleSpanFormat :: Test+shouldConvertToSimpleSpanFormat =+ let+ span_ =+ Span.addRegisteredDataAt "really.deeply.nested.path" True $ entrySpan+ simple = SimpleSpan.convert span_+ traceId = SimpleSpan.traceId simple+ spanId = SimpleSpan.spanId simple+ parentId = SimpleSpan.parentId simple+ spanName = SimpleSpan.spanName simple+ timestamp = SimpleSpan.timestamp simple+ kind = SimpleSpan.kind simple+ errorCount = SimpleSpan.errorCount simple+ spanData = SimpleSpan.spanData simple+ in+ TestCase $ do+ assertEqual "traceId" "traceId" traceId+ assertEqual "spanId" "traceId" spanId+ assertEqual "parentId" Nothing parentId+ assertEqual "spanName" "test.entry" spanName+ assertEqual "timestamp" 1514761200000 timestamp+ assertEqual "kind" EntryKind kind+ assertEqual "errorCount" 0 errorCount+ assertEqual+ "spanData"+ (Aeson.object [+ "really" .= (Aeson.object [+ "deeply" .= (Aeson.object [+ "nested" .= (Aeson.object [+ "path" .= True+ ])+ ])+ ])+ ]) spanData