packages feed

effectful-cli-0.1.0.0: src/Effectful/Logger.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | monad-logger bridge: a static effect holding a log callback, plus a
-- 'MonadLogger' instance for 'Eff' so the usual @logInfoN@ etc. work.
module Effectful.Logger (
  Logger,
  LogFunc,
  runLogger,
  runStderrLogger,
  runOTelLogger,
) where

import Control.Monad.Logger (
  Loc,
  LogLevel,
  LogSource,
  LogStr,
  MonadLogger (..),
  defaultOutput,
  toLogStr,
 )
import Effectful (Dispatch (..), DispatchOf, Eff, Effect, IOE, (:>))
import Effectful.Dispatch.Static (
  SideEffects (..),
  StaticRep,
  evalStaticRep,
  getStaticRep,
  unsafeEff_,
 )
import OpenTelemetry.Instrumentation.MonadLogger qualified as OTel
import OpenTelemetry.Log.Core qualified as OTel
import System.IO (stderr)

-- | @since 0.1.0.0
type LogFunc = Loc -> LogSource -> LogLevel -> LogStr -> IO ()

-- | @since 0.1.0.0
data Logger :: Effect

type instance DispatchOf Logger = Static WithSideEffects

newtype instance StaticRep Logger = Logger LogFunc

-- | @since 0.1.0.0
runLogger :: IOE :> es => LogFunc -> Eff (Logger : es) a -> Eff es a
runLogger = evalStaticRep . Logger

-- | @since 0.1.0.0
runStderrLogger :: IOE :> es => Eff (Logger : es) a -> Eff es a
runStderrLogger = runLogger (defaultOutput stderr)

-- | @since 0.1.0.0
runOTelLogger :: IOE :> es => OTel.Logger -> Eff (Logger : es) a -> Eff es a
runOTelLogger = runLogger . OTel.makeOTelLogCallback

instance Logger :> es => MonadLogger (Eff es) where
  monadLoggerLog loc src lvl msg = do
    Logger f <- getStaticRep
    unsafeEff_ $ f loc src lvl (toLogStr msg)