otel-effectful-1.0.0: src/Effectful/OpenTelemetry/Logging/Effect.hs
module Effectful.OpenTelemetry.Logging.Effect
( -- * Effect
Logging
, runLogging
, runHttpLogging
, runGrpcLogging
, runConsoleLogging
, runInMemoryLogging
, runLoggingWith
, runNoLogging
, withTracing
-- * Emit logs
, log
)
where
import Control.Monad.Extra (ifM)
import Data.Aeson.Types (Value)
import Data.Text (Text)
import Effectful
import Effectful.Concurrent (Concurrent)
import Effectful.Dispatch.Static
import Effectful.Environment (Environment)
import Effectful.Http2Client (HostName, PortNumber)
import Effectful.HttpClient (responseTimeoutDefault)
import Effectful.OpenTelemetry.Exporter (Exporter)
import Effectful.OpenTelemetry.Exporter qualified as Exporter
import Effectful.OpenTelemetry.Exporter.Console qualified as Console
import Effectful.OpenTelemetry.Exporter.Environment qualified as Exporter.Environment
import Effectful.OpenTelemetry.Exporter.OTLP (defaultGrpcSendTimeout)
import Effectful.OpenTelemetry.Logging.LogRecord (LogRecord (..))
import Effectful.OpenTelemetry.Logging.Severity (Severity)
import Effectful.OpenTelemetry.Protocol.Attributes (Attributes)
import Effectful.OpenTelemetry.Protocol.Effect
( OTLP
, exportIO
, runInMemoryOTLP
, runNoOTLP
, runOTLPWith
)
import Effectful.OpenTelemetry.Protocol.Environment qualified as Environment
import Effectful.OpenTelemetry.Protocol.Export qualified as Export
import Effectful.OpenTelemetry.Protocol.Resource (Resource)
import Effectful.OpenTelemetry.Protocol.Scope (Scope)
import Effectful.OpenTelemetry.Protocol.Transport
( Compression
, Encoding
)
import Effectful.OpenTelemetry.Timestamp qualified as Timestamp
import Effectful.OpenTelemetry.Tracing.Effect (Tracing, currentContextIO)
import Effectful.OpenTelemetry.Tracing.Span.Context qualified as Span (Context)
import Effectful.Retry (Retry)
import Effectful.Timeout (Timeout)
import Network.URI (URI)
import Prelude hiding (log)
data Logging :: Effect
type instance DispatchOf Logging = 'Static 'WithSideEffects
data instance StaticRep Logging = Logging
{ getContext :: IO (Maybe Span.Context)
, export :: LogRecord -> IO ()
}
withTracing :: (Logging :> es, Tracing :> es) => Eff es a -> Eff es a
withTracing eff = do
getContext <- currentContextIO
localStaticRep (\logging -> logging{getContext}) eff
-- | Emit a single log record with the current 'Timestamp.Timestamp' and 'Tracing' context, if any.
-- This function can be used without 'Tracing' as well.
log
:: (Logging :> es)
=> Severity
-> Value
-> Attributes
-> Maybe Text
-> Eff es ()
log severity body attributes eventName = do
Logging{..} <- getStaticRep
unsafeEff_ do
timestamp <- Timestamp.now
context <- getContext
export LogRecord{observedTimestamp = timestamp, ..}
runLoggingState
:: (IOE :> es, OTLP LogRecord :> es, Tracing :> es)
=> Eff (Logging ': es) a
-> Eff es a
runLoggingState eff = do
getContext <- currentContextIO
runLoggingState' getContext eff
runLoggingState'
:: (IOE :> es, OTLP LogRecord :> es)
=> IO (Maybe Span.Context)
-> Eff (Logging ': es) a
-> Eff es a
runLoggingState' getContext eff = do
export <- exportIO
evalStaticRep (Logging{..}) eff
-- | Run the 'Logging' effect, sending telemetry to an exporter.
-- Uses the 'Tracing' effect to enrich logs with span context.
-- Reads the configuration from <https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration the standard environment variables>.
-- Delegates to 'runNoLogging' if @OTEL_SDK_DISABLED = true@.
runLogging
:: ( Tracing :> es
, IOE :> es
, Concurrent :> es
, Environment :> es
, Retry :> es
, Timeout :> es
)
=> Resource
-> Scope
-> Eff (Logging ': es) a
-> Eff es a
runLogging resource scope eff =
ifM Environment.isSdkDisabled (runNoLogging eff) do
exporter <- Environment.runConfigError $ Exporter.Environment.lookup @LogRecord resource scope
runLoggingWith exporter eff
-- | Run the 'Logging' effect, sending telemetry to a collector at the given 'URI'
-- over HTTP with the given 'Encoding'.
-- Uses the 'Tracing' effect to enrich logs with span context.
runHttpLogging
:: (IOE :> es, Concurrent :> es, Retry :> es, Timeout :> es, Tracing :> es)
=> Resource
-> Scope
-> Encoding
-> Compression
-> URI
-> Eff (Logging ': es) a
-> Eff es a
runHttpLogging resource scope encoding compression endpoint =
runLoggingWith $
Exporter.http @LogRecord
resource
scope
encoding
endpoint
(Export.defaultConfig @LogRecord)
compression
responseTimeoutDefault
-- | Run the 'Logging' effect, sending telemetry to a gRPC collector.
-- Uses the 'Tracing' effect to enrich logs with span context.
runGrpcLogging
:: (IOE :> es, Concurrent :> es, Retry :> es, Timeout :> es, Tracing :> es)
=> Resource
-> Scope
-> HostName
-> PortNumber
-> Compression
-> Eff (Logging ': es) a
-> Eff es a
runGrpcLogging resource scope host port compression =
runLoggingWith $
Exporter.grpc @LogRecord
resource
scope
host
port
(Export.exportGrpcRPC @LogRecord)
(Export.defaultConfig @LogRecord)
compression
defaultGrpcSendTimeout
-- | Run the 'Logging' effect, printing telemetry to the console rather than sending to a collector.
runConsoleLogging :: (IOE :> es, Tracing :> es) => Eff (Logging ': es) a -> Eff es a
runConsoleLogging = runLoggingWith Console.stdout
-- | Run the 'Logging' effect, collecting telemetry in-memory rather than sending to a collector.
-- Uses the 'Tracing' effect to enrich logs with span context.
runInMemoryLogging
:: (IOE :> es, Concurrent :> es, Tracing :> es)
=> Eff (Logging ': es) a
-> Eff es (a, [LogRecord])
runInMemoryLogging = runInMemoryOTLP @LogRecord . runLoggingState . inject
-- | Run the 'Logging' effect with a given 'Exporter'.
-- Passes records to the exporter synchronously as they are emitted, without batching or retrying.
-- Uses the 'Tracing' effect to enrich logs with span context.
runLoggingWith
:: (IOE :> es, Tracing :> es)
=> Exporter es LogRecord
-> Eff (Logging ': es) a
-> Eff es a
runLoggingWith exporter = runOTLPWith exporter . runLoggingState . inject
-- | Run the 'Logging' effect as a no-op action.
runNoLogging :: (IOE :> es) => Eff (Logging ': es) a -> Eff es a
runNoLogging = runNoOTLP @LogRecord . runLoggingState' (pure Nothing) . inject