packages feed

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 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.+[![hs-opentelemetry-instrumentation-cloudflare](https://img.shields.io/hackage/v/hs-opentelemetry-instrumentation-cloudflare?style=flat-square&logo=haskell&label=hs-opentelemetry-instrumentation-cloudflare&labelColor=5D4F85)](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