-- Search for comments to find commentary of interest {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Control.Exception import Control.Monad import Data.Aeson import Data.ByteString.Internal import Data.Void import Foreign.C.Error import Foreign.C.Types import Foreign.ForeignPtr.Safe import Foreign.Ptr import GHC.Generics import Observe.Event import Observe.Event.Render.IO.JSON import Observe.Event.Render.JSON import System.FilePath import System.IO.Temp import System.Posix.Files import System.Posix.IO import System.Posix.Types -- Pretend this is in a separate module File where -- We take an EventBackend, polymorphic in r, supporting our domain-specific selector type writeToFile :: EventBackend IO r FileSelector -> FilePath -> ByteString -> IO () writeToFile backend path (BS fptr sz) = do -- We start an event, selected by OpenFile fd <- withEvent backend OpenFile $ \ev -> do -- We add a Filename field to our current active event addField ev $ Filename path fd <- openFd path WriteOnly (Just regularFileMode) defaultFileFlags when (fd == -1) $ do errno <- getErrno -- Throw an exception which we can render as JSON throw $ BadOpen path errno addField ev $ FileFd fd pure fd withForeignPtr fptr $ \ptr -> do let bcSz = fromIntegral sz go :: ByteCount -> IO () go offset = do newOffset <- withEvent backend WriteFile $ \ev -> do let ct = bcSz - offset addField ev $ BytesAsked ct written <- fdWriteBuf fd (plusPtr ptr (fromIntegral offset)) ct addField ev $ BytesActual written pure $ offset + written when (newOffset < bcSz) $ go newOffset go 0 closeFd fd pure () -- Define our selector type. A GADT, parameterized by field type data FileSelector f where OpenFile :: FileSelector OpenField WriteFile :: FileSelector WriteField -- Define a renderer for our selector type renderFileSelector :: RenderSelectorJSON FileSelector renderFileSelector OpenFile = ("open-file", renderOpenField) -- Expressed in terms of renderers for our field tyeps renderFileSelector WriteFile = ("write-file", renderWriteField) -- Define a field type data OpenField = Filename !FilePath | FileFd !Fd -- Define a renderer for a field type renderOpenField :: RenderFieldJSON OpenField renderOpenField (Filename path) = ("file-name", toJSON path) renderOpenField (FileFd fd) = ("file-fd", toJSON fd) data WriteField = BytesAsked !ByteCount | BytesActual !ByteCount renderWriteField :: RenderFieldJSON WriteField renderWriteField (BytesAsked ct) = ("asked", toJSON ct) renderWriteField (BytesActual ct) = ("actual", toJSON ct) -- Define a new exception that can be used with simpleJsonStderrBackend data BadOpen = BadOpen { path :: !FilePath, errno :: !Errno } deriving (Show, ToJSON, Generic) -- Our exception is beneath SomeJSONException in the hierarchy instance Exception BadOpen where toException = jsonExceptionToException fromException = jsonExceptionFromException -- end module File -- Note a different selector type than writeToFile instrumentedMain :: EventBackend IO r MainSelector -> IO () instrumentedMain backend = do withEvent backend UsingTempDir $ \ev -> do withSystemTempDirectory "example" $ \dir -> do addField ev dir let -- Create a new EventBackend where all parentless events are made children of our current event subBackend = subEventBackend ev -- Narrow subBackend to create events from FileSelectors instead of MainSelectors narrowerBackend = narrowEventBackend InjectFileSelector subBackend -- Pass our narrower backend to writeToFile writeToFile narrowerBackend (dir "example.txt") "example" main :: IO () main = -- Initialize a backend to write JSON to stderr and use it. simpleJsonStderrBackend renderMainSelector >>= instrumentedMain -- Instrumentation definitions data MainSelector f where UsingTempDir :: MainSelector FilePath InjectFileSelector :: FileSelector a -> MainSelector a -- A typical pattern together with narrowEventBackend to call functions with a more specialized selector type renderMainSelector :: RenderSelectorJSON MainSelector renderMainSelector UsingTempDir = ("using-tmp-dir", \f -> ("path", toJSON f)) renderMainSelector (InjectFileSelector filesel) = renderFileSelector filesel -- Note that we reuse the selector renderer from our File "module" deriving instance Show Errno deriving instance ToJSON Errno deriving instance Generic Errno deriving instance ToJSON CInt deriving instance Generic CInt deriving instance ToJSON Fd deriving instance Generic Fd deriving instance ToJSON ByteCount deriving instance Generic ByteCount