diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,11 @@
 # Changelog for hs-opentelemetry-api
 
+## Unreleased changes
+
+## 0.1.0.0
+
+- Use `HashMap Text Attribute` instead of `[(Text, Attribute)]` as attributes
+
 ## 0.0.3.6
 
 - GHC 9.4 support
@@ -23,5 +29,3 @@
 ## 0.0.1.0
 
 - Initial release
-
-## Unreleased changes
diff --git a/hs-opentelemetry-api.cabal b/hs-opentelemetry-api.cabal
--- a/hs-opentelemetry-api.cabal
+++ b/hs-opentelemetry-api.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:               hs-opentelemetry-api
-version:            0.0.3.8
+version:            0.1.0.0
 synopsis:           OpenTelemetry API for use by libraries for direct instrumentation or wrapper packages.
 description:        Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/api#readme>
 category:           OpenTelemetry, Telemetry, Monitoring, Observability, Metrics
@@ -32,6 +32,8 @@
       OpenTelemetry.Common
       OpenTelemetry.Context
       OpenTelemetry.Context.ThreadLocal
+      OpenTelemetry.Contrib.CarryOns
+      OpenTelemetry.Contrib.SpanTraversals
       OpenTelemetry.Exporter
       OpenTelemetry.Internal.Trace.Id
       OpenTelemetry.Logging.Core
@@ -96,6 +98,7 @@
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
+      OpenTelemetry.BaggageSpec
       OpenTelemetry.Trace.SamplerSpec
       OpenTelemetry.Trace.TraceFlagsSpec
       Paths_hs_opentelemetry_api
diff --git a/src/OpenTelemetry/Attributes.hs b/src/OpenTelemetry/Attributes.hs
--- a/src/OpenTelemetry/Attributes.hs
+++ b/src/OpenTelemetry/Attributes.hs
@@ -7,10 +7,6 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE StrictData #-}
 
------------------------------------------------------------------------------
-
------------------------------------------------------------------------------
-
 {- |
  Module      :  OpenTelemetry.Attributes
  Copyright   :  (c) Ian Duncan, 2021
@@ -49,15 +45,14 @@
   unsafeMergeAttributesIgnoringLimits,
 ) where
 
-import Data.Data
+import Data.Data (Data)
 import qualified Data.HashMap.Strict as H
-import Data.Hashable
+import Data.Hashable (Hashable)
 import Data.Int (Int64)
-import Data.List (foldl')
-import Data.String
+import Data.String (IsString (..))
 import Data.Text (Text)
 import qualified Data.Text as T
-import GHC.Generics
+import GHC.Generics (Generic)
 
 
 {- | Default attribute limits used in the global attribute limit configuration if no environment variables are set.
@@ -87,7 +82,7 @@
 emptyAttributes = Attributes mempty 0 0
 
 
-addAttribute :: ToAttribute a => AttributeLimits -> Attributes -> Text -> a -> Attributes
+addAttribute :: (ToAttribute a) => AttributeLimits -> Attributes -> Text -> a -> Attributes
 addAttribute AttributeLimits {..} Attributes {..} !k !v = case attributeCountLimit of
   Nothing -> Attributes newAttrs newCount attributesDropped
   Just limit_ ->
@@ -95,29 +90,34 @@
       then Attributes attributes attributesCount (attributesDropped + 1)
       else Attributes newAttrs newCount attributesDropped
   where
-    newAttrs = H.insert k (limitLengths $ toAttribute v) attributes
-    newCount =
-      if H.member k attributes
-        then attributesCount
-        else attributesCount + 1
-
-    limitPrimAttr limit_ (TextAttribute t) = TextAttribute (T.take limit_ t)
-    limitPrimAttr _ attr = attr
-
-    limitLengths attr = case attributeLengthLimit of
-      Nothing -> attr
-      Just limit_ -> case attr of
-        AttributeValue val -> AttributeValue $ limitPrimAttr limit_ val
-        AttributeArray arr -> AttributeArray $ fmap (limitPrimAttr limit_) arr
+    newAttrs = H.insert k (maybe id limitLengths attributeCountLimit $ toAttribute v) attributes
+    newCount = H.size newAttrs
 {-# INLINE addAttribute #-}
 
 
-addAttributes :: ToAttribute a => AttributeLimits -> Attributes -> [(Text, a)] -> Attributes
--- TODO, this could be done more efficiently
-addAttributes limits = foldl' (\(!attrs') (!k, !v) -> addAttribute limits attrs' k v)
+addAttributes :: (ToAttribute a) => AttributeLimits -> Attributes -> H.HashMap Text a -> Attributes
+addAttributes AttributeLimits {..} Attributes {..} attrs = case attributeCountLimit of
+  Nothing -> Attributes newAttrs newCount attributesDropped
+  Just limit_ ->
+    if newCount > limit_
+      then Attributes attributes attributesCount (attributesDropped + H.size attrs)
+      else Attributes newAttrs newCount attributesDropped
+  where
+    newAttrs = H.union attributes $ H.map toAttribute attrs
+    newCount = H.size newAttrs
 {-# INLINE addAttributes #-}
 
 
+limitPrimAttr :: Int -> PrimitiveAttribute -> PrimitiveAttribute
+limitPrimAttr limit (TextAttribute t) = TextAttribute (T.take limit t)
+limitPrimAttr _ attr = attr
+
+
+limitLengths :: Int -> Attribute -> Attribute
+limitLengths limit (AttributeValue val) = AttributeValue $ limitPrimAttr limit val
+limitLengths limit (AttributeArray arr) = AttributeArray $ fmap (limitPrimAttr limit) arr
+
+
 getAttributes :: Attributes -> (Int, H.HashMap Text Attribute)
 getAttributes Attributes {..} = (attributesCount, attributes)
 
@@ -203,7 +203,7 @@
 -}
 class ToAttribute a where
   toAttribute :: a -> Attribute
-  default toAttribute :: ToPrimitiveAttribute a => a -> Attribute
+  default toAttribute :: (ToPrimitiveAttribute a) => a -> Attribute
   toAttribute = AttributeValue . toPrimitiveAttribute
 
 
@@ -254,7 +254,7 @@
   toAttribute = id
 
 
-instance ToPrimitiveAttribute a => ToAttribute [a] where
+instance (ToPrimitiveAttribute a) => ToAttribute [a] where
   toAttribute = AttributeArray . map toPrimitiveAttribute
 
 
diff --git a/src/OpenTelemetry/Baggage.hs b/src/OpenTelemetry/Baggage.hs
--- a/src/OpenTelemetry/Baggage.hs
+++ b/src/OpenTelemetry/Baggage.hs
@@ -134,7 +134,7 @@
 
 
 -- Ripped from file-embed-0.0.13
-bsToExp :: Monad m => ByteString -> m Exp
+bsToExp :: (Monad m) => ByteString -> m Exp
 #if MIN_VERSION_template_haskell(2, 5, 0)
 bsToExp bs =
     return $ ConE 'Token
@@ -249,7 +249,7 @@
           ]
     tokenP :: P.Parser Token
     tokenP = Token <$> P.takeWhile1 (`C.member` tokenCharacters)
-    valP = decodeUtf8 <$> P.takeWhile (`C.member` valueSet)
+    valP = decodeUtf8 . urlDecode False <$> P.takeWhile (`C.member` valueSet)
     propertyP :: P.Parser Property
     propertyP = do
       key <- tokenP
@@ -266,13 +266,13 @@
 empty = Baggage H.empty
 
 
-insert ::
-  -- | The name for which to set the value
-  Token ->
-  -- | The value to set. Use 'element' to construct a well-formed element value.
-  Element ->
-  Baggage ->
-  Baggage
+insert
+  :: Token
+  -- ^ The name for which to set the value
+  -> Element
+  -- ^ The value to set. Use 'element' to construct a well-formed element value.
+  -> Baggage
+  -> Baggage
 insert k v (Baggage c) = Baggage (H.insert k v c)
 
 
diff --git a/src/OpenTelemetry/Context.hs b/src/OpenTelemetry/Context.hs
--- a/src/OpenTelemetry/Context.hs
+++ b/src/OpenTelemetry/Context.hs
@@ -53,7 +53,7 @@
 import Prelude hiding (lookup)
 
 
-newKey :: MonadIO m => Text -> m (Key a)
+newKey :: (MonadIO m) => Text -> m (Key a)
 newKey n = liftIO (Key n <$> V.newKey)
 
 
diff --git a/src/OpenTelemetry/Context/ThreadLocal.hs b/src/OpenTelemetry/Context/ThreadLocal.hs
--- a/src/OpenTelemetry/Context/ThreadLocal.hs
+++ b/src/OpenTelemetry/Context/ThreadLocal.hs
@@ -84,7 +84,7 @@
 
  @since 0.0.1.0
 -}
