hs-opentelemetry-api
Copyright(c) Ian Duncan 2021-2026
LicenseBSD-3
Stabilityexperimental
Safe HaskellNone
LanguageHaskell2010

OpenTelemetry.Context.ThreadLocal

Description

Overview

Manages the implicit Context that flows through your application. The inSpan functions automatically push/pop context here, so you rarely need to use this module directly.

Common operations

import OpenTelemetry.Context.ThreadLocal
import OpenTelemetry.Context (insertBaggage, lookupSpan)

-- Read the current context (contains active span + baggage):
ctx <- getContext

-- Modify the current context (e.g. attach baggage):
adjustContext (insertBaggage myBaggage)

-- Attach context extracted from incoming headers:
tok <- attachContext extractedCtx
-- ... later ...
detachContext tok

Token semantics

Per the OpenTelemetry specification, attachContext returns an opaque Token that must be passed to detachContext to restore the previous context. Tokens enforce LIFO ordering: if you attach contexts A then B, you must detach B before A. Detaching out of order logs a diagnostic error.

When to use this module

  • Reading the current span: lookupSpan <$> getContext
  • Setting/reading baggage on the current thread
  • Manually attaching context after propagator extraction
  • Implementing custom instrumentation middleware
Synopsis

Thread-local context

Opaque token (spec-mandated attach/detach handle)

data Token Source #

Opaque token returned by attachContext.

Pass this to detachContext to restore the context that was active before the attach. The implementation validates LIFO ordering: detaching out of order (i.e. with a token that doesn't match the most recent attach) logs a diagnostic error per the OpenTelemetry specification.

Tokens are lightweight (three words) and do not hold resources that need explicit cleanup. If a token is simply dropped without calling detachContext, nothing leaks — but you lose the LIFO validation for that attach point.

Since: 0.5.0.0

getContext :: MonadIO m => m Context Source #

Retrieve a stored Context for the current thread, or an empty context if none exists.

Warning: this can easily cause disconnected traces if libraries don't explicitly set the context on forked threads.

Since: 0.0.1.0

lookupContext :: MonadIO m => m (Maybe Context) Source #

Retrieve a stored Context for the current thread, if it exists.

Since: 0.0.1.0

attachContext :: MonadIO m => Context -> m Token Source #

Attach a Context to the current thread, returning an opaque Token.

Pass the token to detachContext to restore the previous context. Tokens enforce LIFO ordering per the OpenTelemetry specification.

Since: 0.5.0.0

detachContext :: MonadIO m => Token -> m () Source #

Restore the context that was active before the corresponding attachContext call, using the provided Token.

If the token does not match the most recently attached context on this thread (LIFO violation), the implementation logs a diagnostic error per the OpenTelemetry specification. The previous context is still restored regardless.

Since: 0.5.0.0

adjustContext :: MonadIO m => (Context -> Context) -> m () Source #

Alter the context on the current thread using the provided function.

If there is not a context associated with the current thread, the function will be applied to an empty context and the result will be stored.

This does not affect the active token — it modifies the context in place.

Since: 0.0.1.0

getAndAdjustContext :: MonadIO m => (Context -> Context) -> m Context Source #

Atomically read the current context and replace it via f in a single operation, returning the old context.

Since: 0.4.0.0

Active baggage (implicit context)

getActiveBaggage :: MonadIO m => m (Maybe Baggage) Source #

Get the currently active Baggage from the implicit thread-local context.

Since: 0.4.0.0

setActiveBaggage :: MonadIO m => Baggage -> m () Source #

Set the currently active Baggage in the implicit thread-local context.

Since: 0.4.0.0

clearActiveBaggage :: MonadIO m => m () Source #

Clear the active Baggage from the implicit thread-local context.

Since: 0.4.0.0

Fused ref-based operations (zero CAS on hot path)

data ContextEntry Source #

Per-thread context entry: the context plus the active token ID. A token ID of 0 means no token is active (initial state).

ensureContextRef :: ThreadId -> Int -> IO (IORef ContextEntry) Source #

Get or create the per-thread context IORef. On the first call per thread, this inserts a new entry (flat-table CAS, once per thread lifetime). On all subsequent calls the IORef is found via a single flat-table probe (no CAS).

Use readIORef and writeIORef on the returned IORef for zero-overhead context access on the hot path.

Since: 0.5.0.0

ensureContextRefFast :: IO (Int, IORef ContextEntry) Source #

Fused CMM fast path: reads CurrentTSO.id and probes the flat table entirely in CMM, returning the per-thread context IORef. No ThreadId allocation, no FFI call, no Maybe wrapper on the steady-state path.

Returns (threadId, contextRef). The thread ID is returned for reuse as the thread.id span attribute.

First call per thread: falls back to myThreadId + insert (one CAS).

Since: 0.5.0.0

lookupContextRefFast :: IO (Int, Maybe (IORef ContextEntry)) Source #

Fused CMM probe: reads CurrentTSO.id and probes the flat table entirely in CMM. Returns (threadId, Maybe (IORef ContextEntry)). The thread ID is returned for reuse on the slow path.

Since: 0.5.0.0

readContextRef :: IORef ContextEntry -> IO Context Source #

Read just the Context from a per-thread IORef.

For the hot path where you need both the entry and the context, prefer reading the IORef directly and using ceContext.

Since: 0.5.0.0

writeContextRef :: IORef ContextEntry -> Context -> IO () Source #

Write a Context to a per-thread IORef, preserving the active token ID. Only the owning thread should call this.

For the hot path (e.g. inSpanInternal), prefer writing the full ContextEntry via writeIORef to avoid the read-modify-write.

Since: 0.5.0.0

Generalized thread-local context functions

lookupContextOnThread :: MonadIO m => ThreadId -> m (Maybe Context) Source #

Retrieve a stored Context for the provided ThreadId, if it exists.

Since: 0.0.1.0

attachContextOnThread :: MonadIO m => ThreadId -> Context -> m Token Source #

Attach a Context to the provided ThreadId, returning an opaque Token.

Since: 0.5.0.0

detachContextFromThread :: MonadIO m => ThreadId -> Token -> m () Source #

Restore the context on the provided ThreadId using the given Token.

Since: 0.5.0.0

adjustContextOnThread :: MonadIO m => ThreadId -> (Context -> Context) -> m () Source #

Alter the context on the provided thread.

If there is not a context associated with the provided thread, the function will be applied to an empty context and the result will be stored.

Since: 0.0.1.0

Debugging tools

threadContextMap :: ThreadContextMap Source #

This is a global variable that is used to store the thread-local context map. It is not intended to be used directly for production purposes, but is exposed for debugging purposes.