hs-opentelemetry-instrumentation-hspec 0.0.1.3 → 1.0.0.0
raw patch · 6 files changed
+159/−30 lines, 6 filesdep +hs-opentelemetry-exporter-in-memorydep +hs-opentelemetry-instrumentation-hspecdep +hs-opentelemetry-sdkdep ~hs-opentelemetry-apidep ~hspec-corePVP ok
version bump matches the API change (PVP)
Dependencies added: hs-opentelemetry-exporter-in-memory, hs-opentelemetry-instrumentation-hspec, hs-opentelemetry-sdk, hspec
Dependency ranges changed: hs-opentelemetry-api, hspec-core
API changes (from Hackage documentation)
Files
- ChangeLog.md +4/−0
- LICENSE +1/−1
- README.md +18/−4
- hs-opentelemetry-instrumentation-hspec.cabal +34/−13
- src/OpenTelemetry/Instrumentation/Hspec.hs +44/−12
- test/Spec.hs +58/−0
ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for hs-opentelemetry-instrumentation-hspec +## 1.0.0.0 - 2026-05-29++- Promoted to 1.0.0.0 for the hs-opentelemetry 1.0 release.+ ## 0.0.1.3 - Relax `hs-opentelemetry-api` bounds to support 0.3.x
LICENSE view
@@ -1,4 +1,4 @@-Copyright Ian Duncan (c) 2021+Copyright Ian Duncan (c) 2021-2026 All rights reserved.
README.md view
@@ -1,9 +1,23 @@ # hs-opentelemetry-instrumentation-hspec -Instrumentation for Hspec for OpenTelemetry. This creates a span for each test-case inside a test suite.+[](https://hackage.haskell.org/package/hs-opentelemetry-instrumentation-hspec) +OpenTelemetry instrumentation for the [Hspec] test framework; it creates one span per test case inside the instrumented `Spec` tree.++[Hspec]: https://hackage.haskell.org/package/hspec+ ## Usage -See the example in [examples/hspec](../../examples/hspec) for an instrumented-test suite.+```haskell+do+ provider <- getGlobalTracerProvider+ let tracer = OpenTelemetry.Trace.makeTracer provider "my-test-suite" OpenTelemetry.Trace.tracerOptions+ context <- OpenTelemetry.Context.ThreadLocal.getContext++ hspec $ instrumentSpec tracer context $ do+ describe "Spec" do+ it "is instrumented with OpenTelemetry" do+ True `shouldBe` True+```++See [examples/hspec](../../examples/hspec) for an instrumented test suite.
hs-opentelemetry-instrumentation-hspec.cabal view
@@ -1,20 +1,22 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.38.3. -- -- see: https://github.com/sol/hpack -name: hs-opentelemetry-instrumentation-hspec-version: 0.0.1.3-description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/hspec#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: 2024 Ian Duncan, Mercury Technologies-license: BSD3-license-file: LICENSE-build-type: Simple+name: hs-opentelemetry-instrumentation-hspec+version: 1.0.0.0+synopsis: OpenTelemetry instrumentation for Hspec test suites+description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/hspec#readme>+category: OpenTelemetry, Testing+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: 2024 Ian Duncan, Mercury Technologies+license: BSD3+license-file: LICENSE+build-type: Simple extra-source-files: README.md ChangeLog.md@@ -32,8 +34,27 @@ src build-depends: base >=4.7 && <5- , hs-opentelemetry-api ==0.3.*+ , hs-opentelemetry-api ==1.0.* , hspec-core >=2.9.4 , mtl+ , text+ default-language: Haskell2010++test-suite hs-opentelemetry-hspec-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_hs_opentelemetry_instrumentation_hspec+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , hs-opentelemetry-api ==1.0.*+ , hs-opentelemetry-exporter-in-memory ==1.0.*+ , hs-opentelemetry-instrumentation-hspec ==1.0.*+ , hs-opentelemetry-sdk ==1.0.*+ , hspec+ , hspec-core , text default-language: Haskell2010
src/OpenTelemetry/Instrumentation/Hspec.hs view
@@ -1,24 +1,56 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} --- | Instrumentation for Hspec test suites+{- |+Module : OpenTelemetry.Instrumentation.Hspec+Copyright : (c) Ian Duncan, 2021-2026+License : BSD-3+Description : Trace Hspec test suites as spans+Stability : experimental++= Overview++Adds tracing around Hspec examples. Useful for CI observability: see which+tests ran, how long they took, and which ones failed.++'wrapSpec' uses the global tracer provider; compose it with+'OpenTelemetry.Trace.withTracerProvider' so the provider is initialized before+@hspec@ runs. For full control over tracer and parent context, use+'instrumentSpec' instead.++= Quick example++@+import Test.Hspec+import OpenTelemetry.Instrumentation.Hspec (wrapSpec)+import OpenTelemetry.Trace (withTracerProvider)++main :: IO ()+main = withTracerProvider $ \_ -> do+ runSpec <- wrapSpec+ hspec $ runSpec mySpec+@++'OpenTelemetry.Trace.withTracerProvider' lives in @hs-opentelemetry-sdk@.++= Nested structure++'instrumentSpec' adds spans for @describe@ nesting; 'wrapSpec' produces a flat+span per @it@ (see its Haddock for rationale).+-} module OpenTelemetry.Instrumentation.Hspec ( wrapSpec, wrapExampleInSpan, instrumentSpec, ) where -import Control.Monad (void) import Control.Monad.IO.Class-import Control.Monad.Reader import qualified Data.List as List-import Data.Text (Text) import qualified Data.Text as T-import OpenTelemetry.Attributes (Attributes) import OpenTelemetry.Context-import OpenTelemetry.Context.ThreadLocal (adjustContext, attachContext, getContext)+import OpenTelemetry.Context.ThreadLocal (attachContext, getContext) import OpenTelemetry.Trace.Core-import Test.Hspec.Core.Spec (ActionWith, Item (..), Spec, SpecWith, Tree (..), mapSpecForest, mapSpecItem_)+import Test.Hspec.Core.Spec (Item (..), SpecWith, Tree (..), mapSpecForest, mapSpecItem_) {- | Creates a wrapper function that you can pass a spec into.@@ -35,9 +67,9 @@ let tracer = makeTracer tp $detectInstrumentationLibrary tracerOptions context <- getContext - -- FIXME: this kind of just dumps everything flat into one span per `it`. We- -- could possibly do better, e.g. finding the `describe`s and making them into- -- spans but I am not sure how that would be achieved.+ -- Span structure is flat: one span per @it@ item. Nesting spans under+ -- @describe@ blocks would require access to hspec's internal spec tree+ -- before execution, which isn't exposed by the hspec public API. pure $ \spec -> mapSpecItem_ (wrapExampleInSpan tracer context) spec @@ -50,7 +82,7 @@ { itemExample = \params aroundAction pcb -> do let aroundAction' a = do -- we need to reattach the context, since we are on a forked thread- void $ attachContext traceContext+ _ <- attachContext traceContext inSpan tp (T.pack req) defaultSpanArguments (aroundAction a) ex params aroundAction' pcb@@ -75,7 +107,7 @@ { itemExample = \params aroundAction pcb -> do let aroundAction' a = do -- we need to reattach the context, since we are on a forked thread- void $ attachContext traceContext+ _ <- attachContext traceContext addSpans spans $ inSpan tracer (T.pack (itemRequirement item)) defaultSpanArguments (aroundAction a) itemExample item params aroundAction' pcb
+ test/Spec.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Data.IORef+import OpenTelemetry.Context.ThreadLocal (getContext)+import OpenTelemetry.Exporter.InMemory.Span (inMemoryListExporter)+import OpenTelemetry.Instrumentation.Hspec (instrumentSpec)+import OpenTelemetry.Trace.Core+import Test.Hspec+import Test.Hspec.Core.Runner (hspecResult)+++main :: IO ()+main = hspec spec+++spec :: Spec+spec = describe "Hspec instrumentation" $ do+ it "instrumentSpec creates spans for each test item" $ do+ (processor, ref) <- inMemoryListExporter+ tp <- createTracerProvider [processor] emptyTracerProviderOptions+ setGlobalTracerProvider tp+ let tracer = makeTracer tp "test" tracerOptions+ ctx <- getContext++ let innerSpec = instrumentSpec tracer ctx $ do+ it "alpha" $ True `shouldBe` True+ it "beta" $ True `shouldBe` True++ _summary <- hspecResult innerSpec++ _ <- shutdownTracerProvider tp Nothing+ spans <- readIORef ref+ names <- traverse (\s -> hotName <$> readIORef (spanHot s)) spans+ names `shouldContain` ["alpha"]+ names `shouldContain` ["beta"]++ it "instrumentSpec nests describe groups as spans" $ do+ (processor, ref) <- inMemoryListExporter+ tp <- createTracerProvider [processor] emptyTracerProviderOptions+ setGlobalTracerProvider tp+ let tracer = makeTracer tp "test-nested" tracerOptions+ ctx <- getContext++ let innerSpec =+ instrumentSpec tracer ctx $+ describe "outer group" $+ it "nested test" $+ True `shouldBe` True++ _summary <- hspecResult innerSpec++ _ <- shutdownTracerProvider tp Nothing+ spans <- readIORef ref+ names <- traverse (\s -> hotName <$> readIORef (spanHot s)) spans+ names `shouldContain` ["nested test"]+ names `shouldContain` ["outer group"]