module Ecluse.Telemetry (
TelemetrySwitch (..),
parseTelemetrySwitch,
renderTelemetrySwitch,
Telemetry (..),
TelemetryProviders (..),
telemetryDisabled,
telemetryEnabled,
telemetryTracerProvider,
telemetryMeterProvider,
withTelemetry,
observeSpanExporter,
observeMetricExporter,
) where
import Katip (LogEnv)
import OpenTelemetry.Environment (lookupBooleanEnv)
import OpenTelemetry.Exporter.Metric (MetricExporter (..))
import OpenTelemetry.Exporter.OTLP.Span (loadExporterEnvironmentVariables, otlpExporter)
import OpenTelemetry.Exporter.Span (SpanExporter (..))
import OpenTelemetry.Log (initializeGlobalLoggerProvider, shutdownLoggerProvider)
import OpenTelemetry.Metric (
MeterProvider (..),
PeriodicMetricReaderHandle (..),
createMeterProvider,
defaultSdkMeterProviderOptions,
forkPeriodicMetricReader,
noopMeterProvider,
periodicMetricReaderOptionsFromEnv,
resolveMetricExporter,
setGlobalMeterProvider,
shutdownMeterProvider,
)
import OpenTelemetry.Registry (registerSpanExporterFactory)
import OpenTelemetry.Resource (materializeResources, mergeResources, mkResource)
import OpenTelemetry.Resource.Detect (detectBuiltInResources, detectResourceAttributes)
import OpenTelemetry.SDK (OTelSignals (..))
import OpenTelemetry.Trace (TracerProvider, initializeGlobalTracerProvider, shutdownTracerProvider)
import UnliftIO (bracket)
import UnliftIO.Exception (catchAny)
import Ecluse.Telemetry.Resolve (
ExportFailureSink,
exportFailureSink,
installExportErrorHandler,
observeExportResult,
)
import Ecluse.Core.Wire (WireVocab (..), parseWire, renderWire)
data TelemetrySwitch
=
TelemetryOff
|
TelemetryOn
deriving stock (TelemetrySwitch -> TelemetrySwitch -> Bool
(TelemetrySwitch -> TelemetrySwitch -> Bool)
-> (TelemetrySwitch -> TelemetrySwitch -> Bool)
-> Eq TelemetrySwitch
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TelemetrySwitch -> TelemetrySwitch -> Bool
== :: TelemetrySwitch -> TelemetrySwitch -> Bool
$c/= :: TelemetrySwitch -> TelemetrySwitch -> Bool
/= :: TelemetrySwitch -> TelemetrySwitch -> Bool
Eq, Int -> TelemetrySwitch -> ShowS
[TelemetrySwitch] -> ShowS
TelemetrySwitch -> String
(Int -> TelemetrySwitch -> ShowS)
-> (TelemetrySwitch -> String)
-> ([TelemetrySwitch] -> ShowS)
-> Show TelemetrySwitch
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TelemetrySwitch -> ShowS
showsPrec :: Int -> TelemetrySwitch -> ShowS
$cshow :: TelemetrySwitch -> String
show :: TelemetrySwitch -> String
$cshowList :: [TelemetrySwitch] -> ShowS
showList :: [TelemetrySwitch] -> ShowS
Show)
instance WireVocab TelemetrySwitch where
wireKind :: Text
wireKind = Text
"telemetry switch"
wireTable :: NonEmpty (TelemetrySwitch, Text)
wireTable =
(TelemetrySwitch
TelemetryOn, Text
"on")
(TelemetrySwitch, Text)
-> [(TelemetrySwitch, Text)] -> NonEmpty (TelemetrySwitch, Text)
forall a. a -> [a] -> NonEmpty a
:| [(TelemetrySwitch
TelemetryOff, Text
"off")]
parseTelemetrySwitch :: Text -> Either Text TelemetrySwitch
parseTelemetrySwitch :: Text -> Either Text TelemetrySwitch
parseTelemetrySwitch = Text -> Either Text TelemetrySwitch
forall a. WireVocab a => Text -> Either Text a
parseWire
renderTelemetrySwitch :: TelemetrySwitch -> Text
renderTelemetrySwitch :: TelemetrySwitch -> Text
renderTelemetrySwitch = TelemetrySwitch -> Text
forall a. (Eq a, WireVocab a) => a -> Text
renderWire
data Telemetry
=
TelemetryDisabled
|
TelemetryEnabled TelemetryProviders
data TelemetryProviders = TelemetryProviders
{ TelemetryProviders -> TracerProvider
tpTracerProvider :: TracerProvider
, TelemetryProviders -> MeterProvider
tpMeterProvider :: MeterProvider
}
telemetryDisabled :: Telemetry
telemetryDisabled :: Telemetry
telemetryDisabled = Telemetry
TelemetryDisabled
telemetryEnabled :: OTelSignals -> Telemetry
telemetryEnabled :: OTelSignals -> Telemetry
telemetryEnabled OTelSignals
signals =
TelemetryProviders -> Telemetry
TelemetryEnabled
TelemetryProviders
{ tpTracerProvider :: TracerProvider
tpTracerProvider = OTelSignals -> TracerProvider
otelTracerProvider OTelSignals
signals
, tpMeterProvider :: MeterProvider
tpMeterProvider = OTelSignals -> MeterProvider
otelMeterProvider OTelSignals
signals
}
telemetryTracerProvider :: Telemetry -> Maybe TracerProvider
telemetryTracerProvider :: Telemetry -> Maybe TracerProvider
telemetryTracerProvider = \case
Telemetry
TelemetryDisabled -> Maybe TracerProvider
forall a. Maybe a
Nothing
TelemetryEnabled TelemetryProviders
providers -> TracerProvider -> Maybe TracerProvider
forall a. a -> Maybe a
Just (TelemetryProviders -> TracerProvider
tpTracerProvider TelemetryProviders
providers)
telemetryMeterProvider :: Telemetry -> Maybe MeterProvider
telemetryMeterProvider :: Telemetry -> Maybe MeterProvider
telemetryMeterProvider = \case
Telemetry
TelemetryDisabled -> Maybe MeterProvider
forall a. Maybe a
Nothing
TelemetryEnabled TelemetryProviders
providers -> MeterProvider -> Maybe MeterProvider
forall a. a -> Maybe a
Just (TelemetryProviders -> MeterProvider
tpMeterProvider TelemetryProviders
providers)
withTelemetry :: TelemetrySwitch -> LogEnv -> (Telemetry -> IO a) -> IO a
withTelemetry :: forall a. TelemetrySwitch -> LogEnv -> (Telemetry -> IO a) -> IO a
withTelemetry TelemetrySwitch
switch LogEnv
logEnv Telemetry -> IO a
use = case TelemetrySwitch
switch of
TelemetrySwitch
TelemetryOff -> Telemetry -> IO a
use Telemetry
telemetryDisabled
TelemetrySwitch
TelemetryOn -> do
sink <- LogEnv -> IO ExportFailureSink
exportFailureSink LogEnv
logEnv
installExportErrorHandler sink
registerObservedSpanExporter sink
bracket (initializeObservedOpenTelemetry sink) otelShutdown (use . telemetryEnabled)
observeSpanExporter :: ExportFailureSink -> SpanExporter -> SpanExporter
observeSpanExporter :: ExportFailureSink -> SpanExporter -> SpanExporter
observeSpanExporter ExportFailureSink
sink SpanExporter
inner =
SpanExporter
inner
{ spanExporterExport = \HashMap InstrumentationLibrary (Vector ImmutableSpan)
completedSpans -> do
result <- SpanExporter
-> HashMap InstrumentationLibrary (Vector ImmutableSpan)
-> IO ExportResult
spanExporterExport SpanExporter
inner HashMap InstrumentationLibrary (Vector ImmutableSpan)
completedSpans
observeExportResult sink "span" result
pure result
}
observeMetricExporter :: ExportFailureSink -> MetricExporter -> MetricExporter
observeMetricExporter :: ExportFailureSink -> MetricExporter -> MetricExporter
observeMetricExporter ExportFailureSink
sink MetricExporter
inner =
MetricExporter
inner
{ metricExporterExport = \Vector ResourceMetricsExport
batches -> do
result <- MetricExporter -> Vector ResourceMetricsExport -> IO ExportResult
metricExporterExport MetricExporter
inner Vector ResourceMetricsExport
batches
observeExportResult sink "metric" result
pure result
}
registerObservedSpanExporter :: ExportFailureSink -> IO ()
registerObservedSpanExporter :: ExportFailureSink -> IO ()
registerObservedSpanExporter ExportFailureSink
sink =
Text -> IO SpanExporter -> IO ()
registerSpanExporterFactory
Text
"otlp"
(ExportFailureSink -> SpanExporter -> SpanExporter
observeSpanExporter ExportFailureSink
sink (SpanExporter -> SpanExporter)
-> IO SpanExporter -> IO SpanExporter
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (OTLPExporterConfig -> IO SpanExporter
forall (m :: * -> *).
MonadIO m =>
OTLPExporterConfig -> m SpanExporter
otlpExporter (OTLPExporterConfig -> IO SpanExporter)
-> IO OTLPExporterConfig -> IO SpanExporter
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO OTLPExporterConfig
forall (m :: * -> *). MonadIO m => m OTLPExporterConfig
loadExporterEnvironmentVariables))
initializeObservedOpenTelemetry :: ExportFailureSink -> IO OTelSignals
initializeObservedOpenTelemetry :: ExportFailureSink -> IO OTelSignals
initializeObservedOpenTelemetry ExportFailureSink
sink = do
tracerProvider <- IO TracerProvider
initializeGlobalTracerProvider
meterProvider <- initializeObservedMeterProvider sink
loggerProvider <- initializeGlobalLoggerProvider
let shutdown = do
IO ShutdownResult -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (TracerProvider -> Maybe Int -> IO ShutdownResult
forall (m :: * -> *).
MonadIO m =>
TracerProvider -> Maybe Int -> m ShutdownResult
shutdownTracerProvider TracerProvider
tracerProvider Maybe Int
forall a. Maybe a
Nothing) IO () -> (SomeException -> IO ()) -> IO ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
`catchAny` IO () -> SomeException -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass
IO ShutdownResult -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (MeterProvider -> Maybe Int -> IO ShutdownResult
shutdownMeterProvider MeterProvider
meterProvider Maybe Int
forall a. Maybe a
Nothing) IO () -> (SomeException -> IO ()) -> IO ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
`catchAny` IO () -> SomeException -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass
IO ShutdownResult -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (LoggerProvider -> Maybe Int -> IO ShutdownResult
forall (m :: * -> *).
MonadIO m =>
LoggerProvider -> Maybe Int -> m ShutdownResult
shutdownLoggerProvider LoggerProvider
loggerProvider Maybe Int
forall a. Maybe a
Nothing) IO () -> (SomeException -> IO ()) -> IO ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
`catchAny` IO () -> SomeException -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass
pure
OTelSignals
{ otelTracerProvider = tracerProvider
, otelMeterProvider = meterProvider
, otelLoggerProvider = loggerProvider
, otelPropagators = mempty
, otelShutdown = shutdown
}
initializeObservedMeterProvider :: ExportFailureSink -> IO MeterProvider
initializeObservedMeterProvider :: ExportFailureSink -> IO MeterProvider
initializeObservedMeterProvider ExportFailureSink
sink = do
disabled <- String -> IO Bool
lookupBooleanEnv String
"OTEL_SDK_DISABLED"
if disabled
then noopMeterProvider <$ setGlobalMeterProvider noopMeterProvider
else do
exporter <- observeMetricExporter sink <$> resolveMetricExporter
readerOptions <- periodicMetricReaderOptionsFromEnv
builtInResources <- detectBuiltInResources
envResources <- mkResource . map Just <$> detectResourceAttributes
let resources = Resource -> MaterializedResources
materializeResources (Resource -> Resource -> Resource
mergeResources Resource
envResources Resource
builtInResources)
(provider, env) <- createMeterProvider resources defaultSdkMeterProviderOptions
readerHandle <- forkPeriodicMetricReader env exporter readerOptions
let provider' = PeriodicMetricReaderHandle -> MeterProvider -> MeterProvider
stopReaderOnShutdown PeriodicMetricReaderHandle
readerHandle MeterProvider
provider
setGlobalMeterProvider provider'
pure provider'
stopReaderOnShutdown :: PeriodicMetricReaderHandle -> MeterProvider -> MeterProvider
stopReaderOnShutdown :: PeriodicMetricReaderHandle -> MeterProvider -> MeterProvider
stopReaderOnShutdown PeriodicMetricReaderHandle
readerHandle MeterProvider
provider =
MeterProvider
provider
{ meterProviderShutdown = \Maybe Int
timeout -> do
PeriodicMetricReaderHandle -> IO ()
stopPeriodicMetricReader PeriodicMetricReaderHandle
readerHandle
MeterProvider -> Maybe Int -> IO ShutdownResult
meterProviderShutdown MeterProvider
provider Maybe Int
timeout
}