-getContext :: MonadIO m => m Context
+getContext :: (MonadIO m) => m Context
 getContext = fromMaybe empty <$> lookupContext
 
 
@@ -92,7 +92,7 @@
 
  @since 0.0.1.0
 -}
-lookupContext :: MonadIO m => m (Maybe Context)
+lookupContext :: (MonadIO m) => m (Maybe Context)
 lookupContext = lookup threadContextMap
 
 
@@ -100,7 +100,7 @@
 
  @since 0.0.1.0
 -}
-lookupContextOnThread :: MonadIO m => ThreadId -> m (Maybe Context)
+lookupContextOnThread :: (MonadIO m) => ThreadId -> m (Maybe Context)
 lookupContextOnThread = lookupOnThread threadContextMap
 
 
@@ -108,7 +108,7 @@
 
  @since 0.0.1.0
 -}
-attachContext :: MonadIO m => Context -> m (Maybe Context)
+attachContext :: (MonadIO m) => Context -> m (Maybe Context)
 attachContext = attach threadContextMap
 
 
@@ -116,7 +116,7 @@
 
  @since 0.0.1.0
 -}
-attachContextOnThread :: MonadIO m => ThreadId -> Context -> m (Maybe Context)
+attachContextOnThread :: (MonadIO m) => ThreadId -> Context -> m (Maybe Context)
 attachContextOnThread = attachOnThread threadContextMap
 
 
@@ -129,7 +129,7 @@
 
  @since 0.0.1.0
 -}
-detachContext :: MonadIO m => m (Maybe Context)
+detachContext :: (MonadIO m) => m (Maybe Context)
 detachContext = detach threadContextMap
 
 
@@ -142,7 +142,7 @@
 
  @since 0.0.1.0
 -}
-detachContextFromThread :: MonadIO m => ThreadId -> m (Maybe Context)
+detachContextFromThread :: (MonadIO m) => ThreadId -> m (Maybe Context)
 detachContextFromThread = detachFromThread threadContextMap
 
 
@@ -153,7 +153,7 @@
 
  @since 0.0.1.0
 -}
-adjustContext :: MonadIO m => (Context -> Context) -> m ()
+adjustContext :: (MonadIO m) => (Context -> Context) -> m ()
 adjustContext f = update threadContextMap $ \mctx ->
   (pure $ f $ fromMaybe empty mctx, ())
 
@@ -165,6 +165,6 @@
 
  @since 0.0.1.0
 -}
-adjustContextOnThread :: MonadIO m => ThreadId -> (Context -> Context) -> m ()
+adjustContextOnThread :: (MonadIO m) => ThreadId -> (Context -> Context) -> m ()
 adjustContextOnThread tid f = updateOnThread threadContextMap tid $ \mctx ->
   (pure $ f $ fromMaybe empty mctx, ())
