freckle-otel 0.0.0.3 → 0.1.0.0
raw patch · 6 files changed
+100/−51 lines, 6 filesdep ~Blammodep ~aesondep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: Blammo, aeson, base, bytestring, exceptions, faktory, hs-opentelemetry-api, hs-opentelemetry-sdk, hspec, hspec-core, http-client, http-types, lens, mtl, text, unordered-containers
API changes (from Hackage documentation)
Files
- CHANGELOG.md +21/−1
- freckle-otel.cabal +25/−25
- library/Freckle/App/OpenTelemetry.hs +4/−7
- library/Freckle/App/OpenTelemetry/Context.hs +19/−4
- package.yaml +1/−1
- tests/Freckle/App/OpenTelemetry/ContextSpec.hs +30/−13
CHANGELOG.md view
@@ -1,4 +1,24 @@-## [_Unreleased_](https://github.com/freckle/freckle-otel/compare/v0.0.0.3...main)+## [_Unreleased_](https://github.com/freckle/freckle-otel/compare/v0.1.0.0...main)++## [v0.1.0.0](https://github.com/freckle/freckle-otel/compare/v0.0.0.3...v0.1.0.0)++Drops support for GHC 9.2 and GHC 9.4. `hs-opentelemetry-sdk`'s OTLP exporter now depends on a+`tls`/`crypton` stack too recent to satisfy against those two GHC versions' package sets. GHC 9.6+is now the oldest supported version, which also drops support for `base` 4.16 and 4.17.++Require `hs-opentelemetry-api` and `hs-opentelemetry-sdk` at `1.0.0.0` or later. A few behavior+changes from that upgrade are visible through this package's own functions:++- `withTracerProvider`'s shutdown now applies a default 5 second timeout instead of waiting+ indefinitely+- `inSpan`, and so `getCurrentSpanContext`, `getCurrentTraceId`, `addCurrentSpanAttributes`, and+ `withTraceContext`, no longer see the current span when the `TracerProvider` has no+ `SpanProcessor` configured (e.g. `OTEL_TRACES_EXPORTER=none`)+- `extractContext`, `injectContext`, and `processWithContext` keep propagating trace context via+ headers even when `OTEL_SDK_DISABLED=true`, where they previously became no-ops+- the `traceparent` header `injectContext`/`processWithContext` write for a sampled root span now+ has its trailing flags byte set to `03` (sampled, plus the new W3C Trace Context "random trace+ ID" bit), where it was previously `00` ## [v0.0.0.3](https://github.com/freckle/freckle-otel/compare/v0.0.0.2...v0.0.0.3)
freckle-otel.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: freckle-otel-version: 0.0.0.3+version: 0.1.0.0 license: MIT license-file: LICENSE maintainer: Freckle Education@@ -45,23 +45,23 @@ -Wno-safe -Wno-unsafe build-depends:- Blammo >=2.1.0.0,- aeson >=2.0.3.0,- base >=4.16.4.0 && <5,- bytestring >=0.11.4.0,+ Blammo >=2.1.1.0,+ aeson >=2.1.2.1,+ base >=4.18.2.1 && <5,+ bytestring >=0.11.5.3, case-insensitive >=1.2.1.0, errors >=2.3.0,- exceptions >=0.10.4,- faktory >=1.1.2.4,- hs-opentelemetry-api >=0.1.0.0,- hs-opentelemetry-sdk >=0.0.3.6,- http-client >=0.7.13.1,+ exceptions >=0.10.7,+ faktory >=1.1.3.0,+ hs-opentelemetry-api >=1.0.0.0,+ hs-opentelemetry-sdk >=1.0.0.0,+ http-client >=0.7.17, http-conduit >=2.3.5,- http-types >=0.12.3,- lens >=5.1.1,- text >=1.2.5.0,+ http-types >=0.12.4,+ lens >=5.2.3,+ text >=2.0.2, unliftio >=0.2.25.0,- unordered-containers >=0.2.19.1+ unordered-containers >=0.2.20 if impl(ghc >=9.8) ghc-options:@@ -91,20 +91,20 @@ -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N build-depends:- Blammo >=2.1.0.0,- base >=4.16.4.0 && <5,- exceptions >=0.10.4,+ Blammo >=2.1.1.0,+ base >=4.18.2.1 && <5,+ exceptions >=0.10.7, freckle-env >=0.0.1.2, freckle-otel,- hs-opentelemetry-api >=0.1.0.0,- hs-opentelemetry-sdk >=0.0.3.6,- hspec >=2.9.7,- hspec-core >=2.9.7,+ hs-opentelemetry-api >=1.0.0.0,+ hs-opentelemetry-sdk >=1.0.0.0,+ hspec >=2.11.10,+ hspec-core >=2.11.10, hspec-expectations-lifted >=0.10.0,- http-types >=0.12.3,- lens >=5.1.1,- mtl >=2.2.2,- text >=1.2.5.0,+ http-types >=0.12.4,+ lens >=5.2.3,+ mtl >=2.3.1,+ text >=2.0.2, unliftio >=0.2.25.0 if impl(ghc >=9.8)
library/Freckle/App/OpenTelemetry.hs view
@@ -19,11 +19,8 @@ -- You may need to do this even if you don't plan to manually trace things, in -- order to satisfy the 'MonadTracer' constraint required by functions like -- 'runDB'. If you don't need this feature, and don't plan on running an--- otel-collector, set @OTEL_TRACES_EXPORTER=none@ in the environment, which--- makes all tracing a no-op.------ In the future, it should be possible to use @OTEL_SDK_DISABLED@ for the same--- purpose. See <https://github.com/iand675/hs-opentelemetry/issues/60>.+-- otel-collector, set @OTEL_SDK_DISABLED=true@ in the environment, to+-- disable any actual tracing. module Freckle.App.OpenTelemetry ( HasTracer (..) , Tracer@@ -76,7 +73,7 @@ import Data.Text.Encoding.Error qualified as T import OpenTelemetry.Context (lookupSpan) import OpenTelemetry.Context.ThreadLocal (getContext)-import OpenTelemetry.Trace hiding (inSpan)+import OpenTelemetry.Trace hiding (inSpan, withTracerProvider) import OpenTelemetry.Trace.Core (getSpanContext) import OpenTelemetry.Trace.Core qualified as Trace (SpanContext (..)) import OpenTelemetry.Trace.Id@@ -127,7 +124,7 @@ withTracerProvider = bracket (liftIO initializeGlobalTracerProvider)- (liftIO . shutdownTracerProvider)+ (void . liftIO . flip shutdownTracerProvider Nothing) getCurrentTraceId :: MonadIO m => m (Maybe TraceId) getCurrentTraceId = fmap Trace.traceId <$> getCurrentSpanContext
library/Freckle/App/OpenTelemetry/Context.hs view
@@ -38,7 +38,14 @@ import Network.HTTP.Types.Header (Header) import OpenTelemetry.Context (Context) import OpenTelemetry.Context.ThreadLocal (attachContext, getContext)-import OpenTelemetry.Propagator (Propagator, extract, inject)+import OpenTelemetry.Propagator+ ( Propagator+ , TextMap+ , extract+ , inject+ , textMapFromList+ , textMapToList+ ) import OpenTelemetry.Trace.Core ( getTracerProviderPropagators , getTracerTracerProvider@@ -89,7 +96,7 @@ extractContext a = do context <- getContext propagator <- getPropagator- updatedContext <- extract propagator (a ^. headersL) context+ updatedContext <- extract propagator (headersToTextMap $ a ^. headersL) context void $ attachContext updatedContext -- | Inject our trace context into the given item's headers@@ -98,12 +105,20 @@ injectContext a = do context <- getContext propagator <- getPropagator- headers <- inject propagator context $ a ^. headersL+ headers <-+ textMapToHeaders+ <$> inject propagator context (headersToTextMap $ a ^. headersL) pure $ a & headersL .~ headers -getPropagator :: MonadTracer m => m (Propagator Context [Header] [Header])+getPropagator :: MonadTracer m => m (Propagator Context TextMap TextMap) getPropagator = getTracerProviderPropagators . getTracerTracerProvider <$> getTracer++headersToTextMap :: [Header] -> TextMap+headersToTextMap = textMapFromList . map decode++textMapToHeaders :: TextMap -> [Header]+textMapToHeaders = map encode . textMapToList -- | Process an item (a request, a Job, etc) in a top-level span and context processWithContext
package.yaml view
@@ -1,5 +1,5 @@ name: freckle-otel-version: 0.0.0.3+version: 0.1.0.0 maintainer: Freckle Education category: OpenTelemetry, Telemetry, Monitoring, Observability, Metrics github: freckle/freckle-otel
tests/Freckle/App/OpenTelemetry/ContextSpec.hs view
@@ -9,6 +9,7 @@ import Blammo.Logging.Logger (Logger, newTestLogger) import Blammo.Logging.Setup (HasLogger (..)) import Control.Lens (lens)+import Control.Monad (void) import Control.Monad.IO.Class (MonadIO) import Data.List qualified as List import Data.Text (Text)@@ -18,6 +19,8 @@ import Freckle.App.OpenTelemetry.Context import GHC.Stack (HasCallStack) import Network.HTTP.Types.Header (Header)+import OpenTelemetry.Exporter.Span (ExportResult (..), SpanExporter (..))+import OpenTelemetry.Registry (registerSpanExporterFactoryIfAbsent) import OpenTelemetry.Trace.Core qualified as Trace import Test.Hspec (Spec, describe, it) import Test.Hspec.Expectations.Lifted@@ -42,26 +45,42 @@ loadApp f = do appLogger <- newTestLogger defaultLogSettings + -- A TracerProvider with no SpanProcessor never attaches its spans to+ -- context, so getCurrentSpanContext wouldn't see them; register a no-op+ -- exporter so one exists without actually exporting anywhere+ void+ $ registerSpanExporterFactoryIfAbsent "freckle-otel-test-noop"+ $ pure noopSpanExporter+ withTracerProvider $ \tracerProvider -> do let appTracer = makeTracer tracerProvider "app" tracerOptions f App {..} +noopSpanExporter :: SpanExporter+noopSpanExporter =+ SpanExporter+ { spanExporterExport = const $ pure Success+ , spanExporterShutdown = pure Trace.ShutdownSuccess+ , spanExporterForceFlush = pure Trace.FlushSuccess+ }+ spec :: Spec spec = withApp loadApp $ do describe "injectContext" $ do it "sets request headers from existing context" $ appExample @App $ do inSpan "example" defaultSpanArguments $ do spanContext <- assertCurrentSpanContext+ headers <- injectContext ([] :: [Header]) - let expectedTraceParent =- toTraceParent- (traceIdToHex $ Trace.traceId spanContext)- (spanIdToHex $ Trace.spanId spanContext)+ let headerTraceParent = do+ bs <- List.lookup "traceparent" headers+ fromTraceParent $ T.decodeUtf8 bs - injectContext ([] :: [Header])- `shouldReturn` [ ("traceparent", T.encodeUtf8 expectedTraceParent)- , ("tracestate", "")- ]+ fmap fst headerTraceParent+ `shouldBe` Just (traceIdToHex $ Trace.traceId spanContext)+ fmap snd headerTraceParent+ `shouldBe` Just (spanIdToHex $ Trace.spanId spanContext)+ List.lookup "tracestate" headers `shouldBe` Just "" describe "extractContext" $ do it "sets the context from the headers" $ appExample @App $ do@@ -163,11 +182,9 @@ toTraceParent traceId spanId = "00-" <> traceId <> "-" <> spanId <> "-00" fromTraceParent :: Text -> Maybe (Text, Text)-fromTraceParent a = do- b <- T.stripPrefix "00-" a- c <- T.stripSuffix "-00" b- [traceId, spanId] <- Just $ T.splitOn "-" c- pure (traceId, spanId)+fromTraceParent a = case T.splitOn "-" a of+ [_version, traceId, spanId, _flags] -> Just (traceId, spanId)+ _ -> Nothing expectationFailure :: (HasCallStack, MonadIO m) => String -> m a expectationFailure msg = Hspec.expectationFailure msg >> error "unreachable"