packages feed

hs-opentelemetry-api-0.0.1.0: src/OpenTelemetry/Propagator.hs

{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  OpenTelemetry.Trace.Propagator
-- Copyright   :  (c) Ian Duncan, 2021
-- License     :  BSD-3
-- Description :  Sending and receiving state between system boundaries
-- Maintainer  :  Ian Duncan
-- Stability   :  experimental
-- Portability :  non-portable (GHC extensions)
--
-- Cross-cutting concerns send their state to the next process using Propagators, which are defined as objects used to 
-- read and write context data to and from messages exchanged by the applications. 
-- Each concern creates a set of Propagators for every supported Propagator type.
--
-- Propagators leverage the Context to inject and extract data for each cross-cutting concern, such as traces and Baggage.
--
-- Propagation is usually implemented via a cooperation of library-specific request interceptors and Propagators, 
-- where the interceptors detect incoming and outgoing requests and use the Propagator's extract and inject operations 
-- respectively.
--
-- The Propagators API is expected to be leveraged by users writing instrumentation libraries. However,
-- users using the OpenTelemetry SDK may need to select appropriate propagators to work with existing 3rd party systems
-- such as AWS.
--
-----------------------------------------------------------------------------
module OpenTelemetry.Propagator where

import Control.Monad
import Control.Monad.IO.Class
import Data.Text

{- |
A carrier is the medium used by Propagators to read values from and write values to. 
Each specific Propagator type defines its expected carrier type, such as a string map or a byte array.
-}
data Propagator context inboundCarrier outboundCarrier = Propagator
  { propagatorNames :: [Text]
  , extractor :: inboundCarrier -> context -> IO context
  , injector :: context -> outboundCarrier -> IO outboundCarrier
  }

instance Semigroup (Propagator c i o) where
  (Propagator lNames lExtract lInject) <> (Propagator rNames rExtract rInject) = Propagator
    { propagatorNames = lNames <> rNames
    , extractor = \i -> lExtract i >=> rExtract i
    , injector = \c -> lInject c >=> rInject c
    }

instance Monoid (Propagator c i o) where
  mempty = Propagator mempty (\_ c -> pure c) (\_ p -> pure p)

{- |
Extracts the value from an incoming request. For example, from the headers of an HTTP request.

If a value can not be parsed from the carrier, for a cross-cutting concern, the implementation MUST NOT throw an exception and MUST NOT store a new value in the Context, in order to preserve any previously existing valid value.
-}
extract :: (MonadIO m) 
  => Propagator context i o 
  -> i -- ^ The carrier that holds the propagation fields. For example, an incoming message or HTTP request.
  -> context 
  -> m context -- ^ a new Context derived from the Context passed as argument, containing the extracted value, which can be a SpanContext, Baggage or another cross-cutting concern context.
extract (Propagator _ extractor _) i = liftIO . extractor i

-- | Injects the value into a carrier. For example, into the headers of an HTTP request.
inject :: (MonadIO m) 
  => Propagator context i o 
  -> context 
  -> o -- ^ The carrier that holds the propagation fields. For example, an outgoing message or HTTP request. 
  -> m o
inject (Propagator _ _ injector) c = liftIO . injector c