diff --git a/src/OpenTelemetry/Contrib/CarryOns.hs b/src/OpenTelemetry/Contrib/CarryOns.hs
new file mode 100644
--- /dev/null
+++ b/src/OpenTelemetry/Contrib/CarryOns.hs
@@ -0,0 +1,60 @@
+module OpenTelemetry.Contrib.CarryOns (
+  alterCarryOns,
+  withCarryOnProcessor,
+) where
+
+import Control.Monad.IO.Class
+import qualified Data.HashMap.Strict as H
+import Data.IORef (modifyIORef')
+import Data.Maybe (fromMaybe)
+import Data.Text (Text)
+import OpenTelemetry.Attributes
+import OpenTelemetry.Context
+import qualified OpenTelemetry.Context as Context
+import OpenTelemetry.Context.ThreadLocal
+import OpenTelemetry.Internal.Trace.Types
+import OpenTelemetry.Trace.Core
+import System.IO.Unsafe (unsafePerformIO)
+
+
+carryOnKey :: Key (H.HashMap Text Attribute)
+carryOnKey = unsafePerformIO $ newKey "carryOn"
+{-# NOINLINE carryOnKey #-}
+
+
+alterCarryOns :: (MonadIO m) => (H.HashMap Text Attribute -> H.HashMap Text Attribute) -> m ()
+alterCarryOns f = adjustContext $ \ctxt ->
+  Context.insert carryOnKey (f $ fromMaybe mempty $ Context.lookup carryOnKey ctxt) ctxt
+
+
+{- |
+"Carry ons" are extra attributes that are added to every span that is completed for within a thread's context.
+This helps us propagate attributes across a trace without having to manually add them to every span.
+
+Be cautious about adding too many additional attributes via carry ons. The attributes are added to every span,
+and will be discarded if the span has attributes that exceed the configured attribute limits for the configured
+'TracerProvider'.
+-}
+withCarryOnProcessor :: Processor -> Processor
+withCarryOnProcessor p =
+  Processor
+    { processorOnStart = processorOnStart p
+    , processorOnEnd = \spanRef -> do
+        ctxt <- getContext
+        let carryOns = fromMaybe mempty $ Context.lookup carryOnKey ctxt
+        if H.null carryOns
+          then pure ()
+          else do
+            -- I doubt we need atomicity at this point. Hopefully people aren't trying to modify the same span after it has ended from multiple threads.
+            modifyIORef' spanRef $ \is ->
+              is
+                { spanAttributes =
+                    OpenTelemetry.Attributes.addAttributes
+                      (tracerProviderAttributeLimits $ tracerProvider $ spanTracer is)
+                      (spanAttributes is)
+                      carryOns
+                }
+        processorOnEnd p spanRef
+    , processorShutdown = processorShutdown p
+    , processorForceFlush = processorForceFlush p
+    }
diff --git a/src/OpenTelemetry/Contrib/SpanTraversals.hs b/src/OpenTelemetry/Contrib/SpanTraversals.hs
new file mode 100644
--- /dev/null
+++ b/src/OpenTelemetry/Contrib/SpanTraversals.hs
@@ -0,0 +1,28 @@
+module OpenTelemetry.Contrib.SpanTraversals (
+  alterSpansUpwards,
+  IterationInstruction (..),
+) where
+
+import Control.Monad.IO.Class
+import Data.IORef
+import OpenTelemetry.Internal.Trace.Types
+
+
+data IterationInstruction a = Continue a | Halt
+
+
+{- | Alter traces upwards from the provides span to the highest available mutable span. Only mutable spans may be altered.
+
+ The step value indicates whether the desired topmost span has been reached or not. This function will continue to iterate
+ upwards until either a span that cannot be mutated has been reached, or there are no more parent spans remaining.
+-}
+alterSpansUpwards :: (MonadIO m) => Span -> st -> (st -> ImmutableSpan -> (IterationInstruction st, ImmutableSpan)) -> m st
+alterSpansUpwards (Span immutableSpanRef) st f = liftIO $ do
+  (step, a') <- atomicModifyIORef' immutableSpanRef (\a -> let (step, a') = f st a in (a', (step, a')))
+  case step of
+    Continue st' -> case spanParent a' of
+      Nothing -> return st'
+      Just s -> alterSpansUpwards s st' f
+    Halt -> return st
+alterSpansUpwards (FrozenSpan _) st _ = return st
+alterSpansUpwards (Dropped _) st _ = return st
diff --git a/src/OpenTelemetry/Internal/Trace/Id.hs b/src/OpenTelemetry/Internal/Trace/Id.hs
--- a/src/OpenTelemetry/Internal/Trace/Id.hs
+++ b/src/OpenTelemetry/Internal/Trace/Id.hs
@@ -53,9 +53,12 @@
   isTrue#,
   or#,
  )
+
+
 #if MIN_VERSION_base(4,17,0)
 import GHC.Exts (word64ToWord#)
 #endif
+
 import GHC.Generics (Generic)
 import OpenTelemetry.Trace.Id.Generator (
   IdGenerator (generateSpanIdBytes, generateTraceIdBytes),
@@ -105,7 +108,7 @@
 
  @since 0.1.0.0
 -}
-newTraceId :: MonadIO m => IdGenerator -> m TraceId
+newTraceId :: (MonadIO m) => IdGenerator -> m TraceId
 newTraceId gen = liftIO (TraceId . toShort <$> generateTraceIdBytes gen)
 
 
@@ -195,7 +198,7 @@
 
  @since 0.1.0.0
 -}
-newSpanId :: MonadIO m => IdGenerator -> m SpanId
+newSpanId :: (MonadIO m) => IdGenerator -> m SpanId
 newSpanId gen = liftIO (SpanId . toShort <$> generateSpanIdBytes gen)
 
 
diff --git a/src/OpenTelemetry/Internal/Trace/Types.hs b/src/OpenTelemetry/Internal/Trace/Types.hs
--- a/src/OpenTelemetry/Internal/Trace/Types.hs
+++ b/src/OpenTelemetry/Internal/Trace/Types.hs
@@ -12,6 +12,7 @@
 import Control.Monad.IO.Class
 import Data.Bits
 import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as H
 import Data.Hashable (Hashable)
 import Data.IORef (IORef, readIORef)
 import Data.String (IsString (..))
@@ -179,7 +180,7 @@
 data NewLink = NewLink
   { linkContext :: !SpanContext
   -- ^ @SpanContext@ of the @Span@ to link to.
-  , linkAttributes :: [(Text, Attribute)]
+  , linkAttributes :: H.HashMap Text Attribute
   -- ^ Zero or more Attributes further describing the link.
   }
   deriving (Show)
@@ -220,7 +221,7 @@
   { kind :: SpanKind
   -- ^ The kind of the span. See 'SpanKind's documentation for the semantics
   -- of the various values that may be specified.
-  , attributes :: [(Text, Attribute)]
+  , attributes :: H.HashMap Text Attribute
   -- ^ An initial set of attributes that may be set on initial 'Span' creation.
   -- These attributes are provided to 'Processor's, so they may be useful in some
   -- scenarios where calling `addAttribute` or `addAttributes` is too late.
@@ -467,7 +468,7 @@
 data NewEvent = NewEvent
   { newEventName :: Text
   -- ^ The name of an event. Ideally this should be a relatively unique, but low cardinality value.
-  , newEventAttributes :: [(Text, Attribute)]
+  , newEventAttributes :: H.HashMap Text Attribute
   -- ^ Additional context or metadata related to the event, (stack traces, callsites, etc.).
   , newEventTimestamp :: Maybe Timestamp
   -- ^ The time that the event occurred.
@@ -518,7 +519,7 @@
 data Sampler = Sampler
   { getDescription :: Text
   -- ^ Returns the sampler name or short description with the configuration. This may be displayed on debug pages or in the logs.
-  , shouldSample :: Context -> TraceId -> Text -> SpanArguments -> IO (SamplingResult, [(Text, Attribute)], TraceState)
+  , shouldSample :: Context -> TraceId -> Text -> SpanArguments -> IO (SamplingResult, H.HashMap Text Attribute, TraceState)
   }
 
 
@@ -544,7 +545,7 @@
     Nothing
 
 
-type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
+type Lens s t a b = forall f. (Functor f) => (a -> f b) -> s -> f t
 
 
 type Lens' s a = Lens s s a a
@@ -553,7 +554,7 @@
 {- | When sending tracing information across process boundaries,
  the @SpanContext@ is used to serialize the relevant information.
 -}
-getSpanContext :: MonadIO m => Span -> m SpanContext
+getSpanContext :: (MonadIO m) => Span -> m SpanContext
 getSpanContext (Span s) = liftIO (spanContext <$> readIORef s)
 getSpanContext (FrozenSpan c) = pure c
 getSpanContext (Dropped c) = pure c
diff --git a/src/OpenTelemetry/Propagator.hs b/src/OpenTelemetry/Propagator.hs
--- a/src/OpenTelemetry/Propagator.hs
+++ b/src/OpenTelemetry/Propagator.hs
@@ -63,23 +63,23 @@
 
 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 ->
-  -- | The carrier that holds the propagation fields. For example, an incoming message or HTTP request.
-  i ->
-  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.
-  m context
+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 ->
-  -- | The carrier that holds the propagation fields. For example, an outgoing message or HTTP request.
-  o ->
-  m o
+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
diff --git a/src/OpenTelemetry/Resource.hs b/src/OpenTelemetry/Resource.hs
--- a/src/OpenTelemetry/Resource.hs
+++ b/src/OpenTelemetry/Resource.hs
@@ -80,14 +80,14 @@
 {- | Utility function to convert a required resource attribute
  into the format needed for 'mkResource'.
 -}
-(.=) :: ToAttribute a => Text -> a -> Maybe (Text, Attribute)
+(.=) :: (ToAttribute a) => Text -> a -> Maybe (Text, Attribute)
 k .= v = Just (k, toAttribute v)
 
 
 {- | Utility function to convert an optional resource attribute
  into the format needed for 'mkResource'.
 -}
-(.=?) :: ToAttribute a => Text -> Maybe a -> Maybe (Text, Attribute)
+(.=?) :: (ToAttribute a) => Text -> Maybe a -> Maybe (Text, Attribute)
 k .=? mv = (\k' v -> (k', toAttribute v)) k <$> mv
 
 
@@ -135,12 +135,12 @@
 
  @since 0.0.1.0
 -}
-mergeResources ::
-  -- | the old resource
-  Resource old ->
-  -- | the updating resource whose attributes take precedence
-  Resource new ->
-  Resource (ResourceMerge old new)
+mergeResources
+  :: Resource old
+  -- ^ the old resource
+  -> Resource new
+  -- ^ the updating resource whose attributes take precedence
+  -> Resource (ResourceMerge old new)
 mergeResources (Resource l) (Resource r) = Resource (unsafeMergeAttributesIgnoringLimits l r)
 
 
@@ -167,7 +167,7 @@
   materializeResources (Resource attrs) = MaterializedResources Nothing attrs
 
 
-instance KnownSymbol s => MaterializeResource ('Just s) where
+instance (KnownSymbol s) => MaterializeResource ('Just s) where
   materializeResources (Resource attrs) = MaterializedResources (Just $ symbolVal (Proxy @s)) attrs
 
 
diff --git a/src/OpenTelemetry/Trace/Core.hs b/src/OpenTelemetry/Trace/Core.hs
--- a/src/OpenTelemetry/Trace/Core.hs
+++ b/src/OpenTelemetry/Trace/Core.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE NumericUnderscores #-}
+{-# LANGUAGE OverloadedLists #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
@@ -130,6 +131,9 @@
   timestampNanoseconds,
   unsafeReadSpan,
   whenSpanIsRecording,
+  ownCodeAttributes,
+  callerAttributes,
+  addAttributesToSpanArguments,
 
   -- * Limits
   SpanLimits (..),
@@ -145,6 +149,7 @@
 import Control.Monad.IO.Class
 import Control.Monad.IO.Unlift
 import Data.Coerce
+import qualified Data.HashMap.Strict as H
 import Data.IORef
 import Data.Maybe (fromMaybe, isJust, isNothing)
 import Data.Text (Text)
@@ -185,53 +190,38 @@
 
  @since 0.0.1.0
 -}
-createSpan ::
-  (MonadIO m, HasCallStack) =>
-  -- | 'Tracer' to create the span from. Associated 'Processor's and 'Exporter's will be
+createSpan
+  :: (MonadIO m, HasCallStack)
+  => Tracer
+  -- ^ 'Tracer' to create the span from. Associated 'Processor's and 'Exporter's will be
   -- used for the lifecycle of the created 'Span'
-  Tracer ->
-  -- | Context, potentially containing a parent span. If no existing parent (or context) exists,
+  -> Context
+  -- ^ Context, potentially containing a parent span. If no existing parent (or context) exists,
   -- you can use 'OpenTelemetry.Context.empty'.
-  Context ->
-  -- | Span name
-  Text ->
-  -- | Additional span information
-  SpanArguments ->
-  -- | The created span.
-  m Span
-createSpan t c n args = do
-  createSpanWithoutCallStack t c n $ case getCallStack callStack of
-    [] -> args
-    (_, loc) : rest ->
-      let addFunction = case rest of
-            (fn, _) : _ -> (("code.function", toAttribute $ T.pack fn) :)
-            [] -> id
-       in args
-            { attributes =
-                addFunction $
-                  ("code.namespace", toAttribute $ T.pack $ srcLocModule loc)
-                    : ("code.filepath", toAttribute $ T.pack $ srcLocFile loc)
-                    : ("code.lineno", toAttribute $ srcLocStartLine loc)
-                    : ("code.package", toAttribute $ T.pack $ srcLocPackage loc)
-                    : attributes args
-            }
+  -> Text
+  -- ^ Span name
+  -> SpanArguments
+  -- ^ Additional span information
+  -> m Span
+  -- ^ The created span.
+createSpan t ctxt n args = createSpanWithoutCallStack t ctxt n (args {attributes = H.union (attributes args) callerAttributes})
 
 
 -- | The same thing as 'createSpan', except that it does not have a 'HasCallStack' constraint.
-createSpanWithoutCallStack ::
-  MonadIO m =>
-  -- | 'Tracer' to create the span from. Associated 'Processor's and 'Exporter's will be
+createSpanWithoutCallStack
+  :: (MonadIO m)
+  => Tracer
+  -- ^ 'Tracer' to create the span from. Associated 'Processor's and 'Exporter's will be
   -- used for the lifecycle of the created 'Span'
-  Tracer ->
-  -- | Context, potentially containing a parent span. If no existing parent (or context) exists,
+  -> Context
+  -- ^ Context, potentially containing a parent span. If no existing parent (or context) exists,
   -- you can use 'OpenTelemetry.Context.empty'.
-  Context ->
-  -- | Span name
-  Text ->
-  -- | Additional span information
-  SpanArguments ->
-  -- | The created span.
-  m Span
+  -> Text
+  -- ^ Span name
+  -> SpanArguments
+  -- ^ Additional span information
+  -> m Span
+  -- ^ The created span.
 createSpanWithoutCallStack t ctxt n args@SpanArguments {..} = liftIO $ do
   sId <- newSpanId $ tracerProviderIdGenerator $ tracerProvider t
   let parent = lookupSpan ctxt
@@ -283,7 +273,7 @@
                         A.addAttributes
                           (limitBy t spanAttributeCountLimit)
                           emptyAttributes
-                          (concat [additionalInfo, attrs, attributes])
+                          (H.unions [additionalInfo, attrs, attributes])
                     , spanLinks =
                         let limitedLinks = fromMaybe 128 (linkCountLimit $ tracerProviderSpanLimits $ tracerProvider t)
                          in frozenBoundedCollection limitedLinks $ fmap freezeLink links
@@ -313,72 +303,86 @@
         }
 
 
+ownCodeAttributes :: (HasCallStack) => H.HashMap Text Attribute
+ownCodeAttributes = case getCallStack callStack of
+  _ : caller : _ -> srcAttributes caller
+  _ -> mempty
+
+
+callerAttributes :: (HasCallStack) => H.HashMap Text Attribute
+callerAttributes = case getCallStack callStack of
+  _ : _ : caller : _ -> srcAttributes caller
+  _ -> mempty
+
+
+srcAttributes :: (String, SrcLoc) -> H.HashMap Text Attribute
+srcAttributes (fn, loc) = H.fromList
+  [ ("code.function", toAttribute $ T.pack fn)
+  , ("code.namespace", toAttribute $ T.pack $ srcLocModule loc)
+  , ("code.filepath", toAttribute $ T.pack $ srcLocFile loc)
+  , ("code.lineno", toAttribute $ srcLocStartLine loc)
+  , ("code.package", toAttribute $ T.pack $ srcLocPackage loc)
+  ]
+
+
+{- | Attributes are added to the end of the span argument list, so will be discarded
+ if the number of attributes in the span exceeds the limit.
+-}
+addAttributesToSpanArguments :: H.HashMap Text Attribute -> SpanArguments -> SpanArguments
+addAttributesToSpanArguments attrs args = args {attributes = H.union (attributes args) attrs}
+
+
 {- | The simplest function for annotating code with trace information.
 
  @since 0.0.1.0
 -}
-inSpan ::
-  (MonadUnliftIO m, HasCallStack) =>
-  Tracer ->
-  -- | The name of the span. This may be updated later via 'updateName'
-  Text ->
-  -- | Additional options for creating the span, such as 'SpanKind',
+inSpan
+  :: (MonadUnliftIO m, HasCallStack)
+  => Tracer
+  -> Text
+  -- ^ The name of the span. This may be updated later via 'updateName'
+  -> SpanArguments
+  -- ^ Additional options for creating the span, such as 'SpanKind',
   -- span links, starting attributes, etc.
-  SpanArguments ->
-  -- | The action to perform. 'inSpan' will record the time spent on the
+  -> m a
+  -- ^ The action to perform. 'inSpan' will record the time spent on the
   -- action without forcing strict evaluation of the result. Any uncaught
   -- exceptions will be recorded and rethrown.
-  m a ->
-  m a
-inSpan t n args m = inSpan'' t callStack n args (const m)
+  -> m a
+inSpan t n args m = inSpan'' t n (args {attributes = H.union (attributes args) callerAttributes}) (const m)
 
 
-inSpan' ::
-  (MonadUnliftIO m, HasCallStack) =>
-  Tracer ->
-  -- | The name of the span. This may be updated later via 'updateName'
-  Text ->
-  SpanArguments ->
-  (Span -> m a) ->
-  m a
-inSpan' t = inSpan'' t callStack
+inSpan'
+  :: (MonadUnliftIO m, HasCallStack)
+  => Tracer
+  -> Text
+  -- ^ The name of the span. This may be updated later via 'updateName'
+  -> SpanArguments
+  -> (Span -> m a)
+  -> m a
+inSpan' t n args = inSpan'' t n (args {attributes = H.union (attributes args) callerAttributes})
 
 
-inSpan'' ::
-  (MonadUnliftIO m, HasCallStack) =>
-  Tracer ->
-  -- | Record the location of the span in the codebase using the provided
-  -- callstack for source location info.
-  CallStack ->
-  -- | The name of the span. This may be updated later via 'updateName'
-  Text ->
-  SpanArguments ->
-  (Span -> m a) ->
-  m a
-inSpan'' t cs n args f = do
+inSpan''
+  :: (MonadUnliftIO m, HasCallStack)
+  => Tracer
+  -> Text
+  -- ^ The name of the span. This may be updated later via 'updateName'
+  -> SpanArguments
+  -> (Span -> m a)
+  -> m a
+inSpan'' t n args f = do
   bracketError
     ( liftIO $ do
         ctx <- getContext
         s <- createSpanWithoutCallStack t ctx n args
         adjustContext (insertSpan s)
-        whenSpanIsRecording s $ do
-          case getCallStack cs of
-            [] -> pure ()
-            (fn, loc) : _ -> do
-              OpenTelemetry.Trace.Core.addAttributes
-                s
-                [ ("code.function", toAttribute $ T.pack fn)
-                , ("code.namespace", toAttribute $ T.pack $ srcLocModule loc)
-                , ("code.filepath", toAttribute $ T.pack $ srcLocFile loc)
-                , ("code.lineno", toAttribute $ srcLocStartLine loc)
-                , ("code.package", toAttribute $ T.pack $ srcLocPackage loc)
-                ]
         pure (lookupSpan ctx, s)
     )
     ( \e (parent, s) -> liftIO $ do
         forM_ e $ \(SomeException inner) -> do
           setStatus s $ Error $ T.pack $ displayException inner
-          recordException s [] Nothing inner
+          recordException s [("exception.escaped", toAttribute True)] Nothing inner
         endSpan s Nothing
         adjustContext $ \ctx ->
           maybe (removeSpan ctx) (`insertSpan` ctx) parent
@@ -392,7 +396,7 @@
  created by this process, the span will return True until endSpan
  is called.
 -}
-isRecording :: MonadIO m => Span -> m Bool
+isRecording :: (MonadIO m) => Span -> m Bool
 isRecording (Span s) = liftIO (isNothing . spanEnd <$> readIORef s)
 isRecording (FrozenSpan _) = pure True
 isRecording (Dropped _) = pure False
@@ -418,15 +422,15 @@
 
 @since 0.0.1.0
 -}
-addAttribute ::
-  (MonadIO m, A.ToAttribute a) =>
-  -- | Span to add the attribute to
-  Span ->
-  -- | Attribute name
-  Text ->
-  -- | Attribute value
-  a ->
-  m ()
+addAttribute
+  :: (MonadIO m, A.ToAttribute a)
+  => Span
+  -- ^ Span to add the attribute to
+  -> Text
+  -- ^ Attribute name
+  -> a
+  -- ^ Attribute value
+  -> m ()
 addAttribute (Span s) k v = liftIO $ modifyIORef' s $ \(!i) ->
   i
     { spanAttributes =
@@ -446,7 +450,7 @@
 
  @since 0.0.1.0
 -}
-addAttributes :: MonadIO m => Span -> [(Text, A.Attribute)] -> m ()
+addAttributes :: (MonadIO m) => Span -> H.HashMap Text A.Attribute -> m ()
 addAttributes (Span s) attrs = liftIO $ modifyIORef' s $ \(!i) ->
   i
     { spanAttributes =
@@ -463,7 +467,7 @@
 
  @since 0.0.1.0
 -}
-addEvent :: MonadIO m => Span -> NewEvent -> m ()
+addEvent :: (MonadIO m) => Span -> NewEvent -> m ()
 addEvent (Span s) NewEvent {..} = liftIO $ do
   t <- maybe getTimestamp pure newEventTimestamp
   modifyIORef' s $ \(!i) ->
@@ -490,18 +494,27 @@
 
  @since 0.0.1.0
 -}
-setStatus :: MonadIO m => Span -> SpanStatus -> m ()
+setStatus :: (MonadIO m) => Span -> SpanStatus -> m ()
 setStatus (Span s) st = liftIO $ modifyIORef' s $ \(!i) ->
   i
-    { spanStatus =
-        if st > spanStatus i
-          then st
-          else spanStatus i
+    { spanStatus = max st (spanStatus i)
     }
 setStatus (FrozenSpan _) _ = pure ()
 setStatus (Dropped _) _ = pure ()
 
 
+alterFlags :: (MonadIO m) => Span -> (TraceFlags -> TraceFlags) -> m ()
+alterFlags (Span s) f = liftIO $ modifyIORef' s $ \(!i) ->
+  i
+    { spanContext =
+        (spanContext i)
+          { traceFlags = f $ traceFlags $ spanContext i
+          }
+    }
+alterFlags (FrozenSpan _) _ = pure ()
+alterFlags (Dropped _) _ = pure ()
+
+
 {- |
 Updates the Span name. Upon this update, any sampling behavior based on Span name will depend on the implementation.
 
@@ -511,12 +524,12 @@
 
 @since 0.0.1.0
 -}
-updateName ::
-  MonadIO m =>
-  Span ->
-  -- | The new span name, which supersedes whatever was passed in when the Span was started
-  Text ->
-  m ()
+updateName
+  :: (MonadIO m)
+  => Span
+  -> Text
+  -- ^ The new span name, which supersedes whatever was passed in when the Span was started
+  -> m ()
 updateName (Span s) n = liftIO $ modifyIORef' s $ \(!i) -> i {spanName = n}
 updateName (FrozenSpan _) _ = pure ()
 updateName (Dropped _) _ = pure ()
@@ -532,12 +545,12 @@
 
 @since 0.0.1.0
 -}
-endSpan ::
-  MonadIO m =>
-  Span ->
-  -- | Optional @Timestamp@ signalling the end time of the span. If not provided, the current time will be used.
-  Maybe Timestamp ->
-  m ()
+endSpan
+  :: (MonadIO m)
+  => Span
+  -> Maybe Timestamp
+  -- ^ Optional @Timestamp@ signalling the end time of the span. If not provided, the current time will be used.
+  -> m ()
 endSpan (Span s) mts = liftIO $ do
   ts <- maybe getTimestamp pure mts
   (alreadyFinished, frozenS) <- atomicModifyIORef' s $ \(!i) ->
@@ -558,7 +571,7 @@
 
  @since 0.0.1.0
 -}
-recordException :: (MonadIO m, Exception e) => Span -> [(Text, Attribute)] -> Maybe Timestamp -> e -> m ()
+recordException :: (MonadIO m, Exception e) => Span -> H.HashMap Text Attribute -> Maybe Timestamp -> e -> m ()
 recordException s attrs ts e = liftIO $ do
   cs <- whoCreated e
   let message = T.pack $ show e
@@ -566,11 +579,12 @@
     NewEvent
       { newEventName = "exception"
       , newEventAttributes =
-          attrs
-            ++ [ ("exception.type", A.toAttribute $ T.pack $ show $ typeOf e)
-               , ("exception.message", A.toAttribute message)
-               , ("exception.stacktrace", A.toAttribute $ T.unlines $ map T.pack cs)
-               ]
+          H.union
+            attrs
+            [ ("exception.type", A.toAttribute $ T.pack $ show $ typeOf e)
+            , ("exception.message", A.toAttribute message)
+            , ("exception.stacktrace", A.toAttribute $ T.unlines $ map T.pack cs)
+            ]
       , newEventTimestamp = ts
       }
 
@@ -588,7 +602,7 @@
 When extracting a SpanContext through the Propagators API, isRemote MUST return @True@,
 whereas for the SpanContext of any child spans it MUST return @False@.
 -}
-spanIsRemote :: MonadIO m => Span -> m Bool
+spanIsRemote :: (MonadIO m) => Span -> m Bool
 spanIsRemote (Span s) = liftIO $ do
   i <- readIORef s
   pure $ Types.isRemote $ Types.spanContext i
@@ -599,7 +613,7 @@
 {- | Really only intended for tests, this function does not conform
  to semantic versioning .
 -}
-unsafeReadSpan :: MonadIO m => Span -> m ImmutableSpan
+unsafeReadSpan :: (MonadIO m) => Span -> m ImmutableSpan
 unsafeReadSpan = \case
   Span ref -> liftIO $ readIORef ref
   FrozenSpan _s -> error "This span is from another process"
@@ -614,7 +628,7 @@
  using it to copy / otherwise use the data to further enrich
  instrumentation.
 -}
-spanGetAttributes :: MonadIO m => Span -> m A.Attributes
+spanGetAttributes :: (MonadIO m) => Span -> m A.Attributes
 spanGetAttributes = \case
   Span ref -> do
     s <- liftIO $ readIORef ref
@@ -632,15 +646,15 @@
 
  @since 0.0.1.0
 -}
