module Ecluse.Telemetry.Resolve (
ResolvedTelemetry (..),
TelemetryEndpoint (..),
EndpointSource (..),
resolveTelemetry,
otelEnvironmentOverrides,
ThrottleState (..),
ThrottleEmit (..),
initialThrottle,
throttleInterval,
throttleStep,
ExportFailureSink,
newExportFailureSink,
exportFailureSink,
routeExportFailure,
observeExportResult,
installExportErrorHandler,
prepareTelemetry,
) where
import Data.List (lookup)
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Data.Time (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime)
import System.Environment (setEnv)
import Katip (LogEnv, Severity (WarningS), logFM, ls)
import Katip.Monadic (runKatipContextT)
import OpenTelemetry.Exporter.Span (ExportResult (..))
import OpenTelemetry.Internal.Logging (setGlobalErrorHandler)
import Ecluse.Core.Text (nonBlank)
import Ecluse.Log (moduleField)
data EndpointSource
=
FromDdAgentHost
|
FromOtelEndpoint
|
DefaultedEndpoint
deriving stock (EndpointSource -> EndpointSource -> Bool
(EndpointSource -> EndpointSource -> Bool)
-> (EndpointSource -> EndpointSource -> Bool) -> Eq EndpointSource
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: EndpointSource -> EndpointSource -> Bool
== :: EndpointSource -> EndpointSource -> Bool
$c/= :: EndpointSource -> EndpointSource -> Bool
/= :: EndpointSource -> EndpointSource -> Bool
Eq, Int -> EndpointSource -> ShowS
[EndpointSource] -> ShowS
EndpointSource -> String
(Int -> EndpointSource -> ShowS)
-> (EndpointSource -> String)
-> ([EndpointSource] -> ShowS)
-> Show EndpointSource
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> EndpointSource -> ShowS
showsPrec :: Int -> EndpointSource -> ShowS
$cshow :: EndpointSource -> String
show :: EndpointSource -> String
$cshowList :: [EndpointSource] -> ShowS
showList :: [EndpointSource] -> ShowS
Show)
data TelemetryEndpoint = TelemetryEndpoint
{ TelemetryEndpoint -> Text
teUrl :: Text
, TelemetryEndpoint -> EndpointSource
teSource :: EndpointSource
}
deriving stock (TelemetryEndpoint -> TelemetryEndpoint -> Bool
(TelemetryEndpoint -> TelemetryEndpoint -> Bool)
-> (TelemetryEndpoint -> TelemetryEndpoint -> Bool)
-> Eq TelemetryEndpoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TelemetryEndpoint -> TelemetryEndpoint -> Bool
== :: TelemetryEndpoint -> TelemetryEndpoint -> Bool
$c/= :: TelemetryEndpoint -> TelemetryEndpoint -> Bool
/= :: TelemetryEndpoint -> TelemetryEndpoint -> Bool
Eq, Int -> TelemetryEndpoint -> ShowS
[TelemetryEndpoint] -> ShowS
TelemetryEndpoint -> String
(Int -> TelemetryEndpoint -> ShowS)
-> (TelemetryEndpoint -> String)
-> ([TelemetryEndpoint] -> ShowS)
-> Show TelemetryEndpoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TelemetryEndpoint -> ShowS
showsPrec :: Int -> TelemetryEndpoint -> ShowS
$cshow :: TelemetryEndpoint -> String
show :: TelemetryEndpoint -> String
$cshowList :: [TelemetryEndpoint] -> ShowS
showList :: [TelemetryEndpoint] -> ShowS
Show)
data ResolvedTelemetry = ResolvedTelemetry
{ ResolvedTelemetry -> Text
rtServiceName :: Text
, ResolvedTelemetry -> Maybe Text
rtEnvironment :: Maybe Text
, ResolvedTelemetry -> Maybe Text
rtVersion :: Maybe Text
, ResolvedTelemetry -> TelemetryEndpoint
rtEndpoint :: TelemetryEndpoint
}
deriving stock (ResolvedTelemetry -> ResolvedTelemetry -> Bool
(ResolvedTelemetry -> ResolvedTelemetry -> Bool)
-> (ResolvedTelemetry -> ResolvedTelemetry -> Bool)
-> Eq ResolvedTelemetry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ResolvedTelemetry -> ResolvedTelemetry -> Bool
== :: ResolvedTelemetry -> ResolvedTelemetry -> Bool
$c/= :: ResolvedTelemetry -> ResolvedTelemetry -> Bool
/= :: ResolvedTelemetry -> ResolvedTelemetry -> Bool
Eq, Int -> ResolvedTelemetry -> ShowS
[ResolvedTelemetry] -> ShowS
ResolvedTelemetry -> String
(Int -> ResolvedTelemetry -> ShowS)
-> (ResolvedTelemetry -> String)
-> ([ResolvedTelemetry] -> ShowS)
-> Show ResolvedTelemetry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ResolvedTelemetry -> ShowS
showsPrec :: Int -> ResolvedTelemetry -> ShowS
$cshow :: ResolvedTelemetry -> String
show :: ResolvedTelemetry -> String
$cshowList :: [ResolvedTelemetry] -> ShowS
showList :: [ResolvedTelemetry] -> ShowS
Show)
resolveTelemetry :: [(String, String)] -> ResolvedTelemetry
resolveTelemetry :: [(String, String)] -> ResolvedTelemetry
resolveTelemetry [(String, String)]
environment =
ResolvedTelemetry
{ rtServiceName :: Text
rtServiceName = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
defaultServiceName Maybe Text
serviceName
, rtEnvironment :: Maybe Text
rtEnvironment = String -> Maybe Text
lk String
"DD_ENV" Maybe Text -> Maybe Text -> Maybe Text
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Text -> Maybe Text
attr Text
"deployment.environment"
, rtVersion :: Maybe Text
rtVersion = String -> Maybe Text
lk String
"DD_VERSION" Maybe Text -> Maybe Text -> Maybe Text
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Text -> Maybe Text
attr Text
"service.version"
, rtEndpoint :: TelemetryEndpoint
rtEndpoint = TelemetryEndpoint
endpoint
}
where
lk :: String -> Maybe Text
lk :: String -> Maybe Text
lk String
name = Text -> Maybe Text
nonBlank (Text -> Maybe Text) -> (String -> Text) -> String -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. ToText a => a -> Text
toText (String -> Maybe Text) -> Maybe String -> Maybe Text
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< String -> [(String, String)] -> Maybe String
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup String
name [(String, String)]
environment
attrs :: Map Text Text
attrs :: Map Text Text
attrs = Map Text Text
-> (Text -> Map Text Text) -> Maybe Text -> Map Text Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Map Text Text
forall k a. Map k a
Map.empty Text -> Map Text Text
parseResourceAttributes (String -> Maybe Text
lk String
"OTEL_RESOURCE_ATTRIBUTES")
attr :: Text -> Maybe Text
attr :: Text -> Maybe Text
attr Text
key = Text -> Maybe Text
nonBlank (Text -> Maybe Text) -> Maybe Text -> Maybe Text
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Text -> Map Text Text -> Maybe Text
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
key Map Text Text
attrs
serviceName :: Maybe Text
serviceName :: Maybe Text
serviceName = String -> Maybe Text
lk String
"DD_SERVICE" Maybe Text -> Maybe Text -> Maybe Text
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> String -> Maybe Text
lk String
"OTEL_SERVICE_NAME" Maybe Text -> Maybe Text -> Maybe Text
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Text -> Maybe Text
attr Text
"service.name"
endpoint :: TelemetryEndpoint
endpoint :: TelemetryEndpoint
endpoint = case String -> Maybe Text
lk String
"DD_AGENT_HOST" of
Just Text
host -> Text -> EndpointSource -> TelemetryEndpoint
TelemetryEndpoint (Text -> Text
agentHostUrl Text
host) EndpointSource
FromDdAgentHost
Maybe Text
Nothing -> case String -> Maybe Text
lk String
"OTEL_EXPORTER_OTLP_ENDPOINT" of
Just Text
url -> Text -> EndpointSource -> TelemetryEndpoint
TelemetryEndpoint Text
url EndpointSource
FromOtelEndpoint
Maybe Text
Nothing -> Text -> EndpointSource -> TelemetryEndpoint
TelemetryEndpoint Text
defaultEndpointUrl EndpointSource
DefaultedEndpoint
defaultServiceName :: Text
defaultServiceName :: Text
defaultServiceName = Text
"ecluse"
defaultEndpointUrl :: Text
defaultEndpointUrl :: Text
defaultEndpointUrl = Text
"http://localhost:4318"
agentHostUrl :: Text -> Text
agentHostUrl :: Text -> Text
agentHostUrl Text
raw
| Text
"://" Text -> Text -> Bool
`T.isInfixOf` Text
host = Text
host
| Bool
otherwise = Text
"http://" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
authority
where
host :: Text
host = Text -> Text
T.strip Text
raw
authority :: Text
authority
| Text
"[" Text -> Text -> Bool
`T.isPrefixOf` Text
host = if Text
"]:" Text -> Text -> Bool
`T.isInfixOf` Text
host then Text
host else Text
host Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":4318"
| HasCallStack => Text -> Text -> Int
Text -> Text -> Int
T.count Text
":" Text
host Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
2 = Text
"[" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
host Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"]:4318"
| HasCallStack => Text -> Text -> Int
Text -> Text -> Int
T.count Text
":" Text
host Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 = Text
host
| Bool
otherwise = Text
host Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":4318"
otelEnvironmentOverrides :: [(String, String)] -> [(String, String)]
otelEnvironmentOverrides :: [(String, String)] -> [(String, String)]
otelEnvironmentOverrides [(String, String)]
environment =
[ (String
"OTEL_SERVICE_NAME", Text -> String
forall a. ToString a => a -> String
toString (ResolvedTelemetry -> Text
rtServiceName ResolvedTelemetry
resolved))
, (String
"OTEL_EXPORTER_OTLP_ENDPOINT", Text -> String
forall a. ToString a => a -> String
toString (TelemetryEndpoint -> Text
teUrl (ResolvedTelemetry -> TelemetryEndpoint
rtEndpoint ResolvedTelemetry
resolved)))
, (String
"OTEL_EXPORTER_OTLP_PROTOCOL", String
"http/protobuf")
, (String
"OTEL_RESOURCE_ATTRIBUTES", Text -> String
forall a. ToString a => a -> String
toString (Map Text Text -> Text
renderResourceAttributes (ResolvedTelemetry -> [(String, String)] -> Map Text Text
mergedResourceAttributes ResolvedTelemetry
resolved [(String, String)]
environment)))
]
where
resolved :: ResolvedTelemetry
resolved :: ResolvedTelemetry
resolved = [(String, String)] -> ResolvedTelemetry
resolveTelemetry [(String, String)]
environment
mergedResourceAttributes :: ResolvedTelemetry -> [(String, String)] -> Map Text Text
mergedResourceAttributes :: ResolvedTelemetry -> [(String, String)] -> Map Text Text
mergedResourceAttributes ResolvedTelemetry
resolved [(String, String)]
environment =
Map Text Text
resolvedAttrs Map Text Text -> Map Text Text -> Map Text Text
forall a. Semigroup a => a -> a -> a
<> Map Text Text
existing
where
existing :: Map Text Text
existing :: Map Text Text
existing =
Map Text Text
-> (Text -> Map Text Text) -> Maybe Text -> Map Text Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
Map Text Text
forall k a. Map k a
Map.empty
Text -> Map Text Text
parseResourceAttributes
(Text -> Maybe Text
nonBlank (Text -> Maybe Text) -> (String -> Text) -> String -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. ToText a => a -> Text
toText (String -> Maybe Text) -> Maybe String -> Maybe Text
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< String -> [(String, String)] -> Maybe String
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup String
"OTEL_RESOURCE_ATTRIBUTES" [(String, String)]
environment)
resolvedAttrs :: Map Text Text
resolvedAttrs :: Map Text Text
resolvedAttrs =
[(Text, Text)] -> Map Text Text
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList
[ (Text
key, Text
value)
| (Text
key, Just Text
value) <-
[ (Text
"service.name", Text -> Maybe Text
forall a. a -> Maybe a
Just (ResolvedTelemetry -> Text
rtServiceName ResolvedTelemetry
resolved))
, (Text
"deployment.environment", ResolvedTelemetry -> Maybe Text
rtEnvironment ResolvedTelemetry
resolved)
, (Text
"service.version", ResolvedTelemetry -> Maybe Text
rtVersion ResolvedTelemetry
resolved)
]
]
parseResourceAttributes :: Text -> Map Text Text
parseResourceAttributes :: Text -> Map Text Text
parseResourceAttributes Text
raw =
[(Text, Text)] -> Map Text Text
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList
[ (Text
key, Text -> Text
T.strip (Int -> Text -> Text
T.drop Int
1 Text
value))
| Text
pair <- HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"," Text
raw
, let (Text
before, Text
value) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"=" Text
pair
, let key :: Text
key = Text -> Text
T.strip Text
before
, Bool -> Bool
not (Text -> Bool
T.null Text
key)
, Bool -> Bool
not (Text -> Bool
T.null Text
value)
]
renderResourceAttributes :: Map Text Text -> Text
renderResourceAttributes :: Map Text Text -> Text
renderResourceAttributes =
Text -> [Text] -> Text
T.intercalate Text
"," ([Text] -> Text)
-> (Map Text Text -> [Text]) -> Map Text Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Text) -> Text) -> [(Text, Text)] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (\(Text
key, Text
value) -> Text
key Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"=" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
value) ([(Text, Text)] -> [Text])
-> (Map Text Text -> [(Text, Text)]) -> Map Text Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Text Text -> [(Text, Text)]
forall k a. Map k a -> [(k, a)]
Map.toList
defaultedEndpointMessage :: Text -> Text
defaultedEndpointMessage :: Text -> Text
defaultedEndpointMessage Text
url =
Text
"no telemetry export endpoint configured (DD_AGENT_HOST / OTEL_EXPORTER_OTLP_ENDPOINT unset); defaulting to "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
url
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"."
data ThrottleState = ThrottleState
{ ThrottleState -> Maybe UTCTime
tsLastLogged :: Maybe UTCTime
, ThrottleState -> Int
tsSuppressed :: Int
}
deriving stock (ThrottleState -> ThrottleState -> Bool
(ThrottleState -> ThrottleState -> Bool)
-> (ThrottleState -> ThrottleState -> Bool) -> Eq ThrottleState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ThrottleState -> ThrottleState -> Bool
== :: ThrottleState -> ThrottleState -> Bool
$c/= :: ThrottleState -> ThrottleState -> Bool
/= :: ThrottleState -> ThrottleState -> Bool
Eq, Int -> ThrottleState -> ShowS
[ThrottleState] -> ShowS
ThrottleState -> String
(Int -> ThrottleState -> ShowS)
-> (ThrottleState -> String)
-> ([ThrottleState] -> ShowS)
-> Show ThrottleState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ThrottleState -> ShowS
showsPrec :: Int -> ThrottleState -> ShowS
$cshow :: ThrottleState -> String
show :: ThrottleState -> String
$cshowList :: [ThrottleState] -> ShowS
showList :: [ThrottleState] -> ShowS
Show)
data ThrottleEmit
=
EmitFirst
|
EmitHeartbeat Int
|
EmitSuppress
deriving stock (ThrottleEmit -> ThrottleEmit -> Bool
(ThrottleEmit -> ThrottleEmit -> Bool)
-> (ThrottleEmit -> ThrottleEmit -> Bool) -> Eq ThrottleEmit
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ThrottleEmit -> ThrottleEmit -> Bool
== :: ThrottleEmit -> ThrottleEmit -> Bool
$c/= :: ThrottleEmit -> ThrottleEmit -> Bool
/= :: ThrottleEmit -> ThrottleEmit -> Bool
Eq, Int -> ThrottleEmit -> ShowS
[ThrottleEmit] -> ShowS
ThrottleEmit -> String
(Int -> ThrottleEmit -> ShowS)
-> (ThrottleEmit -> String)
-> ([ThrottleEmit] -> ShowS)
-> Show ThrottleEmit
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ThrottleEmit -> ShowS
showsPrec :: Int -> ThrottleEmit -> ShowS
$cshow :: ThrottleEmit -> String
show :: ThrottleEmit -> String
$cshowList :: [ThrottleEmit] -> ShowS
showList :: [ThrottleEmit] -> ShowS
Show)
initialThrottle :: ThrottleState
initialThrottle :: ThrottleState
initialThrottle = Maybe UTCTime -> Int -> ThrottleState
ThrottleState Maybe UTCTime
forall a. Maybe a
Nothing Int
0
throttleInterval :: NominalDiffTime
throttleInterval :: NominalDiffTime
throttleInterval = NominalDiffTime
60
throttleStep :: NominalDiffTime -> UTCTime -> ThrottleState -> (ThrottleState, ThrottleEmit)
throttleStep :: NominalDiffTime
-> UTCTime -> ThrottleState -> (ThrottleState, ThrottleEmit)
throttleStep NominalDiffTime
interval UTCTime
now ThrottleState
st = case ThrottleState -> Maybe UTCTime
tsLastLogged ThrottleState
st of
Maybe UTCTime
Nothing -> (Maybe UTCTime -> Int -> ThrottleState
ThrottleState (UTCTime -> Maybe UTCTime
forall a. a -> Maybe a
Just UTCTime
now) Int
0, ThrottleEmit
EmitFirst)
Just UTCTime
lastLogged
| UTCTime -> UTCTime -> NominalDiffTime
diffUTCTime UTCTime
now UTCTime
lastLogged NominalDiffTime -> NominalDiffTime -> Bool
forall a. Ord a => a -> a -> Bool
>= NominalDiffTime
interval ->
(Maybe UTCTime -> Int -> ThrottleState
ThrottleState (UTCTime -> Maybe UTCTime
forall a. a -> Maybe a
Just UTCTime
now) Int
0, Int -> ThrottleEmit
EmitHeartbeat (ThrottleState -> Int
tsSuppressed ThrottleState
st Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))
| Bool
otherwise ->
(ThrottleState
st{tsSuppressed = tsSuppressed st + 1}, ThrottleEmit
EmitSuppress)
prepareTelemetry :: LogEnv -> [(String, String)] -> IO ()
prepareTelemetry :: LogEnv -> [(String, String)] -> IO ()
prepareTelemetry LogEnv
logEnv [(String, String)]
environment = do
let resolved :: ResolvedTelemetry
resolved = [(String, String)] -> ResolvedTelemetry
resolveTelemetry [(String, String)]
environment
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (TelemetryEndpoint -> EndpointSource
teSource (ResolvedTelemetry -> TelemetryEndpoint
rtEndpoint ResolvedTelemetry
resolved) EndpointSource -> EndpointSource -> Bool
forall a. Eq a => a -> a -> Bool
== EndpointSource
DefaultedEndpoint) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
LogEnv -> Severity -> Text -> IO ()
logResolve LogEnv
logEnv Severity
WarningS (Text -> Text
defaultedEndpointMessage (TelemetryEndpoint -> Text
teUrl (ResolvedTelemetry -> TelemetryEndpoint
rtEndpoint ResolvedTelemetry
resolved)))
((String, String) -> IO ()) -> [(String, String)] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((String -> String -> IO ()) -> (String, String) -> IO ()
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry String -> String -> IO ()
setEnv) ([(String, String)] -> [(String, String)]
otelEnvironmentOverrides [(String, String)]
environment)
data ExportFailureSink = ExportFailureSink
{ ExportFailureSink -> IO UTCTime
sinkNow :: IO UTCTime
, ExportFailureSink -> IORef ThrottleState
sinkState :: IORef ThrottleState
, ExportFailureSink -> Severity -> Text -> IO ()
sinkSurface :: Severity -> Text -> IO ()
}
newExportFailureSink :: IO UTCTime -> (Severity -> Text -> IO ()) -> IO ExportFailureSink
newExportFailureSink :: IO UTCTime -> (Severity -> Text -> IO ()) -> IO ExportFailureSink
newExportFailureSink IO UTCTime
now Severity -> Text -> IO ()
surface = do
throttleRef <- ThrottleState -> IO (IORef ThrottleState)
forall (m :: * -> *) a. MonadIO m => a -> m (IORef a)
newIORef ThrottleState
initialThrottle
pure ExportFailureSink{sinkNow = now, sinkState = throttleRef, sinkSurface = surface}
exportFailureSink :: LogEnv -> IO ExportFailureSink
exportFailureSink :: LogEnv -> IO ExportFailureSink
exportFailureSink LogEnv
logEnv = IO UTCTime -> (Severity -> Text -> IO ()) -> IO ExportFailureSink
newExportFailureSink IO UTCTime
getCurrentTime (LogEnv -> Severity -> Text -> IO ()
logResolve LogEnv
logEnv)
routeExportFailure :: ExportFailureSink -> Text -> IO ()
routeExportFailure :: ExportFailureSink -> Text -> IO ()
routeExportFailure ExportFailureSink
sink Text
diagnostic = do
now <- ExportFailureSink -> IO UTCTime
sinkNow ExportFailureSink
sink
emit <- atomicModifyIORef' (sinkState sink) (throttleStep throttleInterval now)
case emit of
ThrottleEmit
EmitFirst -> ExportFailureSink -> Severity -> Text -> IO ()
sinkSurface ExportFailureSink
sink Severity
WarningS (Text -> Text
firstErrorMessage Text
diagnostic)
EmitHeartbeat Int
suppressed -> ExportFailureSink -> Severity -> Text -> IO ()
sinkSurface ExportFailureSink
sink Severity
WarningS (Int -> Text -> Text
heartbeatMessage Int
suppressed Text
diagnostic)
ThrottleEmit
EmitSuppress -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
observeExportResult :: ExportFailureSink -> Text -> ExportResult -> IO ()
observeExportResult :: ExportFailureSink -> Text -> ExportResult -> IO ()
observeExportResult ExportFailureSink
sink Text
signal = \case
ExportResult
Success -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
Failure Maybe SomeException
mErr -> ExportFailureSink -> Text -> IO ()
routeExportFailure ExportFailureSink
sink (Text
signal Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" export failed" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> (SomeException -> Text) -> Maybe SomeException -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" ((Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) (Text -> Text) -> (SomeException -> Text) -> SomeException -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> Text
forall b a. (Show a, IsString b) => a -> b
show) Maybe SomeException
mErr)
installExportErrorHandler :: ExportFailureSink -> IO ()
installExportErrorHandler :: ExportFailureSink -> IO ()
installExportErrorHandler ExportFailureSink
sink = (String -> IO ()) -> IO ()
setGlobalErrorHandler (ExportFailureSink -> Text -> IO ()
routeExportFailure ExportFailureSink
sink (Text -> IO ()) -> (String -> Text) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. ToText a => a -> Text
toText)
firstErrorMessage :: Text -> Text
firstErrorMessage :: Text -> Text
firstErrorMessage Text
diagnostic =
Text
"telemetry export error (subsequent identical errors are throttled): " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
diagnostic
heartbeatMessage :: Int -> Text -> Text
heartbeatMessage :: Int -> Text -> Text
heartbeatMessage Int
suppressed Text
diagnostic =
Text
"telemetry export still failing: "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
suppressed
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" export errors since the last report. Latest: "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
diagnostic
logResolve :: LogEnv -> Severity -> Text -> IO ()
logResolve :: LogEnv -> Severity -> Text -> IO ()
logResolve LogEnv
logEnv Severity
severity Text
message =
LogEnv
-> SimpleLogPayload -> Namespace -> KatipContextT IO () -> IO ()
forall c (m :: * -> *) a.
LogItem c =>
LogEnv -> c -> Namespace -> KatipContextT m a -> m a
runKatipContextT LogEnv
logEnv (Text -> SimpleLogPayload
moduleField Text
"Ecluse.Telemetry.Resolve") Namespace
forall a. Monoid a => a
mempty (KatipContextT IO () -> IO ()) -> KatipContextT IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
Severity -> LogStr -> KatipContextT IO ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
severity (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls Text
message)