packages feed

freckle-app-1.16.0.0: library/Freckle/App/OpenTelemetry/Http.hs

module Freckle.App.OpenTelemetry.Http
  ( httpSpanName
  , httpSpanArguments
  , httpAttributes
  , httpResponseAttributes
  ) where

import Freckle.App.Prelude

import qualified Data.CaseInsensitive as CI
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Freckle.App.OpenTelemetry
  ( SpanArguments (..)
  , byteStringToAttribute
  , clientSpanArguments
  )
import Network.HTTP.Client (Request, Response)
import qualified Network.HTTP.Client as HTTP
import Network.HTTP.Types.Status (statusCode)
import OpenTelemetry.Attributes (Attribute, ToAttribute (..))

httpSpanName :: Request -> Text
httpSpanName req =
  decodeUtf8With lenientDecode $ HTTP.method req <> " " <> HTTP.path req

httpSpanArguments :: Request -> SpanArguments
httpSpanArguments req = clientSpanArguments {attributes = httpAttributes req}

httpAttributes :: Request -> HashMap Text Attribute
httpAttributes req =
  HashMap.fromList
    [ ("service.name", byteStringToAttribute $ HTTP.host req)
    , ("resource.name", toAttribute $ httpSpanName req)
    , ("http.host", byteStringToAttribute $ HTTP.host req)
    , ("http.method", byteStringToAttribute $ HTTP.method req)
    , ("http.path", byteStringToAttribute $ HTTP.path req)
    , ("http.query", byteStringToAttribute $ HTTP.queryString req)
    ]

httpResponseAttributes :: Response body -> HashMap Text Attribute
httpResponseAttributes resp = statusAttr <> foldMap (uncurry headerAttr) (HTTP.responseHeaders resp)
 where
  statusAttr =
    HashMap.singleton "http.status_code"
      . toAttribute
      . statusCode
      $ HTTP.responseStatus resp

  headerAttr k = HashMap.singleton (headerAttrKey k) . byteStringToAttribute

  headerAttrKey =
    ("http.response.headers." <>)
      . T.toLower
      . decodeUtf8With lenientDecode
      . CI.original