-getTimestamp :: MonadIO m => m Timestamp
+getTimestamp :: (MonadIO m) => m Timestamp
 getTimestamp = liftIO $ coerce @(IO TimeSpec) @(IO Timestamp) $ getTime Realtime
 
 
-limitBy ::
-  Tracer ->
-  -- | Attribute count
-  (SpanLimits -> Maybe Int) ->
-  AttributeLimits
+limitBy
+  :: Tracer
+  -> (SpanLimits -> Maybe Int)
+  -- ^ Attribute count
+  -> AttributeLimits
 limitBy t countF =
   AttributeLimits
     { attributeCountLimit = countLimit
@@ -700,7 +714,7 @@
 
  You should generally use 'getGlobalTracerProvider' for most applications.
 -}
-createTracerProvider :: MonadIO m => [Processor] -> TracerProviderOptions -> m TracerProvider
+createTracerProvider :: (MonadIO m) => [Processor] -> TracerProviderOptions -> m TracerProvider
 createTracerProvider ps opts = liftIO $ do
   let g = tracerProviderOptionsIdGenerator opts
   pure $
@@ -723,7 +737,7 @@
 
  @since 0.0.1.0
 -}
-getGlobalTracerProvider :: MonadIO m => m TracerProvider
+getGlobalTracerProvider :: (MonadIO m) => m TracerProvider
 getGlobalTracerProvider = liftIO $ readIORef globalTracer
 
 
