hs-opentelemetry-instrumentation-cloudflare 0.2.0.2 → 1.0.0.0
raw patch · 6 files changed
+162/−20 lines, 6 filesdep +hs-opentelemetry-exporter-in-memorydep +hs-opentelemetry-instrumentation-cloudflaredep +hs-opentelemetry-sdkdep ~hs-opentelemetry-apidep ~hs-opentelemetry-instrumentation-waiPVP ok
version bump matches the API change (PVP)
Dependencies added: hs-opentelemetry-exporter-in-memory, hs-opentelemetry-instrumentation-cloudflare, hs-opentelemetry-sdk, hs-opentelemetry-semantic-conventions, hspec, http-types, network, vault
Dependency ranges changed: hs-opentelemetry-api, hs-opentelemetry-instrumentation-wai
API changes (from Hackage documentation)
Files
- ChangeLog.md +4/−0
- LICENSE +1/−1
- README.md +7/−1
- hs-opentelemetry-instrumentation-cloudflare.cabal +40/−14
- src/OpenTelemetry/Instrumentation/Cloudflare.hs +11/−4
- test/Spec.hs +99/−0
ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for cloudflare +## 1.0.0.0 - 2026-05-29++- Promoted to 1.0.0.0 for the hs-opentelemetry 1.0 release.+ ## 0.2.0.2 - Relax `hs-opentelemetry-api` bounds to support 0.3.x
LICENSE view
@@ -1,4 +1,4 @@-Copyright Ian Duncan (c) 2022+Copyright Ian Duncan (c) 2022-2026 All rights reserved.
README.md view
@@ -1,3 +1,9 @@ # Cloudflare OpenTelemetry Instrumentation -This instrumentation package can be used with the `hs-opentelemetry-instrumentation-wai` package to capture a number of interesting Cloudflare headers on incoming HTTP requests.+[](https://hackage.haskell.org/package/hs-opentelemetry-instrumentation-cloudflare)++Instrumentation for capturing a number of interesting Cloudflare headers on incoming HTTP requests for WAI applications.++Intended to be used alongside [hs-opentelemetry-instrumentation-wai].++[hs-opentelemetry-instrumentation-wai]: ../wai
hs-opentelemetry-instrumentation-cloudflare.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-cloudflare-version: 0.2.0.2-description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/cloudflare#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-cloudflare+version: 1.0.0.0+synopsis: OpenTelemetry instrumentation for Cloudflare services+description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/instrumentation/cloudflare#readme>+category: OpenTelemetry, Telemetry, Web, Cloudflare+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@@ -33,9 +35,33 @@ build-depends: base >=4.7 && <5 , case-insensitive- , hs-opentelemetry-api ==0.3.*- , hs-opentelemetry-instrumentation-wai+ , hs-opentelemetry-api ==1.0.*+ , hs-opentelemetry-instrumentation-wai ==1.0.*+ , hs-opentelemetry-semantic-conventions >=1.40 && <2 , text , unordered-containers+ , wai+ default-language: Haskell2010++test-suite cloudflare-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_hs_opentelemetry_instrumentation_cloudflare+ 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-cloudflare ==1.0.*+ , hs-opentelemetry-instrumentation-wai ==1.0.*+ , hs-opentelemetry-sdk ==1.0.*+ , hspec+ , http-types+ , network+ , text+ , vault , wai default-language: Haskell2010
src/OpenTelemetry/Instrumentation/Cloudflare.hs view
@@ -1,19 +1,26 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{- |+Module : OpenTelemetry.Instrumentation.Cloudflare+Description : OpenTelemetry instrumentation for Cloudflare Workers.+Stability : experimental++Extracts trace context from Cloudflare request headers.+-} module OpenTelemetry.Instrumentation.Cloudflare where import Control.Monad (forM_) import qualified Data.CaseInsensitive as CI import qualified Data.HashMap.Strict as H import qualified Data.List-import Data.Maybe-import qualified Data.Text as T import qualified Data.Text.Encoding as T import Network.Wai-import OpenTelemetry.Attributes (PrimitiveAttribute (..), ToAttribute (..))+import OpenTelemetry.Attributes (ToAttribute (..))+import OpenTelemetry.Attributes.Key (unkey) import OpenTelemetry.Context import OpenTelemetry.Instrumentation.Wai (requestContext)+import qualified OpenTelemetry.SemanticConventions as SC import OpenTelemetry.Trace.Core (addAttributes) @@ -29,7 +36,7 @@ Nothing -> [] Just val -> [- ( "http.request.header." <> T.decodeUtf8 (CI.foldedCase hn)+ ( unkey (SC.http_request_header (T.decodeUtf8 (CI.foldedCase hn))) , toAttribute $ T.decodeUtf8 val ) ]
+ test/Spec.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Data.IORef+import qualified Data.Vault.Lazy as Vault+import Network.HTTP.Types (RequestHeaders, ok200)+import Network.Socket (SockAddr (..))+import Network.Wai (defaultRequest, responseLBS)+import Network.Wai.Internal (Request (..), ResponseReceived (..))+import OpenTelemetry.Attributes (lookupAttribute)+import OpenTelemetry.Attributes.Attribute (Attribute (..), PrimitiveAttribute (..))+import OpenTelemetry.Exporter.InMemory.Span (inMemoryListExporter)+import OpenTelemetry.Instrumentation.Cloudflare (cloudflareInstrumentationMiddleware)+import OpenTelemetry.Instrumentation.Wai (newOpenTelemetryWaiMiddleware')+import OpenTelemetry.Internal.Common.Types (instrumentationLibrary)+import OpenTelemetry.Metric.Core (noopMeter)+import OpenTelemetry.Trace.Core+import System.Environment (setEnv)+import Test.Hspec+++main :: IO ()+main = do+ setEnv "OTEL_SEMCONV_STABILITY_OPT_IN" "http"+ hspec spec+++spec :: Spec+spec = describe "Cloudflare middleware" $ do+ it "adds cf-connecting-ip header as attribute" $ do+ spans <-+ withCloudflareMiddleware+ [("cf-connecting-ip", "203.0.113.50"), ("Host", "example.com")]+ s <- firstSpan spans+ hot <- readIORef (spanHot s)+ lookupAttribute (hotAttributes hot) "http.request.header.cf-connecting-ip"+ `shouldBe` Just (AttributeValue (TextAttribute "203.0.113.50"))++ it "adds cf-ray header as attribute" $ do+ spans <-+ withCloudflareMiddleware+ [("cf-ray", "abc123-LAX"), ("Host", "example.com")]+ s <- firstSpan spans+ hot <- readIORef (spanHot s)+ lookupAttribute (hotAttributes hot) "http.request.header.cf-ray"+ `shouldBe` Just (AttributeValue (TextAttribute "abc123-LAX"))++ it "adds cf-ipcountry header as attribute" $ do+ spans <-+ withCloudflareMiddleware+ [("cf-ipcountry", "US"), ("Host", "example.com")]+ s <- firstSpan spans+ hot <- readIORef (spanHot s)+ lookupAttribute (hotAttributes hot) "http.request.header.cf-ipcountry"+ `shouldBe` Just (AttributeValue (TextAttribute "US"))++ it "adds true-client-ip header as attribute" $ do+ spans <-+ withCloudflareMiddleware+ [("true-client-ip", "198.51.100.42"), ("Host", "example.com")]+ s <- firstSpan spans+ hot <- readIORef (spanHot s)+ lookupAttribute (hotAttributes hot) "http.request.header.true-client-ip"+ `shouldBe` Just (AttributeValue (TextAttribute "198.51.100.42"))++ it "does not add attributes for missing CF headers" $ do+ spans <- withCloudflareMiddleware [("Host", "example.com")]+ s <- firstSpan spans+ hot <- readIORef (spanHot s)+ lookupAttribute (hotAttributes hot) "http.request.header.cf-connecting-ip"+ `shouldBe` Nothing+ lookupAttribute (hotAttributes hot) "http.request.header.cf-ray"+ `shouldBe` Nothing+++firstSpan :: [ImmutableSpan] -> IO ImmutableSpan+firstSpan (s : _) = pure s+firstSpan [] = expectationFailure "No spans recorded" >> pure (error "unreachable")+++withCloudflareMiddleware :: RequestHeaders -> IO [ImmutableSpan]+withCloudflareMiddleware headers = do+ (processor, ref) <- inMemoryListExporter+ tp <- createTracerProvider [processor] emptyTracerProviderOptions+ waiMw <- newOpenTelemetryWaiMiddleware' tp (noopMeter (instrumentationLibrary "test" "0.0.0"))+ let cfMw = cloudflareInstrumentationMiddleware+ app _req respond = respond $ responseLBS ok200 [] "ok"+ req =+ defaultRequest+ { requestMethod = "GET"+ , rawPathInfo = "/test"+ , requestHeaders = headers+ , remoteHost = SockAddrInet 12345 0x0100007f+ , vault = Vault.empty+ }+ _ <- waiMw (cfMw app) req $ \_ -> pure ResponseReceived+ _ <- shutdownTracerProvider tp Nothing+ readIORef ref