cachix-1.2: src/Cachix/Deploy/Log.hs
{-# LANGUAGE DerivingStrategies #-}
module Cachix.Deploy.Log where
import qualified Cachix.API.WebSocketSubprotocol as WSS
import qualified Control.Concurrent.STM.TMQueue as TMQueue
import Control.Exception.Safe (MonadMask, bracket)
import qualified Data.Aeson as Aeson
import Data.Conduit ((.|))
import qualified Data.Conduit as Conduit
import qualified Data.Conduit.Combinators as Conduit
import qualified Data.Conduit.TQueue as Conduit
import Data.Time.Clock (getCurrentTime)
import qualified Katip
import qualified Network.WebSockets as WS
import Protolude hiding (bracket, toS)
import Protolude.Conv (toS)
-- A temporary crutch while we fix up the types
type WithLog = Katip.KatipContextT IO () -> IO ()
data Options = Options
{ -- | The minimum verbosity level
verbosity :: Verbosity,
-- | The logging namespace, e.g. agent
namespace :: Katip.Namespace,
-- | The logging environment, e.g. production
environment :: Katip.Environment
}
deriving stock (Show, Generic)
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
data Verbosity
= Normal
| Verbose
deriving stock (Show, Generic)
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
withLog ::
(MonadIO m, MonadMask m) =>
Options ->
(WithLog -> m a) ->
m a
withLog Options {..} inner =
let permit = case verbosity of
Verbose -> Katip.DebugS
Normal -> Katip.InfoS
createLogEnv = liftIO $ do
logEnv <- Katip.initLogEnv namespace environment
stdoutScribe <- Katip.mkHandleScribe Katip.ColorIfTerminal stdout (Katip.permitItem permit) Katip.V2
Katip.registerScribe "stdout" stdoutScribe Katip.defaultScribeSettings logEnv
createContext logEnv = Katip.runKatipContextT logEnv () namespace
closeLog = liftIO . Katip.closeScribes
in bracket createLogEnv closeLog $ inner . createContext
-- Streaming log
type LogStream = Conduit.ConduitT ByteString Conduit.Void IO ()
-- TODO: prepend katip-like format to each line
streamLog :: WithLog -> WS.Connection -> TMQueue.TMQueue ByteString -> IO ()
streamLog logger connection queue = do
Conduit.runConduit $
Conduit.sourceTMQueue queue
.| Conduit.linesUnboundedAscii
.| logLocal logger
.| sendLog connection
streamLine :: LogStream -> ByteString -> IO ()
streamLine logStream msg = Conduit.connect (Conduit.yield $ msg <> "\n") logStream
logLocal :: WithLog -> Conduit.ConduitT ByteString ByteString IO ()
logLocal logger =
Conduit.mapM $ \bs -> do
logger . Katip.logLocM Katip.DebugS . Katip.ls $ bs
return bs
sendLog :: WS.Connection -> LogStream
sendLog connection = Conduit.mapM_ $ \bs -> do
now <- getCurrentTime
WS.sendTextData connection $ Aeson.encode $ WSS.Log {WSS.line = toS bs, WSS.time = now}