@@ -735,7 +749,7 @@
 
  @since 0.0.1.0
 -}
-setGlobalTracerProvider :: MonadIO m => TracerProvider -> m ()
+setGlobalTracerProvider :: (MonadIO m) => TracerProvider -> m ()
 setGlobalTracerProvider = liftIO . writeIORef globalTracer
 
 
@@ -777,7 +791,7 @@
 makeTracer tp n TracerOptions {} = Tracer n tp
 
 
-getTracer :: MonadIO m => TracerProvider -> InstrumentationLibrary -> TracerOptions -> m Tracer
+getTracer :: (MonadIO m) => TracerProvider -> InstrumentationLibrary -> TracerOptions -> m Tracer
 getTracer tp n TracerOptions {} = liftIO $ do
   pure $ Tracer n tp
 {-# DEPRECATED getTracer "use makeTracer" #-}
@@ -817,7 +831,7 @@
 
  @since 0.0.1.0
 -}
-shutdownTracerProvider :: MonadIO m => TracerProvider -> m ()
+shutdownTracerProvider :: (MonadIO m) => TracerProvider -> m ()
 shutdownTracerProvider TracerProvider {..} = liftIO $ do
   asyncShutdownResults <- forM tracerProviderProcessors $ \processor -> do
     processorShutdown processor
