hs-opentelemetry-sdk 0.0.3.3 → 0.0.3.4
raw patch · 16 files changed
+781/−578 lines, 16 filessetup-changedPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- Setup.hs +2/−0
- hs-opentelemetry-sdk.cabal +4/−4
- src/OpenTelemetry/Processor/Batch.hs +218/−116
- src/OpenTelemetry/Processor/Simple.hs +31/−28
- src/OpenTelemetry/Resource/Host/Detector.hs +32/−26
- src/OpenTelemetry/Resource/OperatingSystem/Detector.hs +39/−30
- src/OpenTelemetry/Resource/Process/Detector.hs +40/−30
- src/OpenTelemetry/Resource/Service/Detector.hs +12/−9
- src/OpenTelemetry/Resource/Telemetry/Detector.hs +10/−6
- src/OpenTelemetry/Trace.hs +332/−282
- src/OpenTelemetry/Trace/Id/Generator/Default.hs +30/−20
- test/OpenTelemetry/BaggageSpec.hs +1/−0
- test/OpenTelemetry/ContextSpec.hs +3/−1
- test/OpenTelemetry/ResourceSpec.hs +1/−0
- test/OpenTelemetry/TraceSpec.hs +23/−24
- test/Spec.hs +3/−2
Setup.hs view
@@ -1,2 +1,4 @@ import Distribution.Simple++ main = defaultMain
hs-opentelemetry-sdk.cabal view
@@ -1,19 +1,19 @@ cabal-version: 1.22 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.34.5. -- -- see: https://github.com/sol/hpack name: hs-opentelemetry-sdk-version: 0.0.3.3+version: 0.0.3.4 synopsis: OpenTelemetry SDK for use in applications. description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/sdk#readme> category: OpenTelemetry, Telemetry, Monitoring, Observability, Metrics homepage: https://github.com/iand675/hs-opentelemetry#readme bug-reports: https://github.com/iand675/hs-opentelemetry/issues-author: Ian Duncan+author: Ian Duncan, Jade Lovelace maintainer: ian@iankduncan.com-copyright: 2021 Ian Duncan+copyright: 2022 Ian Duncan license: BSD3 license-file: LICENSE build-type: Simple
src/OpenTelemetry/Processor/Batch.hs view
@@ -1,41 +1,44 @@ {-# LANGUAGE RecordWildCards #-}+ -------------------------------------------------------------------------------- |--- Module : OpenTelemetry.Processor.Batch--- Copyright : (c) Ian Duncan, 2021--- License : BSD-3--- Description : Performant exporting of spans in time & space-bounded batches.--- Maintainer : Ian Duncan--- Stability : experimental--- Portability : non-portable (GHC extensions)------ This is an implementation of the Span Processor which create batches of finished spans and passes the export-friendly span data representations to the configured Exporter.---+ ------------------------------------------------------------------------------module OpenTelemetry.Processor.Batch- ( BatchTimeoutConfig(..)- , batchTimeoutConfig- , batchProcessor++{- |+ Module : OpenTelemetry.Processor.Batch+ Copyright : (c) Ian Duncan, 2021+ License : BSD-3+ Description : Performant exporting of spans in time & space-bounded batches.+ Maintainer : Ian Duncan+ Stability : experimental+ Portability : non-portable (GHC extensions)++ This is an implementation of the Span Processor which create batches of finished spans and passes the export-friendly span data representations to the configured Exporter.+-}+module OpenTelemetry.Processor.Batch (+ BatchTimeoutConfig (..),+ batchTimeoutConfig,+ batchProcessor, -- , BatchProcessorOperations- ) where-import Control.Monad.IO.Class-import OpenTelemetry.Processor-import OpenTelemetry.Exporter (Exporter)-import qualified OpenTelemetry.Exporter as Exporter-import VectorBuilder.Builder as Builder-import VectorBuilder.Vector as Builder-import Data.IORef (atomicModifyIORef', readIORef, newIORef)+) where+ import Control.Concurrent.Async-import Control.Concurrent.MVar (newEmptyMVar, takeMVar, tryPutMVar)-import Control.Monad-import Control.Monad.Trans.Except-import System.Timeout+import Control.Concurrent.STM import Control.Exception+import Control.Monad+import Control.Monad.IO.Class import Data.HashMap.Strict (HashMap)-import OpenTelemetry.Trace.Core import qualified Data.HashMap.Strict as HashMap+import Data.IORef (atomicModifyIORef', newIORef, readIORef) import Data.Vector (Vector)+import OpenTelemetry.Exporter (Exporter)+import qualified OpenTelemetry.Exporter as Exporter+import OpenTelemetry.Processor+import OpenTelemetry.Trace.Core+import VectorBuilder.Builder as Builder+import VectorBuilder.Vector as Builder + -- | Configurable options for batch exporting frequence and size data BatchTimeoutConfig = BatchTimeoutConfig { maxQueueSize :: Int@@ -49,17 +52,21 @@ , maxExportBatchSize :: Int -- ^ The maximum batch size of every export. It must be -- smaller or equal to 'maxQueueSize'. The default value is 512.- } deriving (Show)+ }+ deriving (Show) + -- | Default configuration values batchTimeoutConfig :: BatchTimeoutConfig-batchTimeoutConfig = BatchTimeoutConfig- { maxQueueSize = 1024- , scheduledDelayMillis = 5000- , exportTimeoutMillis = 30000- , maxExportBatchSize = 512- }+batchTimeoutConfig =+ BatchTimeoutConfig+ { maxQueueSize = 1024+ , scheduledDelayMillis = 5000+ , exportTimeoutMillis = 30000+ , maxExportBatchSize = 512+ } + -- type BatchProcessorOperations = () -- A multi-producer single-consumer green/blue buffer.@@ -74,10 +81,8 @@ -- , gbVector :: !(M.IOVector a) -- } - {- brainstorm: Single Word64 state sketch - 63 (high bit): green or blue 32-62: read section 0-32: write count@@ -87,12 +92,12 @@ Green 512 512 512 512-|---------|---------|---------|---------|+\|---------|---------|---------|---------| 0 1 2 3 Blue 512 512 512 512-|---------|---------|---------|---------|+\|---------|---------|---------|---------| 0 1 2 3 The current read section denotes one chunk of length gbSize, which gets flushed@@ -146,13 +151,13 @@ -- consumeChunk :: GreenBlueBuffer a -> IO (V.Vector a) -- consumeChunk GreenBlueBuffer{..} = atomically $ do -- r <- readTVar gbReadSection- -- w <- readTVar gbWriteSection- -- c <- readTVar gbPendingWrites- -- when (r == w) $ do- -- modifyTVar gbWriteSection (+ 1)- -- setTVar gbPendingWrites 0- -- -- TODO slice and freeze appropriate section- -- M.slice (gbSectionSize * (r .&. gbSectionMask)+-- w <- readTVar gbWriteSection+-- c <- readTVar gbPendingWrites+-- when (r == w) $ do+-- modifyTVar gbWriteSection (+ 1)+-- setTVar gbPendingWrites 0+-- -- TODO slice and freeze appropriate section+-- M.slice (gbSectionSize * (r .&. gbSectionMask) -- TODO, counters for dropped spans, exported spans @@ -162,98 +167,195 @@ , itemMap :: HashMap InstrumentationLibrary (Builder.Builder a) } + boundedMap :: Int -> BoundedMap a boundedMap bounds = BoundedMap bounds 0 mempty + push :: ImmutableSpan -> BoundedMap ImmutableSpan -> Maybe (BoundedMap ImmutableSpan)-push s m = if itemCount m + 1 >= itemBounds m- then Nothing- else Just $! m- { itemCount = itemCount m + 1- , itemMap = HashMap.insertWith- (<>)- (tracerName $ spanTracer s)- (Builder.singleton s) $- itemMap m- }+push s m =+ if itemCount m + 1 >= itemBounds m+ then Nothing+ else+ Just $!+ m+ { itemCount = itemCount m + 1+ , itemMap =+ HashMap.insertWith+ (<>)+ (tracerName $ spanTracer s)+ (Builder.singleton s)+ $ itemMap m+ } + buildExport :: BoundedMap a -> (BoundedMap a, HashMap InstrumentationLibrary (Vector a)) buildExport m =- ( m { itemCount = 0, itemMap = mempty }+ ( m {itemCount = 0, itemMap = mempty} , Builder.build <$> itemMap m ) --- | Exitable forever loop-loop :: Monad m => ExceptT e m a -> m e-loop = liftM (either id id) . runExceptT . forever data ProcessorMessage = Flush | Shutdown --- |--- The batch processor accepts spans and places them into batches. Batching helps better compress the data and reduce the number of outgoing connections--- required to transmit the data. This processor supports both size and time based batching.++-- note: [Unmasking Asyncs] --+-- It is possible to create unkillable asyncs. Behold:+--+-- ```+-- a <- uninterruptibleMask_ $ do+-- async $ do+-- forever $ do+-- threadDelay 10_000+-- putStrLn "still alive"+-- cancel a+-- ```+--+-- The prior code block will never successfully cancel `a` and will be+-- blocked forever. The reason is that `cancel` sends an async exception to+-- the thread performing the action, but the `uninterruptibleMask` state is+-- inherited by the forked thread. This means that *no async exceptions*+-- can reach it, and `cancel` will therefore run forever.+--+-- This also affects `timeout`, which uses an async exception to kill the+-- running job. If the action is done in an uninterruptible masked state,+-- then the timeout will not successfully kill the running action.++{- |+ The batch processor accepts spans and places them into batches. Batching helps better compress the data and reduce the number of outgoing connections+ required to transmit the data. This processor supports both size and time based batching.+-} batchProcessor :: MonadIO m => BatchTimeoutConfig -> Exporter ImmutableSpan -> m Processor-batchProcessor BatchTimeoutConfig{..} exporter = liftIO $ do+batchProcessor BatchTimeoutConfig {..} exporter = liftIO $ do batch <- newIORef $ boundedMap maxQueueSize- workSignal <- newEmptyMVar- worker <- async $ loop $ do- req <- liftIO $ timeout (millisToMicros scheduledDelayMillis)- $ takeMVar workSignal- batchToProcess <- liftIO $ atomicModifyIORef' batch buildExport- res <- liftIO $ Exporter.exporterExport exporter batchToProcess+ workSignal <- newEmptyTMVarIO+ shutdownSignal <- newEmptyTMVarIO+ let publish batchToProcess = mask_ $ do+ -- we mask async exceptions in this, so that a buggy exporter that+ -- catches async exceptions won't swallow them. since we use+ -- an interruptible mask, blocking calls can still be killed, like+ -- `threadDelay` or `putMVar` or most file I/O operations.+ --+ -- if we've received a shutdown, then we should be expecting+ -- a `cancel` anytime now.+ Exporter.exporterExport exporter batchToProcess - -- if we were asked to shutdown, quit cleanly after this batch- -- FIXME: this could lose batches if there's more than one in queue?- case req of- Just Shutdown -> throwE res- _ -> pure ()+ let flushQueueImmediately ret = do+ batchToProcess <- atomicModifyIORef' batch buildExport+ if null batchToProcess+ then do+ pure ret+ else do+ ret' <- publish batchToProcess+ flushQueueImmediately ret' - pure $ Processor- { processorOnStart = \_ _ -> pure ()- , processorOnEnd = \s -> do- span_ <- readIORef s- appendFailed <- atomicModifyIORef' batch $ \builder ->- case push span_ builder of- Nothing -> (builder, True)- Just b' -> (b', False)- when appendFailed $ void $ tryPutMVar workSignal Flush+ let waiting = do+ delay <- registerDelay (millisToMicros scheduledDelayMillis)+ atomically $ do+ msum+ [ Flush <$ do+ continue <- readTVar delay+ check continue+ , Flush <$ takeTMVar workSignal+ , Shutdown <$ takeTMVar shutdownSignal+ ] - , processorForceFlush = void $ tryPutMVar workSignal Flush- -- TODO where to call restore, if anywhere?- , processorShutdown = async $ mask $ \_restore -> do- -- flush remaining messages- void $ tryPutMVar workSignal Shutdown+ let workerAction = do+ req <- waiting+ batchToProcess <- atomicModifyIORef' batch buildExport+ res <- publish batchToProcess - shutdownResult <- timeout (millisToMicros exportTimeoutMillis) $- wait worker- -- make sure the worker comes down- uninterruptibleCancel worker- -- TODO, not convinced we should shut down processor here+ -- if we were asked to shutdown, stop waiting and flush it all out+ case req of+ Shutdown ->+ flushQueueImmediately res+ _ ->+ workerAction+ -- see note [Unmasking Asyncs]+ worker <- asyncWithUnmask $ \unmask -> unmask workerAction - case shutdownResult of- Nothing -> pure ShutdownFailure- Just _ -> pure ShutdownSuccess- }+ pure $+ Processor+ { processorOnStart = \_ _ -> pure ()+ , processorOnEnd = \s -> do+ span_ <- readIORef s+ appendFailed <- atomicModifyIORef' batch $ \builder ->+ case push span_ builder of+ Nothing -> (builder, True)+ Just b' -> (b', False)+ when appendFailed $ void $ atomically $ tryPutTMVar workSignal ()+ , processorForceFlush = void $ atomically $ tryPutTMVar workSignal ()+ , -- TODO where to call restore, if anywhere?+ processorShutdown =+ asyncWithUnmask $ \unmask -> unmask $ do+ -- we call asyncWithUnmask here because the shutdown action is+ -- likely to happen inside of a `finally` or `bracket`. the+ -- @safe-exceptions@ pattern (followed by unliftio as well)+ -- will use uninterruptibleMask in an exception cleanup. the+ -- uninterruptibleMask state means that the `timeout` call+ -- below will never exit, because `wait worker` will be in the+ -- `uninterruptibleMasked` state, and the timeout async+ -- exception will not be delivered.+ --+ -- see note [Unmasking Asyncs]+ mask $ \_restore -> do+ -- is it a little silly that we unmask and remask? seems+ -- silly! but the `mask` here is doing an interruptible mask.+ -- which means that async exceptions can still be delivered+ -- if a process is blocking.++ -- flush remaining messages and signal the worker to shutdown+ void $ atomically $ putTMVar shutdownSignal ()++ -- gracefully wait for the worker to stop. we may be in+ -- a `bracket` or responding to an async exception, so we+ -- must be very careful not to wait too long. the following+ -- STM action will block, so we'll be susceptible to an async+ -- exception.+ delay <- registerDelay (millisToMicros exportTimeoutMillis)+ shutdownResult <-+ atomically $+ msum+ [ Just <$> waitCatchSTM worker+ , const Nothing <$> do+ shouldStop <- readTVar delay+ check shouldStop+ ]++ -- make sure the worker comes down.+ cancel worker+ -- TODO, not convinced we should shut down processor here++ pure $ case shutdownResult of+ Nothing ->+ ShutdownTimeout+ Just er ->+ case er of+ Left _ ->+ ShutdownFailure+ Right _ ->+ ShutdownSuccess+ } where millisToMicros = (* 1000)- {-- buffer <- newGreenBlueBuffer _ _- batchProcessorAction <- async $ forever $ do- -- It would be nice to do an immediate send when possible- chunk <- if (sendDelay == 0)- else consumeChunk- then threadDelay sendDelay >> consumeChunk- timeout _ $ export exporter chunk- pure $ Processor- { onStart = \_ _ -> pure ()- , onEnd = \s -> void $ tryInsert buffer s- , shutdown = do- gracefullyShutdownBatchProcessor +{-+buffer <- newGreenBlueBuffer _ _+batchProcessorAction <- async $ forever $ do+ -- It would be nice to do an immediate send when possible+ chunk <- if (sendDelay == 0)+ else consumeChunk+ then threadDelay sendDelay >> consumeChunk+ timeout _ $ export exporter chunk+pure $ Processor+ { onStart = \_ _ -> pure ()+ , onEnd = \s -> void $ tryInsert buffer s+ , shutdown = do+ gracefullyShutdownBatchProcessor - , forceFlush = pure ()- }- where- sendDelay = scheduledDelayMilis * 1_000- -}+ , forceFlush = pure ()+ }+where+ sendDelay = scheduledDelayMilis * 1_000+-}
src/OpenTelemetry/Processor/Simple.hs view
@@ -1,32 +1,36 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-module OpenTelemetry.Processor.Simple- ( SimpleProcessorConfig(..)- , simpleProcessor- ) where -import Control.Exception+module OpenTelemetry.Processor.Simple (+ SimpleProcessorConfig (..),+ simpleProcessor,+) where+ import Control.Concurrent.Async import Control.Concurrent.Chan.Unagi+import Control.Exception import Control.Monad+import qualified Data.HashMap.Strict as HashMap import Data.IORef+import qualified OpenTelemetry.Exporter as Exporter import OpenTelemetry.Processor import OpenTelemetry.Trace.Core (ImmutableSpan, spanTracer, tracerName)-import qualified OpenTelemetry.Exporter as Exporter-import qualified Data.HashMap.Strict as HashMap + newtype SimpleProcessorConfig = SimpleProcessorConfig { exporter :: Exporter.Exporter ImmutableSpan -- ^ The exporter where the spans are pushed. } --- | This is an implementation of SpanProcessor which passes finished spans --- and passes the export-friendly span data representation to the configured SpanExporter, --- as soon as they are finished.------ @since 0.0.1.0++{- | This is an implementation of SpanProcessor which passes finished spans+ and passes the export-friendly span data representation to the configured SpanExporter,+ as soon as they are finished.++ @since 0.0.1.0+-} simpleProcessor :: SimpleProcessorConfig -> IO Processor-simpleProcessor SimpleProcessorConfig{..} = do+simpleProcessor SimpleProcessorConfig {..} = do (inChan :: InChan (IORef ImmutableSpan), outChan :: OutChan (IORef ImmutableSpan)) <- newChan exportWorker <- async $ forever $ do -- TODO, masking vs bracket here, not sure what's the right choice@@ -34,28 +38,27 @@ span_ <- readIORef spanRef mask_ (exporter `Exporter.exporterExport` HashMap.singleton (tracerName $ spanTracer span_) (pure span_)) - pure $ Processor- { processorOnStart = \_ _ -> pure ()- , processorOnEnd = writeChan inChan - , processorShutdown = async $ mask $ \restore -> do- cancel exportWorker- -- TODO handle timeouts- restore $ do- -- TODO, not convinced we should shut down processor here- shutdownProcessor outChan `finally` Exporter.exporterShutdown exporter- pure ShutdownSuccess- , processorForceFlush = pure ()- }-+ pure $+ Processor+ { processorOnStart = \_ _ -> pure ()+ , processorOnEnd = writeChan inChan+ , processorShutdown = async $ mask $ \restore -> do+ cancel exportWorker+ -- TODO handle timeouts+ restore $ do+ -- TODO, not convinced we should shut down processor here+ shutdownProcessor outChan `finally` Exporter.exporterShutdown exporter+ pure ShutdownSuccess+ , processorForceFlush = pure ()+ } where shutdownProcessor :: OutChan (IORef ImmutableSpan) -> IO () shutdownProcessor outChan = do (Element m, _) <- tryReadChan outChan mSpan <- m- case mSpan of + case mSpan of Nothing -> pure () Just spanRef -> do span_ <- readIORef spanRef _ <- exporter `Exporter.exporterExport` HashMap.singleton (tracerName $ spanTracer span_) (pure span_) shutdownProcessor outChan-
src/OpenTelemetry/Resource/Host/Detector.hs view
@@ -1,18 +1,19 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-module OpenTelemetry.Resource.Host.Detector - ( detectHost- , builtInHostDetectors- , HostDetector- ) where -import OpenTelemetry.Resource.Host+module OpenTelemetry.Resource.Host.Detector (+ detectHost,+ builtInHostDetectors,+ HostDetector,+) where +import Control.Monad import qualified Data.Text as T import Network.BSD-import Control.Monad+import OpenTelemetry.Resource.Host import System.Info (arch) + adaptedArch :: T.Text adaptedArch = case arch of "aarch64" -> "arm64"@@ -25,6 +26,7 @@ "powerpc64le" -> "ppc64" other -> T.pack other + -- | Detect as much host information as possible detectHost :: IO Host detectHost = do@@ -36,29 +38,33 @@ go Nothing hostDetector = hostDetector go mhost@(Just _host) _ = pure mhost --- | A set of detectors for e.g. AWS, GCP, and other cloud providers.------ Currently only emits hostName and hostArch. Additional detectors are--- welcome via PR.++{- | A set of detectors for e.g. AWS, GCP, and other cloud providers.++ Currently only emits hostName and hostArch. Additional detectors are+ welcome via PR.+-} builtInHostDetectors :: [HostDetector] builtInHostDetectors =- [- -- TODO- -- AWS support- -- GCP support- -- any other user contributed- fallbackHostDetector+ [ -- TODO+ -- AWS support+ -- GCP support+ -- any other user contributed+ fallbackHostDetector ] + type HostDetector = IO (Maybe Host) + fallbackHostDetector :: HostDetector-fallbackHostDetector = Just <$> do- let hostId = Nothing- hostType = Nothing- hostArch = Just adaptedArch- hostImageName = Nothing- hostImageId = Nothing- hostImageVersion = Nothing- hostName <- Just . T.pack <$> getHostName- pure Host{..}+fallbackHostDetector =+ Just <$> do+ let hostId = Nothing+ hostType = Nothing+ hostArch = Just adaptedArch+ hostImageName = Nothing+ hostImageId = Nothing+ hostImageVersion = Nothing+ hostName <- Just . T.pack <$> getHostName+ pure Host {..}
src/OpenTelemetry/Resource/OperatingSystem/Detector.hs view
@@ -1,35 +1,44 @@ {-# LANGUAGE OverloadedStrings #-}+ -------------------------------------------------------------------------------- |--- Module : OpenTelemetry.Resource.OperatingSystem.Detector--- Copyright : (c) Ian Duncan, 2021--- License : BSD-3--- Maintainer : Ian Duncan--- Stability : experimental--- Portability : non-portable (GHC extensions)------ Detect information about the current system's OS.---+ ------------------------------------------------------------------------------module OpenTelemetry.Resource.OperatingSystem.Detector - ( detectOperatingSystem- ) where-import OpenTelemetry.Resource.OperatingSystem++{- |+ Module : OpenTelemetry.Resource.OperatingSystem.Detector+ Copyright : (c) Ian Duncan, 2021+ License : BSD-3+ Maintainer : Ian Duncan+ Stability : experimental+ Portability : non-portable (GHC extensions)++ Detect information about the current system's OS.+-}+module OpenTelemetry.Resource.OperatingSystem.Detector (+ detectOperatingSystem,+) where+ import qualified Data.Text as T-import System.Info ( os )+import OpenTelemetry.Resource.OperatingSystem+import System.Info (os) --- | Retrieve any infomration able to be detected about the current operation system.------ Currently only supports 'osType' detection, but PRs are welcome to support additional--- details.------ @since 0.0.1.0-detectOperatingSystem :: IO OperatingSystem -detectOperatingSystem = pure $ OperatingSystem- { osType = if os == "mingw32"- then "windows"- else T.pack os- , osDescription = Nothing- , osName = Nothing- , osVersion = Nothing- }++{- | Retrieve any infomration able to be detected about the current operation system.++ Currently only supports 'osType' detection, but PRs are welcome to support additional+ details.++ @since 0.0.1.0+-}+detectOperatingSystem :: IO OperatingSystem+detectOperatingSystem =+ pure $+ OperatingSystem+ { osType =+ if os == "mingw32"+ then "windows"+ else T.pack os+ , osDescription = Nothing+ , osName = Nothing+ , osVersion = Nothing+ }
src/OpenTelemetry/Resource/Process/Detector.hs view
@@ -1,47 +1,57 @@ module OpenTelemetry.Resource.Process.Detector where +import Control.Exception (throwIO, try) import qualified Data.Text as T-import System.Environment- ( getArgs, getProgName, getExecutablePath )-import System.Posix.Process ( getProcessID )-import System.Posix.User (getEffectiveUserName)-import System.Info import Data.Version import OpenTelemetry.Resource.Process-import Control.Exception (try, throwIO)+import System.Environment (+ getArgs,+ getExecutablePath,+ getProgName,+ ) import System.IO.Error+import System.Info+import System.Posix.Process (getProcessID)+import System.Posix.User (getEffectiveUserName) --- | Create a 'Process' 'Resource' based off of the current process' knowledge--- of itself.------ @since 0.1.0.0++{- | Create a 'Process' 'Resource' based off of the current process' knowledge+ of itself.++ @since 0.1.0.0+-} detectProcess :: IO Process detectProcess = do- Process <$>- (Just . fromIntegral <$> getProcessID) <*>- (Just . T.pack <$> getProgName) <*>- (Just . T.pack <$> getExecutablePath) <*>- pure Nothing <*>- pure Nothing <*>- (Just . map T.pack <$> getArgs) <*>- tryGetUser+ Process+ <$> (Just . fromIntegral <$> getProcessID)+ <*> (Just . T.pack <$> getProgName)+ <*> (Just . T.pack <$> getExecutablePath)+ <*> pure Nothing+ <*> pure Nothing+ <*> (Just . map T.pack <$> getArgs)+ <*> tryGetUser + tryGetUser :: IO (Maybe T.Text) tryGetUser = do eResult <- try getEffectiveUserName case eResult of- Left err -> if isDoesNotExistError err- then pure Nothing- else throwIO err+ Left err ->+ if isDoesNotExistError err+ then pure Nothing+ else throwIO err Right ok -> pure $ Just $ T.pack ok --- | A 'ProcessRuntime' 'Resource' populated with the current process' knoweldge--- of itself.------ @since 0.0.1.0++{- | A 'ProcessRuntime' 'Resource' populated with the current process' knoweldge+ of itself.++ @since 0.0.1.0+-} detectProcessRuntime :: ProcessRuntime-detectProcessRuntime = ProcessRuntime- { processRuntimeName = Just $ T.pack compilerName- , processRuntimeVersion = Just $ T.pack $ showVersion compilerVersion- , processRuntimeDescription = Nothing- }+detectProcessRuntime =+ ProcessRuntime+ { processRuntimeName = Just $ T.pack compilerName+ , processRuntimeVersion = Just $ T.pack $ showVersion compilerVersion+ , processRuntimeDescription = Nothing+ }
src/OpenTelemetry/Resource/Service/Detector.hs view
@@ -2,19 +2,22 @@ import qualified Data.Text as T import OpenTelemetry.Resource.Service-import System.Environment (lookupEnv, getProgName)+import System.Environment (getProgName, lookupEnv) --- | Detect a service name using the 'OTEL_SERVICE_NAME' environment--- variable. Otherwise, populates the name with 'unknown_service:process_name'.++{- | Detect a service name using the 'OTEL_SERVICE_NAME' environment+ variable. Otherwise, populates the name with 'unknown_service:process_name'.+-} detectService :: IO Service detectService = do mSvcName <- lookupEnv "OTEL_SERVICE_NAME" svcName <- case mSvcName of Nothing -> T.pack . ("unknown_service:" <>) <$> getProgName Just svcName -> pure $ T.pack svcName- pure $ Service- { serviceName = svcName- , serviceNamespace = Nothing- , serviceInstanceId = Nothing- , serviceVersion = Nothing- }+ pure $+ Service+ { serviceName = svcName+ , serviceNamespace = Nothing+ , serviceInstanceId = Nothing+ , serviceVersion = Nothing+ }
src/OpenTelemetry/Resource/Telemetry/Detector.hs view
@@ -1,15 +1,19 @@ {-# LANGUAGE OverloadedStrings #-}+ module OpenTelemetry.Resource.Telemetry.Detector where+ import qualified Data.Text as T import Data.Version (showVersion) import OpenTelemetry.Resource.Telemetry import Paths_hs_opentelemetry_sdk + -- | Built-in information about this package detectTelemetry :: Telemetry-detectTelemetry = Telemetry- { telemetrySdkName = "hs-opentelemetry-sdk"- , telemetrySdkLanguage = Just "haskell"- , telemetrySdkVersion = Just $ T.pack $ showVersion version- , telemetryAutoVersion = Nothing- }+detectTelemetry =+ Telemetry+ { telemetrySdkName = "hs-opentelemetry-sdk"+ , telemetrySdkLanguage = Just "haskell"+ , telemetrySdkVersion = Just $ T.pack $ showVersion version+ , telemetryAutoVersion = Nothing+ }
src/OpenTelemetry/Trace.hs view
@@ -1,60 +1,63 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RankNTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeOperators #-}+ -------------------------------------------------------------------------------- |--- Module : OpenTelemetry.Trace--- Copyright : (c) Ian Duncan, 2021--- License : BSD-3--- Description : Application Tracing API--- Maintainer : Ian Duncan--- Stability : experimental--- Portability : non-portable (GHC extensions)------ Traces track the progression of a single request, called a trace, as it is handled --- by services that make up an application. The request may be initiated by a user or --- an application. Distributed tracing is a form of tracing that traverses process, network --- and security boundaries. Each unit of work in a trace is called a span; a trace is a tree of spans. --- Spans are objects that represent the work being done by individual services or components involved in a request as it flows through a system. A span contains a span context, which is a set of globally unique identifiers that represent the unique request that each span is a part of. --- A span provides Request, Error and Duration (RED) metrics that can be used to debug availability as well as performance issues.------ Here is a visualization of the relationship between traces and spans:------ <<docs/img/traces_spans.png>>------ A trace contains a single root span which encapsulates the end-to-end latency for the entire request. You can think of this as a single logical operation, such as clicking a button in a web application to add a product to a shopping cart. The root span would measure the time it took from an end-user clicking that button to the operation being completed or failing (so, the item is added to the cart or some error occurs) and the result being displayed to the user. A trace is comprised of the single root span and any number of child spans, which represent operations taking place as part of the request. Each span contains metadata about the operation, such as its name, start and end timestamps, attributes (which represent additonal user-defined metadata about a span), events, and status.--- --- To create and manage spans in OpenTelemetry, the OpenTelemetry API provides the tracer interface. This object is responsible for tracking the active span in your process, and allows you to access the current span in order to perform operations on it such as adding attributes, events, and finishing it when the work it tracks is complete. One or more tracer objects can be created in a process through the tracer provider, a factory interface that allows for multiple tracers to be instantiated in a single process with different options.--- --- Generally, the lifecycle of a span resembles the following:--- --- - A request is received by a service. The span context is extracted from the request headers, if it exists.--- - A new span is created as a child of the extracted span context; if none exists, a new root span is created.--- - The service handles the request. Additional attributes and events are added to the span that are useful for understanding the context of the request, such as the hostname of the machine handling the request, or customer identifiers.--- - New spans may be created to represent work being done by sub-components of the service.--- - When the service makes a remote call to another service, the current span context is serialized and forwarded to the next service by injecting the span context into the headers or message envelope.--- - The work being done by the service completes, successfully or not. The span status is appropriately set, and the span is marked finished.--- - For more information, see the traces specification, which covers concepts including: trace, span, parent/child relationship, span context, attributes, events and links.--------- This module implements eveything required to conform to the trace & span public interface described--- by the OpenTelemetry specification.------ See "OpenTelemetry.Trace.Monad" for an implementation of 'inSpan' variants that are--- slightly easier to use in idiomatic Haskell monadic code.---+ ------------------------------------------------------------------------------module OpenTelemetry.Trace - ( ++{- |+ Module : OpenTelemetry.Trace+ Copyright : (c) Ian Duncan, 2021+ License : BSD-3+ Description : Application Tracing API+ Maintainer : Ian Duncan+ Stability : experimental+ Portability : non-portable (GHC extensions)++ Traces track the progression of a single request, called a trace, as it is handled+ by services that make up an application. The request may be initiated by a user or+ an application. Distributed tracing is a form of tracing that traverses process, network+ and security boundaries. Each unit of work in a trace is called a span; a trace is a tree of spans.+ Spans are objects that represent the work being done by individual services or components involved in a request as it flows through a system. A span contains a span context, which is a set of globally unique identifiers that represent the unique request that each span is a part of.+ A span provides Request, Error and Duration (RED) metrics that can be used to debug availability as well as performance issues.++ Here is a visualization of the relationship between traces and spans:++ <<docs/img/traces_spans.png>>++ A trace contains a single root span which encapsulates the end-to-end latency for the entire request. You can think of this as a single logical operation, such as clicking a button in a web application to add a product to a shopping cart. The root span would measure the time it took from an end-user clicking that button to the operation being completed or failing (so, the item is added to the cart or some error occurs) and the result being displayed to the user. A trace is comprised of the single root span and any number of child spans, which represent operations taking place as part of the request. Each span contains metadata about the operation, such as its name, start and end timestamps, attributes (which represent additonal user-defined metadata about a span), events, and status.++ To create and manage spans in OpenTelemetry, the OpenTelemetry API provides the tracer interface. This object is responsible for tracking the active span in your process, and allows you to access the current span in order to perform operations on it such as adding attributes, events, and finishing it when the work it tracks is complete. One or more tracer objects can be created in a process through the tracer provider, a factory interface that allows for multiple tracers to be instantiated in a single process with different options.++ Generally, the lifecycle of a span resembles the following:++ - A request is received by a service. The span context is extracted from the request headers, if it exists.+ - A new span is created as a child of the extracted span context; if none exists, a new root span is created.+ - The service handles the request. Additional attributes and events are added to the span that are useful for understanding the context of the request, such as the hostname of the machine handling the request, or customer identifiers.+ - New spans may be created to represent work being done by sub-components of the service.+ - When the service makes a remote call to another service, the current span context is serialized and forwarded to the next service by injecting the span context into the headers or message envelope.+ - The work being done by the service completes, successfully or not. The span status is appropriately set, and the span is marked finished.+ - For more information, see the traces specification, which covers concepts including: trace, span, parent/child relationship, span context, attributes, events and links.+++ This module implements eveything required to conform to the trace & span public interface described+ by the OpenTelemetry specification.++ See "OpenTelemetry.Trace.Monad" for an implementation of 'inSpan' variants that are+ slightly easier to use in idiomatic Haskell monadic code.+-}+module OpenTelemetry.Trace ( -- * How to use this library -- ** Quick start -- $use- + -- ** Configuration+ -- Nearly everything is configurable via environment variables. -- *** General configuration variables@@ -69,13 +72,13 @@ -- *** Span limits -- $envSpanLimits - -- ** Exporting data+ -- -- By default, the <https://hackage.haskell.org/package/hs-opentelemetry-exporter-otlp OTLP protocol exporter> -- will be used. It supports exporting to the <https://opentelemetry.io/docs/collector/getting-started/ OpenTelemetry collector agent>, -- which supports a wide array of 3rd party services, and also provides a wide array of data enrichment abilities.- -- + -- -- Additionally, a number of third party services directly support the OTLP protocol, so you can also often directly connect -- to their API gateway to send data. See your telemetry vendor's documentation to determine if this is the case. --@@ -84,186 +87,202 @@ -- * 'TracerProvider' operations -- $tracerProvider- TracerProvider- , initializeGlobalTracerProvider- , initializeTracerProvider- , getTracerProviderInitializationOptions- , getTracerProviderInitializationOptions'- , shutdownTracerProvider+ TracerProvider,+ initializeGlobalTracerProvider,+ initializeTracerProvider,+ getTracerProviderInitializationOptions,+ getTracerProviderInitializationOptions',+ shutdownTracerProvider,+ -- ** Getting / setting the global 'TracerProvider'- , getGlobalTracerProvider- , setGlobalTracerProvider+ getGlobalTracerProvider,+ setGlobalTracerProvider,+ -- * 'Tracer' operations- , Tracer- , tracerName- , getTracer- , makeTracer- , TracerOptions(..)- , tracerOptions- , HasTracer(..)- , InstrumentationLibrary(..)+ Tracer,+ tracerName,+ getTracer,+ makeTracer,+ TracerOptions (..),+ tracerOptions,+ HasTracer (..),+ InstrumentationLibrary (..),+ -- * 'Span' operations- , Span- , inSpan- , defaultSpanArguments- , SpanArguments(..)- , SpanKind(..)- , NewLink(..)- , inSpan'- , updateName- , addAttribute - , addAttributes- , recordException- , setStatus- , SpanStatus(..)- , NewEvent(..)- , addEvent- , inSpan''+ Span,+ inSpan,+ defaultSpanArguments,+ SpanArguments (..),+ SpanKind (..),+ NewLink (..),+ inSpan',+ updateName,+ addAttribute,+ addAttributes,+ recordException,+ setStatus,+ SpanStatus (..),+ NewEvent (..),+ addEvent,+ inSpan'',+ -- * Primitive span and tracing operations+ -- ** Alternative 'TracerProvider' initialization- , createTracerProvider- , TracerProviderOptions(..)- , emptyTracerProviderOptions- , detectBuiltInResources- , detectSampler- , createSpan- , createSpanWithoutCallStack- , endSpan- , spanGetAttributes- , ToAttribute(..)- , ToPrimitiveAttribute(..)- , Attribute(..)- , PrimitiveAttribute(..)- , Link- , Event- , SpanContext(..)+ createTracerProvider,+ TracerProviderOptions (..),+ emptyTracerProviderOptions,+ detectBuiltInResources,+ detectSampler,+ createSpan,+ createSpanWithoutCallStack,+ endSpan,+ spanGetAttributes,+ ToAttribute (..),+ ToPrimitiveAttribute (..),+ Attribute (..),+ PrimitiveAttribute (..),+ Link,+ Event,+ SpanContext (..), -- TODO, don't remember if this is okay with the spec or not- , ImmutableSpan(..)- ) where+ ImmutableSpan (..),+) where -import OpenTelemetry.Trace.Core-import OpenTelemetry.Resource-import Data.Maybe (fromMaybe)+import qualified Data.ByteString.Char8 as B import Data.Either (partitionEithers)+import qualified Data.HashMap.Strict as H+import Data.Maybe (fromMaybe) import qualified Data.Text as T-import OpenTelemetry.Context (Context)+import Data.Text.Encoding (decodeUtf8) import Network.HTTP.Types.Header-import OpenTelemetry.Propagator.W3CTraceContext (w3cTraceContextPropagator)-import OpenTelemetry.Propagator.W3CBaggage (w3cBaggagePropagator)-import OpenTelemetry.Propagator (Propagator)-import System.Environment (lookupEnv)-import OpenTelemetry.Trace.Sampler (Sampler, alwaysOn, alwaysOff, traceIdRatioBased, parentBased, parentBasedOptions)-import Text.Read (readMaybe)-import OpenTelemetry.Exporter (Exporter)-import OpenTelemetry.Processor.Batch (BatchTimeoutConfig (..), batchTimeoutConfig, batchProcessor)-import OpenTelemetry.Attributes (AttributeLimits(..), defaultAttributeLimits)+import OpenTelemetry.Attributes (AttributeLimits (..), defaultAttributeLimits) import OpenTelemetry.Baggage (decodeBaggageHeader)-import qualified Data.ByteString.Char8 as B import qualified OpenTelemetry.Baggage as Baggage-import qualified Data.HashMap.Strict as H-import Data.Text.Encoding (decodeUtf8)+import OpenTelemetry.Context (Context)+import OpenTelemetry.Exporter (Exporter) import OpenTelemetry.Exporter.OTLP (loadExporterEnvironmentVariables, otlpExporter) import OpenTelemetry.Processor (Processor)-import OpenTelemetry.Resource.Service.Detector (detectService)-import OpenTelemetry.Resource.Process.Detector (detectProcess, detectProcessRuntime)-import OpenTelemetry.Resource.OperatingSystem.Detector (detectOperatingSystem)+import OpenTelemetry.Processor.Batch (BatchTimeoutConfig (..), batchProcessor, batchTimeoutConfig)+import OpenTelemetry.Propagator (Propagator)+import OpenTelemetry.Propagator.W3CBaggage (w3cBaggagePropagator)+import OpenTelemetry.Propagator.W3CTraceContext (w3cTraceContextPropagator)+import OpenTelemetry.Resource import OpenTelemetry.Resource.Host.Detector (detectHost)+import OpenTelemetry.Resource.OperatingSystem.Detector (detectOperatingSystem)+import OpenTelemetry.Resource.Process.Detector (detectProcess, detectProcessRuntime)+import OpenTelemetry.Resource.Service.Detector (detectService) import OpenTelemetry.Resource.Telemetry.Detector (detectTelemetry)+import OpenTelemetry.Trace.Core import OpenTelemetry.Trace.Id.Generator.Default (defaultIdGenerator)+import OpenTelemetry.Trace.Sampler (Sampler, alwaysOff, alwaysOn, parentBased, parentBasedOptions, traceIdRatioBased)+import System.Environment (lookupEnv)+import Text.Read (readMaybe) --- $use------ 1. Initialize a 'TracerProvider'.--- 2. Create a 'Tracer' for your system.--- 3. Add <https://hackage.haskell.org/packages/search?terms=hs-opentelemetry-instrumentation relevant pre-made instrumentation>--- 4. Annotate your internal functions using the 'inSpan' function or one of its variants. --- $tracerProvider--- --- A `TracerProvider` is key to using OpenTelemetry tracing. It is the data structure responsible for designating how spans are processed and exported------ You will generally only need to call 'initializeGlobalTracerProvider' on initialization,--- and 'shutdownTracerProvider' when your application exits.------ @------ main :: IO ()--- main = withTracer $ \tracer -> do--- -- your existing code here...--- pure ()--- where--- withTracer f = bracket --- -- Install the SDK, pulling configuration from the environment--- initializeGlobalTracerProvider--- -- Ensure that any spans that haven't been exported yet are flushed--- shutdownTracerProvider--- (\tracerProvider -> do--- -- Get a tracer so you can create spans--- tracer <- getTracer tracerProvider "your-app-name-or-subsystem"--- f tracer--- )------ @---+{- $use --- $envGeneral------ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | Name | Description | Default | Notes |--- +===========================+===============================================================================================================+================================================================================================================================================+================================================================================================================================================================================================================================================================================+--- | OTEL_RESOURCE_ATTRIBUTES | Key-value pairs to be used as resource attributes | See [Resource semantic conventions](resource/semantic_conventions/README.md#semantic-attributes-with-sdk-provided-default-value) for details. | See [Resource SDK](./resource/sdk.md#specifying-resource-information-via-an-environment-variable) for more details. |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | OTEL_SERVICE_NAME | Sets the value of the [`service.name`](./resource/semantic_conventions/README.md#service) resource attribute | | If `service.name` is also provided in `OTEL_RESOURCE_ATTRIBUTES`, then `OTEL_SERVICE_NAME` takes precedence. |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | OTEL_LOG_LEVEL | Log level used by the SDK logger | "info" | |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | OTEL_PROPAGATORS | Propagators to be used as a comma-separated list | "tracecontext,baggage" | Values MUST be deduplicated in order to register a `Propagator` only once. |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | OTEL_TRACES_SAMPLER | Sampler to be used for traces | "parentbased_always_on" | See [Sampling](./trace/sdk.md#sampling) |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--- | OTEL_TRACES_SAMPLER_ARG | String value to be used as the sampler argument | | The specified value will only be used if OTEL_TRACES_SAMPLER is set. Each Sampler type defines its own expected input, if any. Invalid or unrecognized input MUST be logged and MUST be otherwise ignored, i.e. the SDK MUST behave as if OTEL_TRACES_SAMPLER_ARG is not set. |--- +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ 1. Initialize a 'TracerProvider'.+ 2. Create a 'Tracer' for your system.+ 3. Add <https://hackage.haskell.org/packages/search?terms=hs-opentelemetry-instrumentation relevant pre-made instrumentation>+ 4. Annotate your internal functions using the 'inSpan' function or one of its variants.+-} --- $envBsp--- +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------+--- | Name | Description | Default | Notes |--- +=================================+=================================================+==========+========================================================+--- | OTEL_BSP_SCHEDULE_DELAY | Delay interval between two consecutive exports | 5000 | |--- +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------+--- | OTEL_BSP_EXPORT_TIMEOUT | Maximum allowed time to export data | 30000 | |--- +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------+--- | OTEL_BSP_MAX_QUEUE_SIZE | Maximum queue size | 2048 | |--- +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------+--- | OTEL_BSP_MAX_EXPORT_BATCH_SIZE | Maximum batch size | 512 | Must be less than or equal to OTEL_BSP_MAX_QUEUE_SIZE |--- +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------+ --- $envAttributeLimits--- +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------+--- | Name | Description | Default | Notes |--- +====================================+=======================================+==========+===================================================================================+--- | OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT | Maximum allowed attribute value size | | Empty value is treated as infinity. Non-integer and negative values are invalid. |--- +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_ATTRIBUTE_COUNT_LIMIT | Maximum allowed span attribute count | 128 | |--- +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------++{- $tracerProvider --- $envSpanLimits--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | Name | Description | Default | Notes |--- +=========================================+=================================================+==========+===================================================================================+--- | OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT | Maximum allowed attribute value size | | Empty value is treated as infinity. Non-integer and negative values are invalid. |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT | Maximum allowed span attribute count | 128 | |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_SPAN_EVENT_COUNT_LIMIT | Maximum allowed span event count | 128 | |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_SPAN_LINK_COUNT_LIMIT | Maximum allowed span link count | 128 | |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT | Maximum allowed attribute per span event count | 128 | |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------+--- | OTEL_LINK_ATTRIBUTE_COUNT_LIMIT | Maximum allowed attribute per span link count | 128 | |--- +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ A `TracerProvider` is key to using OpenTelemetry tracing. It is the data structure responsible for designating how spans are processed and exported + You will generally only need to call 'initializeGlobalTracerProvider' on initialization,+ and 'shutdownTracerProvider' when your application exits. + @++ main :: IO ()+ main = withTracer $ \tracer -> do+ -- your existing code here...+ pure ()+ where+ withTracer f = bracket+ -- Install the SDK, pulling configuration from the environment+ initializeGlobalTracerProvider+ -- Ensure that any spans that haven't been exported yet are flushed+ shutdownTracerProvider+ (\tracerProvider -> do+ -- Get a tracer so you can create spans+ tracer <- getTracer tracerProvider "your-app-name-or-subsystem"+ f tracer+ )++ @+-}+++{- $envGeneral++ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | Name | Description | Default | Notes |+ +===========================+===============================================================================================================+================================================================================================================================================+================================================================================================================================================================================================================================================================================++ | OTEL_RESOURCE_ATTRIBUTES | Key-value pairs to be used as resource attributes | See [Resource semantic conventions](resource/semantic_conventions/README.md#semantic-attributes-with-sdk-provided-default-value) for details. | See [Resource SDK](./resource/sdk.md#specifying-resource-information-via-an-environment-variable) for more details. |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | OTEL_SERVICE_NAME | Sets the value of the [`service.name`](./resource/semantic_conventions/README.md#service) resource attribute | | If `service.name` is also provided in `OTEL_RESOURCE_ATTRIBUTES`, then `OTEL_SERVICE_NAME` takes precedence. |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | OTEL_LOG_LEVEL | Log level used by the SDK logger | "info" | |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | OTEL_PROPAGATORS | Propagators to be used as a comma-separated list | "tracecontext,baggage" | Values MUST be deduplicated in order to register a `Propagator` only once. |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | OTEL_TRACES_SAMPLER | Sampler to be used for traces | "parentbased_always_on" | See [Sampling](./trace/sdk.md#sampling) |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++ | OTEL_TRACES_SAMPLER_ARG | String value to be used as the sampler argument | | The specified value will only be used if OTEL_TRACES_SAMPLER is set. Each Sampler type defines its own expected input, if any. Invalid or unrecognized input MUST be logged and MUST be otherwise ignored, i.e. the SDK MUST behave as if OTEL_TRACES_SAMPLER_ARG is not set. |+ +---------------------------+---------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++-}+++{- $envBsp+ +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------++ | Name | Description | Default | Notes |+ +=================================+=================================================+==========+========================================================++ | OTEL_BSP_SCHEDULE_DELAY | Delay interval between two consecutive exports | 5000 | |+ +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------++ | OTEL_BSP_EXPORT_TIMEOUT | Maximum allowed time to export data | 30000 | |+ +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------++ | OTEL_BSP_MAX_QUEUE_SIZE | Maximum queue size | 2048 | |+ +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------++ | OTEL_BSP_MAX_EXPORT_BATCH_SIZE | Maximum batch size | 512 | Must be less than or equal to OTEL_BSP_MAX_QUEUE_SIZE |+ +---------------------------------+-------------------------------------------------+----------+--------------------------------------------------------++-}+++{- $envAttributeLimits+ +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------++ | Name | Description | Default | Notes |+ +====================================+=======================================+==========+===================================================================================++ | OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT | Maximum allowed attribute value size | | Empty value is treated as infinity. Non-integer and negative values are invalid. |+ +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_ATTRIBUTE_COUNT_LIMIT | Maximum allowed span attribute count | 128 | |+ +------------------------------------+---------------------------------------+----------+-----------------------------------------------------------------------------------++-}+++{- $envSpanLimits+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | Name | Description | Default | Notes |+ +=========================================+=================================================+==========+===================================================================================++ | OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT | Maximum allowed attribute value size | | Empty value is treated as infinity. Non-integer and negative values are invalid. |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT | Maximum allowed span attribute count | 128 | |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_SPAN_EVENT_COUNT_LIMIT | Maximum allowed span event count | 128 | |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_SPAN_LINK_COUNT_LIMIT | Maximum allowed span link count | 128 | |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT | Maximum allowed attribute per span event count | 128 | |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++ | OTEL_LINK_ATTRIBUTE_COUNT_LIMIT | Maximum allowed attribute per span link count | 128 | |+ +-----------------------------------------+-------------------------------------------------+----------+-----------------------------------------------------------------------------------++-}++ knownPropagators :: [(T.Text, Propagator Context RequestHeaders ResponseHeaders)] knownPropagators = [ ("tracecontext", w3cTraceContextPropagator)@@ -273,35 +292,42 @@ , ("jaeger", error "Jaeger not yet implemented") ] + -- TODO, actually implement a registry systme readRegisteredPropagators :: IO [(T.Text, Propagator Context RequestHeaders ResponseHeaders)] readRegisteredPropagators = pure knownPropagators --- | Create a new 'TracerProvider' and set it as the global--- tracer provider. This pulls all configuration from environment--- variables. The full list of environment variables supported is--- specified in the configuration section of this module's documentation.--- --- Note however, that 3rd-party span processors, exporters, sampling strategies,--- etc. may have their own set of environment-based configuration values that they--- utilize.++{- | Create a new 'TracerProvider' and set it as the global+ tracer provider. This pulls all configuration from environment+ variables. The full list of environment variables supported is+ specified in the configuration section of this module's documentation.++ Note however, that 3rd-party span processors, exporters, sampling strategies,+ etc. may have their own set of environment-based configuration values that they+ utilize.+-} initializeGlobalTracerProvider :: IO TracerProvider initializeGlobalTracerProvider = do t <- initializeTracerProvider setGlobalTracerProvider t pure t + initializeTracerProvider :: IO TracerProvider initializeTracerProvider = do (processors, opts) <- getTracerProviderInitializationOptions createTracerProvider processors opts + getTracerProviderInitializationOptions :: IO ([Processor], TracerProviderOptions) getTracerProviderInitializationOptions = getTracerProviderInitializationOptions' (mempty :: Resource 'Nothing) --- | Detect ptions for initializing a tracer provider from the app environment, taking additional supported resources as well.------ @since 0.0.3.1++{- | Detect options for initializing a tracer provider from the app environment, taking additional supported resources as well.++ @since 0.0.3.1+-} getTracerProviderInitializationOptions' :: (ResourceMerge 'Nothing any ~ 'Nothing) => Resource any -> IO ([Processor], TracerProviderOptions) getTracerProviderInitializationOptions' rs = do sampler <- detectSampler@@ -316,18 +342,20 @@ processors <- case exporters of [] -> do pure []- e:_ -> do+ e : _ -> do pure <$> batchProcessor processorConf e- let providerOpts = emptyTracerProviderOptions- { tracerProviderOptionsIdGenerator = defaultIdGenerator- , tracerProviderOptionsSampler = sampler- , tracerProviderOptionsAttributeLimits = attrLimits- , tracerProviderOptionsSpanLimits = spanLimits- , tracerProviderOptionsPropagators = propagators- , tracerProviderOptionsResources = materializeResources allRs- }+ let providerOpts =+ emptyTracerProviderOptions+ { tracerProviderOptionsIdGenerator = defaultIdGenerator+ , tracerProviderOptionsSampler = sampler+ , tracerProviderOptionsAttributeLimits = attrLimits+ , tracerProviderOptionsSpanLimits = spanLimits+ , tracerProviderOptionsPropagators = propagators+ , tracerProviderOptionsResources = materializeResources allRs+ } pure (processors, providerOpts) + detectPropagators :: IO (Propagator Context RequestHeaders ResponseHeaders) detectPropagators = do registeredPropagators <- readRegisteredPropagators@@ -341,32 +369,39 @@ -- TODO log warn notFound pure $ mconcat propagators + knownSamplers :: [(T.Text, Maybe T.Text -> Maybe Sampler)] knownSamplers = [ ("always_on", const $ pure alwaysOn) , ("always_off", const $ pure alwaysOff)- , ("traceidratio", \case- Nothing -> Nothing - Just val -> case readMaybe (T.unpack val) of+ ,+ ( "traceidratio"+ , \case Nothing -> Nothing- Just ratioVal -> pure $ traceIdRatioBased ratioVal+ Just val -> case readMaybe (T.unpack val) of+ Nothing -> Nothing+ Just ratioVal -> pure $ traceIdRatioBased ratioVal ) , ("parentbased_always_on", const $ pure $ parentBased $ parentBasedOptions alwaysOn) , ("parentbased_always_off", const $ pure $ parentBased $ parentBasedOptions alwaysOff)- , ("parentbased_traceidratio", \case- Nothing -> Nothing - Just val -> case readMaybe (T.unpack val) of+ ,+ ( "parentbased_traceidratio"+ , \case Nothing -> Nothing- Just ratioVal -> pure $ parentBased $ parentBasedOptions $ traceIdRatioBased ratioVal- )+ Just val -> case readMaybe (T.unpack val) of+ Nothing -> Nothing+ Just ratioVal -> pure $ parentBased $ parentBasedOptions $ traceIdRatioBased ratioVal+ ) ] + -- TODO MUST log invalid arg --- | Detect a sampler from the app environment. If no sampler is specified,--- the parentbased sampler is used.------ @since 0.0.3.3+{- | Detect a sampler from the app environment. If no sampler is specified,+ the parentbased sampler is used.++ @since 0.0.3.3+-} detectSampler :: IO Sampler detectSampler = do envSampler <- lookupEnv "OTEL_TRACES_SAMPLER"@@ -377,37 +412,47 @@ samplerConstructor (T.pack <$> envArg) pure sampler + detectBatchProcessorConfig :: IO BatchTimeoutConfig-detectBatchProcessorConfig = BatchTimeoutConfig - <$> readEnvDefault "OTEL_BSP_MAX_QUEUE_SIZE" (maxQueueSize batchTimeoutConfig)- <*> readEnvDefault "OTEL_BSP_SCHEDULE_DELAY" (scheduledDelayMillis batchTimeoutConfig)- <*> readEnvDefault "OTEL_BSP_EXPORT_TIMEOUT" (exportTimeoutMillis batchTimeoutConfig)- <*> readEnvDefault "OTEL_BSP_MAX_EXPORT_BATCH_SIZE" (maxExportBatchSize batchTimeoutConfig)+detectBatchProcessorConfig =+ BatchTimeoutConfig+ <$> readEnvDefault "OTEL_BSP_MAX_QUEUE_SIZE" (maxQueueSize batchTimeoutConfig)+ <*> readEnvDefault "OTEL_BSP_SCHEDULE_DELAY" (scheduledDelayMillis batchTimeoutConfig)+ <*> readEnvDefault "OTEL_BSP_EXPORT_TIMEOUT" (exportTimeoutMillis batchTimeoutConfig)+ <*> readEnvDefault "OTEL_BSP_MAX_EXPORT_BATCH_SIZE" (maxExportBatchSize batchTimeoutConfig) + detectAttributeLimits :: IO AttributeLimits-detectAttributeLimits = AttributeLimits- <$> readEnvDefault "OTEL_ATTRIBUTE_COUNT_LIMIT" (attributeCountLimit defaultAttributeLimits)- <*> ((>>= readMaybe) <$> lookupEnv "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT")+detectAttributeLimits =+ AttributeLimits+ <$> readEnvDefault "OTEL_ATTRIBUTE_COUNT_LIMIT" (attributeCountLimit defaultAttributeLimits)+ <*> ((>>= readMaybe) <$> lookupEnv "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT") + detectSpanLimits :: IO SpanLimits-detectSpanLimits = SpanLimits- <$> readEnv "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"- <*> readEnv "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"- <*> readEnv "OTEL_SPAN_EVENT_COUNT_LIMIT"- <*> readEnv "OTEL_SPAN_LINK_COUNT_LIMIT"- <*> readEnv "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"- <*> readEnv "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"+detectSpanLimits =+ SpanLimits+ <$> readEnv "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"+ <*> readEnv "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"+ <*> readEnv "OTEL_SPAN_EVENT_COUNT_LIMIT"+ <*> readEnv "OTEL_SPAN_LINK_COUNT_LIMIT"+ <*> readEnv "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"+ <*> readEnv "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT" + knownExporters :: [(T.Text, IO (Exporter ImmutableSpan))] knownExporters =- [ ("otlp", do- otlpConfig <- loadExporterEnvironmentVariables- otlpExporter otlpConfig+ [+ ( "otlp"+ , do+ otlpConfig <- loadExporterEnvironmentVariables+ otlpExporter otlpConfig ) , ("jaeger", error "Jaeger exporter not implemented") , ("zipkin", error "Zipkin exporter not implemented") ] + -- TODO, support multiple exporters detectExporters :: IO [Exporter ImmutableSpan] detectExporters = do@@ -421,9 +466,10 @@ -- TODO, notFound logging sequence exporterIntializers - -- -- detectMetricsExporterSelection :: _- -- -- TODO other metrics stuff +-- -- detectMetricsExporterSelection :: _+-- -- TODO other metrics stuff+ detectResourceAttributes :: IO [(T.Text, Attribute)] detectResourceAttributes = do mEnv <- lookupEnv "OTEL_RESOURCE_ATTRIBUTES"@@ -435,32 +481,36 @@ putStrLn err pure [] Right ok ->- pure- $ map (\(k, v) -> (decodeUtf8 $ Baggage.tokenValue k, toAttribute $ Baggage.value v))- $ H.toList- $ Baggage.values ok+ pure $+ map (\(k, v) -> (decodeUtf8 $ Baggage.tokenValue k, toAttribute $ Baggage.value v)) $+ H.toList $+ Baggage.values ok -readEnvDefault :: forall a. Read a => String -> a -> IO a ++readEnvDefault :: forall a. Read a => String -> a -> IO a readEnvDefault k defaultValue = fromMaybe defaultValue . (>>= readMaybe) <$> lookupEnv k + readEnv :: forall a. Read a => String -> IO (Maybe a) readEnv k = (>>= readMaybe) <$> lookupEnv k --- | Use all built-in resource detectors to populate resource information.------ Currently used detectors include:------ - 'detectService'--- - 'detectProcess'--- - 'detectOperatingSystem'--- - 'detectHost'--- - 'detectTelemetry'--- - 'detectProcessRuntime'------ This list will grow in the future as more detectors are implemented.------ @since 0.0.1.0++{- | Use all built-in resource detectors to populate resource information.++ Currently used detectors include:++ - 'detectService'+ - 'detectProcess'+ - 'detectOperatingSystem'+ - 'detectHost'+ - 'detectTelemetry'+ - 'detectProcessRuntime'++ This list will grow in the future as more detectors are implemented.++ @since 0.0.1.0+-} detectBuiltInResources :: IO (Resource 'Nothing) detectBuiltInResources = do svc <- detectService@@ -468,10 +518,10 @@ osInfo <- detectOperatingSystem host <- detectHost let rs =- toResource svc `mergeResources`- toResource detectTelemetry `mergeResources`- toResource detectProcessRuntime `mergeResources`- toResource processInfo `mergeResources`- toResource osInfo `mergeResources`- toResource host+ toResource svc+ `mergeResources` toResource detectTelemetry+ `mergeResources` toResource detectProcessRuntime+ `mergeResources` toResource processInfo+ `mergeResources` toResource osInfo+ `mergeResources` toResource host pure rs
src/OpenTelemetry/Trace/Id/Generator/Default.hs view
@@ -1,44 +1,54 @@ {-# LANGUAGE CPP #-}+ -------------------------------------------------------------------------------- |--- Module : OpenTelemetry.Trace.Id.Generator.Default--- Copyright : (c) Ian Duncan, 2021--- License : BSD-3--- Maintainer : Ian Duncan--- Stability : experimental--- Portability : non-portable (GHC extensions)------ A reasonably performant out of the box implementation of random span and trace id generation.---+ ------------------------------------------------------------------------------module OpenTelemetry.Trace.Id.Generator.Default - ( defaultIdGenerator- ) where +{- |+ Module : OpenTelemetry.Trace.Id.Generator.Default+ Copyright : (c) Ian Duncan, 2021+ License : BSD-3+ Maintainer : Ian Duncan+ Stability : experimental+ Portability : non-portable (GHC extensions)++ A reasonably performant out of the box implementation of random span and trace id generation.+-}+module OpenTelemetry.Trace.Id.Generator.Default (+ defaultIdGenerator,+) where++import OpenTelemetry.Trace.Id.Generator (IdGenerator (..))+import System.IO.Unsafe (unsafePerformIO) import System.Random.MWC++ #if MIN_VERSION_random(1,2,0) import System.Random.Stateful #else import Data.ByteString.Random #endif-import System.IO.Unsafe (unsafePerformIO)-import OpenTelemetry.Trace.Id.Generator (IdGenerator(..)) --- | The default generator for trace and span ids.------ @since 0.1.0.0++{- | The default generator for trace and span ids.++ @since 0.1.0.0+-} defaultIdGenerator :: IdGenerator+#if MIN_VERSION_random(1,2,0) defaultIdGenerator = unsafePerformIO $ do g <- createSystemRandom-#if MIN_VERSION_random(1,2,0) pure $ IdGenerator { generateSpanIdBytes = uniformByteStringM 8 g , generateTraceIdBytes = uniformByteStringM 16 g }+{-# NOINLINE defaultIdGenerator #-} #else+defaultIdGenerator = unsafePerformIO $ do+ g <- createSystemRandom pure $ IdGenerator { generateSpanIdBytes = randomGen g 8 , generateTraceIdBytes = randomGen g 16 }-#endif {-# NOINLINE defaultIdGenerator #-}+#endif
test/OpenTelemetry/BaggageSpec.hs view
@@ -2,6 +2,7 @@ import Test.Hspec + spec :: Spec spec = describe "Baggage" $ do specify "Basic support" pending
test/OpenTelemetry/ContextSpec.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE OverloadedStrings #-}+ module OpenTelemetry.ContextSpec where+ import Control.Monad import Data.Maybe import OpenTelemetry.Context@@ -7,6 +9,7 @@ import Test.Hspec import Prelude hiding (lookup) + spec :: Spec spec = describe "Context" $ do describe "Create Context Key" $ do@@ -60,4 +63,3 @@ specify "Setter argument" pending specify "Getter argument" pending specify "Getter argument returning keys" pending-
test/OpenTelemetry/ResourceSpec.hs view
@@ -2,6 +2,7 @@ import Test.Hspec + spec :: Spec spec = describe "Resource" $ do specify "Create from Attributes" pending
test/OpenTelemetry/TraceSpec.hs view
@@ -1,10 +1,11 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-}+ module OpenTelemetry.TraceSpec where import Control.Monad-import Data.Int import Data.IORef+import Data.Int import Data.Text (Text) import qualified OpenTelemetry.Context as Context import OpenTelemetry.Trace@@ -13,15 +14,16 @@ import OpenTelemetry.Trace.Id.Generator import OpenTelemetry.Trace.Id.Generator.Default import qualified OpenTelemetry.Trace.TraceState as TraceState-import Test.Hspec import System.Clock+import Test.Hspec + asIO :: IO a -> IO a asIO = id + spec :: Spec spec = describe "Trace" $ do- describe "TracerProvider" $ do specify "Create TracerProvider" $ do void (createTracerProvider [] (emptyTracerProviderOptions :: TracerProviderOptions) :: IO TracerProvider)@@ -63,15 +65,16 @@ (Right goodTrace) = bytesToTraceId "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1" (Right badSpan) = bytesToSpanId "\0\0\0\0\0\0\0\0" (Right badTrace) = bytesToTraceId "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"- validSpan = SpanContext- { traceFlags = defaultTraceFlags- , isRemote = False- , traceState = TraceState.empty- , spanId = goodSpan- , traceId = goodTrace- }+ validSpan =+ SpanContext+ { traceFlags = defaultTraceFlags+ , isRemote = False+ , traceState = TraceState.empty+ , spanId = goodSpan+ , traceId = goodTrace+ } validSpan `shouldSatisfy` isValid- (validSpan { spanId = badSpan, traceId = badTrace }) `shouldSatisfy` (not . isValid)+ (validSpan {spanId = badSpan, traceId = badTrace}) `shouldSatisfy` (not . isValid) specify "IsRemote" pending specify "Conforms to the W3C TraceContext spec" pending describe "Span" $ do@@ -136,7 +139,6 @@ i <- unsafeReadSpan s spanStatus i `shouldBe` Ok - specify "Safe for concurrent calls" pending specify "events collection size limit" pending specify "attribute collection size limit" pending@@ -148,7 +150,7 @@ let t = makeTracer p "woo" tracerOptions s <- createSpan t Context.empty "create_root_span" defaultSpanArguments addAttribute s "attr" (1.0 :: Double)- + specify "Set order preserved" pending specify "String type" $ asIO $ do p <- getGlobalTracerProvider@@ -178,26 +180,26 @@ p <- getGlobalTracerProvider let t = makeTracer p "woo" tracerOptions s <- createSpan t Context.empty "create_root_span" defaultSpanArguments- addAttribute s "attr" [(1 :: Int64)..10]+ addAttribute s "attr" [(1 :: Int64) .. 10] specify "Unicode support for keys and string values" $ asIO $ do p <- getGlobalTracerProvider let t = makeTracer p "woo" tracerOptions s <- createSpan t Context.empty "create_root_span" defaultSpanArguments addAttribute s "🚀" ("🚀" :: Text)- -- TODO actually get attributes out-+ -- TODO actually get attributes out describe "Span events" $ do specify "AddEvent" $ asIO $ do p <- getGlobalTracerProvider let t = makeTracer p "woo" tracerOptions s <- createSpan t Context.empty "create_root_span" defaultSpanArguments- addEvent s $ NewEvent- { newEventName = "EVENT"- , newEventAttributes = []- , newEventTimestamp = Nothing- }+ addEvent s $+ NewEvent+ { newEventName = "EVENT"+ , newEventAttributes = []+ , newEventTimestamp = Nothing+ } specify "Add order preserved" pending specify "Safe for concurrent calls" pending @@ -215,6 +217,3 @@ specify "SpanLimits" pending specify "Built-in Processors implement ForceFlush spec" pending specify "Attribute Limits" pending---
test/Spec.hs view
@@ -1,9 +1,10 @@-import OpenTelemetry.Trace (initializeGlobalTracerProvider) import qualified OpenTelemetry.BaggageSpec as BaggageSpec import qualified OpenTelemetry.ContextSpec as ContextSpec-import qualified OpenTelemetry.TraceSpec as TraceSpec import qualified OpenTelemetry.ResourceSpec as ResourceSpec+import OpenTelemetry.Trace (initializeGlobalTracerProvider)+import qualified OpenTelemetry.TraceSpec as TraceSpec import Test.Hspec+ main :: IO () main = do