{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module OpenTelemetry.Propagator.Datadog ( datadogTraceContextPropagator, convertOpenTelemetrySpanIdToDatadogSpanId, convertOpenTelemetryTraceIdToDatadogTraceId, ) where import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Short.Internal as SBI import Data.Primitive (ByteArray (ByteArray)) import Data.String (IsString) import qualified Data.Text as T import Data.Word (Word64) import Network.HTTP.Types ( RequestHeaders, ResponseHeaders, ) import OpenTelemetry.Common (TraceFlags (TraceFlags)) import OpenTelemetry.Context ( Context, insertSpan, lookupSpan, ) import OpenTelemetry.Internal.Trace.Id ( SpanId (SpanId), TraceId (TraceId), ) import OpenTelemetry.Propagator (Propagator (Propagator, extractor, injector, propagatorNames)) import OpenTelemetry.Propagator.Datadog.Internal ( indexByteArrayNbo, newHeaderFromSpanId, newHeaderFromTraceId, newSpanIdFromHeader, newTraceIdFromHeader, ) import OpenTelemetry.Trace (SpanContext (SpanContext, isRemote, spanId, traceFlags, traceId, traceState)) import OpenTelemetry.Trace.Core ( getSpanContext, wrapSpanContext, ) import OpenTelemetry.Trace.TraceState (TraceState (TraceState)) import qualified OpenTelemetry.Trace.TraceState as TS -- Reference: bi-directional conversion of IDs of Open Telemetry and ones of Datadog -- - English: https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/opentelemetry/ -- - Japanese: https://docs.datadoghq.com/ja/tracing/connect_logs_and_traces/opentelemetry/ datadogTraceContextPropagator :: Propagator Context RequestHeaders ResponseHeaders datadogTraceContextPropagator = Propagator { propagatorNames = ["datadog trace context"] , extractor = \hs c -> do let spanContext' = do traceId <- TraceId . newTraceIdFromHeader <$> lookup traceIdKey hs parentId <- SpanId . newSpanIdFromHeader <$> lookup parentIdKey hs samplingPriority <- T.pack . BC.unpack <$> lookup samplingPriorityKey hs pure $ SpanContext { traceId , spanId = parentId , isRemote = True , -- when 0, not sampled -- refer: OpenTelemetry.Internal.Trace.Types.isSampled traceFlags = TraceFlags 1 , traceState = TraceState [(TS.Key samplingPriorityKey, TS.Value samplingPriority)] } case spanContext' of Nothing -> pure c Just spanContext -> pure $ insertSpan (wrapSpanContext spanContext) c , injector = \c hs -> case lookupSpan c of Nothing -> pure hs Just span' -> do SpanContext {traceId, spanId, traceState = TraceState traceState} <- getSpanContext span' let traceIdValue = (\(TraceId b) -> newHeaderFromTraceId b) traceId parentIdValue = (\(SpanId b) -> newHeaderFromSpanId b) spanId samplingPriority <- case lookup (TS.Key samplingPriorityKey) traceState of Nothing -> pure "1" -- when an origin of the trace Just (TS.Value p) -> pure $ BC.pack $ T.unpack p pure $ (traceIdKey, traceIdValue) : (parentIdKey, parentIdValue) : (samplingPriorityKey, samplingPriority) : hs } where traceIdKey, parentIdKey, samplingPriorityKey :: (IsString s) => s traceIdKey = "x-datadog-trace-id" parentIdKey = "x-datadog-parent-id" samplingPriorityKey = "x-datadog-sampling-priority" convertOpenTelemetrySpanIdToDatadogSpanId :: SpanId -> Word64 convertOpenTelemetrySpanIdToDatadogSpanId (SpanId (SBI.SBS a)) = indexByteArrayNbo (ByteArray a) 0 convertOpenTelemetryTraceIdToDatadogTraceId :: TraceId -> Word64 convertOpenTelemetryTraceIdToDatadogTraceId (TraceId (SBI.SBS a)) = indexByteArrayNbo (ByteArray a) 1