@@ -827,13 +841,13 @@
 {- | This method provides a way for provider to immediately export all spans that have not yet
  been exported for all the internal processors.
 -}
-forceFlushTracerProvider ::
-  MonadIO m =>
-  TracerProvider ->
-  -- | Optional timeout in microseconds, defaults to 5,000,000 (5s)
-  Maybe Int ->
-  -- | Result that denotes whether the flush action succeeded, failed, or timed out.
-  m FlushResult
+forceFlushTracerProvider
+  :: (MonadIO m)
+  => TracerProvider
+  -> Maybe Int
+  -- ^ Optional timeout in microseconds, defaults to 5,000,000 (5s)
+  -> m FlushResult
+  -- ^ Result that denotes whether the flush action succeeded, failed, or timed out.
 forceFlushTracerProvider TracerProvider {..} mtimeout = liftIO $ do
   jobs <- forM tracerProviderProcessors $ \processor -> async $ do
     processorForceFlush processor
@@ -856,7 +870,7 @@
 {- | Utility function to only perform costly attribute annotations
  for spans that are actually
 -}
-whenSpanIsRecording :: MonadIO m => Span -> m () -> m ()
+whenSpanIsRecording :: (MonadIO m) => Span -> m () -> m ()
 whenSpanIsRecording (Span ref) m = do
   span_ <- liftIO $ readIORef ref
   case spanEnd span_ of
diff --git a/src/OpenTelemetry/Trace/Monad.hs b/src/OpenTelemetry/Trace/Monad.hs
--- a/src/OpenTelemetry/Trace/Monad.hs
+++ b/src/OpenTelemetry/Trace/Monad.hs
@@ -46,48 +46,49 @@
   Span,
   SpanArguments (..),
   Tracer,
+  addAttributesToSpanArguments,
+  callerAttributes,
   inSpan'',
  )
 
 
 -- | This is generally scoped by Monad stack to do different things
-class Monad m => MonadTracer m where
+class (Monad m) => MonadTracer m where
   getTracer :: m Tracer
 
 
-inSpan ::
-  (MonadUnliftIO m, MonadTracer m, HasCallStack) =>
-  Text ->
-  SpanArguments ->
-  m a ->
-  m a
-inSpan n args m = OpenTelemetry.Trace.Monad.inSpan'' callStack n args (const m)
+inSpan
+  :: (MonadUnliftIO m, MonadTracer m, HasCallStack)
+  => Text
+  -> SpanArguments
+  -> m a
+  -> m a
+inSpan n args m = OpenTelemetry.Trace.Monad.inSpan'' n (addAttributesToSpanArguments callerAttributes args) (const m)
 
 
-inSpan' ::
-  (MonadUnliftIO m, MonadTracer m, HasCallStack) =>
-  Text ->
-  SpanArguments ->
-  (Span -> m a) ->
-  m a
-inSpan' = OpenTelemetry.Trace.Monad.inSpan'' callStack
+inSpan'
+  :: (MonadUnliftIO m, MonadTracer m, HasCallStack)
+  => Text
+  -> SpanArguments
+  -> (Span -> m a)
+  -> m a
+inSpan' n args f = OpenTelemetry.Trace.Monad.inSpan'' n (addAttributesToSpanArguments callerAttributes args) f
 
 
-inSpan'' ::
-  (MonadUnliftIO m, MonadTracer m, HasCallStack) =>
-  CallStack ->
-  Text ->
-  SpanArguments ->
-  (Span -> m a) ->
-  m a
-inSpan'' cs n args f = do
+inSpan''
+  :: (MonadUnliftIO m, MonadTracer m, HasCallStack)
+  => Text
+  -> SpanArguments
+  -> (Span -> m a)
+  -> m a
+inSpan'' n args f = do
   t <- getTracer
-  OpenTelemetry.Trace.Core.inSpan'' t cs n args f
+  OpenTelemetry.Trace.Core.inSpan'' t n args f
 
 
-instance MonadTracer m => MonadTracer (IdentityT m) where
+instance (MonadTracer m) => MonadTracer (IdentityT m) where
   getTracer = lift getTracer
 
 
-instance {-# OVERLAPPABLE #-} MonadTracer m => MonadTracer (ReaderT r m) where
+instance {-# OVERLAPPABLE #-} (MonadTracer m) => MonadTracer (ReaderT r m) where
   getTracer = lift getTracer
diff --git a/src/OpenTelemetry/Trace/Sampler.hs b/src/OpenTelemetry/Trace/Sampler.hs
--- a/src/OpenTelemetry/Trace/Sampler.hs
+++ b/src/OpenTelemetry/Trace/Sampler.hs
@@ -1,6 +1,4 @@
------------------------------------------------------------------------------
-
------------------------------------------------------------------------------
+{-# LANGUAGE OverloadedLists #-}
 
 {- |
  Module      :  OpenTelemetry.Trace.Sampler
@@ -146,10 +144,10 @@
 
  @since 0.1.0.0
 -}
-parentBasedOptions ::
-  -- | Root sampler
-  Sampler ->
-  ParentBasedOptions
+parentBasedOptions
+  :: Sampler
+  -- ^ Root sampler
+  -> ParentBasedOptions
 parentBasedOptions root =
   ParentBasedOptions
     { rootSampler = root
diff --git a/src/OpenTelemetry/Util.hs b/src/OpenTelemetry/Util.hs
--- a/src/OpenTelemetry/Util.hs
+++ b/src/OpenTelemetry/Util.hs
@@ -51,11 +51,11 @@
 import Foreign.C (CInt (..))
 import GHC.Base (Addr#)
 import GHC.Conc (ThreadId (ThreadId))
+import GHC.Exts (unsafeCoerce#)
 import GHC.Generics
 import VectorBuilder.Builder (Builder)
 import qualified VectorBuilder.Builder as Builder
 import qualified VectorBuilder.Vector as Builder
-import Unsafe.Coerce (unsafeCoerce#)
 
 
 {- | Useful for annotating which constructor in an ADT was chosen
@@ -71,7 +71,7 @@
   genericConstrName :: f x -> String
 
 
-instance HasConstructor f => HasConstructor (D1 c f) where
+instance (HasConstructor f) => HasConstructor (D1 c f) where
   genericConstrName (M1 x) = genericConstrName x
 
 
@@ -80,11 +80,13 @@
   genericConstrName (R1 r) = genericConstrName r
 
 
-instance Constructor c => HasConstructor (C1 c f) where
+instance (Constructor c) => HasConstructor (C1 c f) where
   genericConstrName = conName
 
+
 foreign import ccall unsafe "rts_getThreadId" c_getThreadId :: Addr# -> CInt
 
+
 -- | Get an int representation of a thread id
 getThreadId :: ThreadId -> Int
 getThreadId (ThreadId tid#) = fromIntegral $ c_getThreadId (unsafeCoerce# tid#)
@@ -98,7 +100,7 @@
   }
 
 
-instance forall a. Show a => Show (AppendOnlyBoundedCollection a) where
+instance forall a. (Show a) => Show (AppendOnlyBoundedCollection a) where
   showsPrec d AppendOnlyBoundedCollection {collection = c, maxSize = m, dropped = r} =
     let vec = Builder.build c :: V.Vector a
      in showParen (d > 10) $
@@ -112,10 +114,10 @@
 
 
 -- | Initialize a bounded collection that admits a maximum size
-emptyAppendOnlyBoundedCollection ::
-  -- | Maximum size
-  Int ->
-  AppendOnlyBoundedCollection a
+emptyAppendOnlyBoundedCollection
+  :: Int
+  -- ^ Maximum size
+  -> AppendOnlyBoundedCollection a
 emptyAppendOnlyBoundedCollection s = AppendOnlyBoundedCollection mempty s 0
 
 
@@ -145,7 +147,7 @@
   deriving (Show)
 
 
-frozenBoundedCollection :: Foldable f => Int -> f a -> FrozenBoundedCollection a
+frozenBoundedCollection :: (Foldable f) => Int -> f a -> FrozenBoundedCollection a
 frozenBoundedCollection maxSize_ coll = FrozenBoundedCollection (V.fromListN maxSize_ $ toList coll) (collLength - maxSize_)
   where
     collLength = length coll
@@ -164,7 +166,7 @@
 
  @since 0.1.0.0
 -}
-bracketError :: MonadUnliftIO m => m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
+bracketError :: (MonadUnliftIO m) => m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
 bracketError before after thing = withRunInIO $ \run -> EUnsafe.mask $ \restore -> do
   x <- run before
   res1 <- EUnsafe.try $ restore $ run $ thing x
diff --git a/test/OpenTelemetry/BaggageSpec.hs b/test/OpenTelemetry/BaggageSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/OpenTelemetry/BaggageSpec.hs
@@ -0,0 +1,15 @@
+module OpenTelemetry.BaggageSpec where
+
+import qualified Data.HashMap.Strict as HashMap
+import OpenTelemetry.Baggage
+import Test.Hspec
+
+
+spec :: Spec
+spec = describe "Baggage" $ do
+  it "decodes simple header" $ do
+    let baggage = values <$> decodeBaggageHeader "x-api-key=asdf"
+    HashMap.mapKeys tokenValue <$> baggage `shouldBe` Right (HashMap.fromList [("x-api-key", Element {value = "asdf", properties = []})])
+  it "decodes percent encoded header" $ do
+    let baggage = values <$> decodeBaggageHeader "Authorization=Basic%20asdf"
+    HashMap.mapKeys tokenValue <$> baggage `shouldBe` Right (HashMap.fromList [("Authorization", Element {value = "Basic asdf", properties = []})])
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -9,10 +9,11 @@
 import Data.Maybe (isJust)
 import qualified Data.Vector as V
 import OpenTelemetry.Attributes (lookupAttribute)
-import OpenTelemetry.Context
-import OpenTelemetry.Trace.Core
 -- Specs
 
+import qualified OpenTelemetry.BaggageSpec as Baggage
+import OpenTelemetry.Context
+import OpenTelemetry.Trace.Core
 import qualified OpenTelemetry.Trace.SamplerSpec as Sampler
 import qualified OpenTelemetry.Trace.TraceFlagsSpec as TraceFlags
 import OpenTelemetry.Util
@@ -50,5 +51,6 @@
   -- describe "inSpan" $ do
   --   it "records exceptions" $ do
   --     exceptionTest
+  Baggage.spec
   Sampler.spec
   TraceFlags.spec
