eventuo11y 0.4.0.0 → 0.5.0.0
raw patch · 9 files changed
+488/−409 lines, 9 filesdep +safe-exceptionsdep +transformersdep −aesondep −bytestringdep −textdep ~basedep ~resourcetPVP ok
version bump matches the API change (PVP)
Dependencies added: safe-exceptions, transformers
Dependencies removed: aeson, bytestring, text, time, uuid
Dependency ranges changed: base, resourcet
API changes (from Hackage documentation)
- Observe.Event.Dynamic: DynamicField :: !Text -> !Value -> DynamicField
- Observe.Event.Dynamic: [DynamicEventSelector] :: !Text -> DynamicEventSelector DynamicField
- Observe.Event.Dynamic: [name] :: DynamicField -> !Text
- Observe.Event.Dynamic: [value] :: DynamicField -> !Value
- Observe.Event.Dynamic: data DynamicEventSelector f
- Observe.Event.Dynamic: data DynamicField
- Observe.Event.Dynamic: instance (f GHC.Types.~ Observe.Event.Dynamic.DynamicField) => Data.String.IsString (Observe.Event.Dynamic.DynamicEventSelector f)
- Observe.Event.Dynamic: instance Data.Aeson.Types.ToJSON.ToJSON x => Data.String.IsString (x -> Observe.Event.Dynamic.DynamicField)
- Observe.Event.Dynamic: type DynamicEvent m r = Event m r DynamicEventSelector DynamicField
- Observe.Event.Dynamic: type DynamicEventBackend m r = EventBackend m r DynamicEventSelector
- Observe.Event.Render.IO.JSON: JSONRef :: UUID -> JSONRef
- Observe.Event.Render.IO.JSON: instance Data.Aeson.Types.ToJSON.ToJSON Observe.Event.Render.IO.JSON.JSONRef
- Observe.Event.Render.IO.JSON: jsonHandleBackend :: Exception stex => Handle -> RenderExJSON stex -> RenderSelectorJSON s -> IO (EventBackend IO JSONRef s)
- Observe.Event.Render.IO.JSON: newtype JSONRef
- Observe.Event.Render.IO.JSON: simpleJsonStderrBackend :: RenderSelectorJSON s -> IO (EventBackend IO JSONRef s)
- Observe.Event.Render.JSON: SomeJSONException :: RenderExJSON e -> e -> SomeJSONException
- Observe.Event.Render.JSON: data SomeJSONException
- Observe.Event.Render.JSON: instance GHC.Exception.Type.Exception Observe.Event.Render.JSON.SomeJSONException
- Observe.Event.Render.JSON: instance GHC.Show.Show Observe.Event.Render.JSON.SomeJSONException
- Observe.Event.Render.JSON: jsonExceptionFromException :: Exception e => SomeException -> Maybe e
- Observe.Event.Render.JSON: jsonExceptionToException :: Exception e => RenderExJSON e -> e -> SomeException
- Observe.Event.Render.JSON: renderDynamicEventSelectorJSON :: RenderSelectorJSON DynamicEventSelector
- Observe.Event.Render.JSON: renderDynamicFieldJSON :: RenderFieldJSON DynamicField
- Observe.Event.Render.JSON: renderJSONException :: RenderExJSON SomeJSONException
- Observe.Event.Render.JSON: type RenderExJSON stex = stex -> Value
- Observe.Event.Render.JSON: type RenderFieldJSON field = field -> (Key, Value)
- Observe.Event.Render.JSON: type RenderSelectorJSON sel = forall f. sel f -> (Key, RenderFieldJSON f)
+ Control.Monad.Cleanup: AbortException :: AbortException
+ Control.Monad.Cleanup: CleanupFromMask :: m a -> CleanupFromMask m a
+ Control.Monad.Cleanup: CleanupNoException :: m a -> CleanupNoException m a
+ Control.Monad.Cleanup: class (Monad m) => MonadCleanup m
+ Control.Monad.Cleanup: data AbortException
+ Control.Monad.Cleanup: generalCleanup :: MonadCleanup m => m a -> (a -> ExitCase b -> m c) -> (a -> m b) -> m (b, c)
+ Control.Monad.Cleanup: instance (Control.Monad.Cleanup.MonadCleanup m, GHC.Base.Monoid w) => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.RWS.Lazy.RWST r w s m)
+ Control.Monad.Cleanup: instance (Control.Monad.Cleanup.MonadCleanup m, GHC.Base.Monoid w) => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.RWS.Strict.RWST r w s m)
+ Control.Monad.Cleanup: instance (Control.Monad.Cleanup.MonadCleanup m, GHC.Base.Monoid w) => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ Control.Monad.Cleanup: instance (Control.Monad.Cleanup.MonadCleanup m, GHC.Base.Monoid w) => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Writer.Strict.WriterT w m)
+ Control.Monad.Cleanup: instance Control.Monad.Catch.MonadMask m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Cleanup.CleanupFromMask m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup (GHC.ST.ST s)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup Data.Functor.Identity.Identity
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup GHC.Types.IO
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Except.ExceptT e m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Identity.IdentityT m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Maybe.MaybeT m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.Reader.ReaderT r m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.State.Lazy.StateT s m)
+ Control.Monad.Cleanup: instance Control.Monad.Cleanup.MonadCleanup m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Trans.State.Strict.StateT s m)
+ Control.Monad.Cleanup: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Monad.Cleanup.CleanupFromMask m)
+ Control.Monad.Cleanup: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Monad.Cleanup.CleanupNoException m)
+ Control.Monad.Cleanup: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Monad.Cleanup.CleanupFromMask m)
+ Control.Monad.Cleanup: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Monad.Cleanup.CleanupNoException m)
+ Control.Monad.Cleanup: instance GHC.Base.Monad m => Control.Monad.Cleanup.MonadCleanup (Control.Monad.Cleanup.CleanupNoException m)
+ Control.Monad.Cleanup: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Monad.Cleanup.CleanupFromMask m)
+ Control.Monad.Cleanup: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Monad.Cleanup.CleanupNoException m)
+ Control.Monad.Cleanup: instance GHC.Exception.Type.Exception Control.Monad.Cleanup.AbortException
+ Control.Monad.Cleanup: instance GHC.Show.Show Control.Monad.Cleanup.AbortException
+ Control.Monad.Cleanup: newtype CleanupFromMask m a
+ Control.Monad.Cleanup: newtype CleanupNoException m a
+ Control.Monad.Cleanup: withCleanup :: MonadCleanup m => m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
+ Observe.Event: causedEventBackend :: Monad m => (forall f'. s f' -> t f') -> Event m r t f -> EventBackend m r s
+ Observe.Event: hoistEvent :: (Functor m, Functor n) => (forall x. m x -> n x) -> Event m r s f -> Event n r s f
+ Observe.Event: withEventMask :: forall m r s. MonadMask m => EventBackend m r s -> forall f. s f -> forall a. (Event m r s f -> m a) -> m a
+ Observe.Event: withSubEventMask :: MonadMask m => Event m r s f -> forall f'. s f' -> (Event m r s f' -> m a) -> m a
+ Observe.Event.Backend: hoistEventImpl :: (forall x. m x -> n x) -> EventImpl m r f -> EventImpl n r f
- Observe.Event: failEvent :: Monad m => Event m r s f -> Maybe SomeException -> m ()
+ Observe.Event: failEvent :: Monad m => Event m r s f -> SomeException -> m ()
- Observe.Event: subEventBackend :: Monad m => Event m r s f -> EventBackend m r s
+ Observe.Event: subEventBackend :: Monad m => (forall f'. s f' -> t f') -> Event m r t f -> EventBackend m r s
- Observe.Event: withEvent :: MonadMask m => EventBackend m r s -> forall f. s f -> (Event m r s f -> m a) -> m a
+ Observe.Event: withEvent :: MonadCleanup m => EventBackend m r s -> forall f. s f -> (Event m r s f -> m a) -> m a
- Observe.Event: withSubEvent :: MonadMask m => Event m r s f -> forall f'. s f' -> (Event m r s f' -> m a) -> m a
+ Observe.Event: withSubEvent :: MonadCleanup m => Event m r s f -> forall f'. s f' -> (Event m r s f' -> m a) -> m a
- Observe.Event.Backend: EventImpl :: !r -> !f -> m () -> !r -> m () -> !r -> m () -> !m () -> !Maybe SomeException -> m () -> EventImpl m r f
+ Observe.Event.Backend: EventImpl :: !r -> !f -> m () -> !r -> m () -> !r -> m () -> !m () -> !SomeException -> m () -> EventImpl m r f
- Observe.Event.Backend: [failImpl] :: EventImpl m r f -> !Maybe SomeException -> m ()
+ Observe.Event.Backend: [failImpl] :: EventImpl m r f -> !SomeException -> m ()
Files
- CHANGELOG.md +12/−2
- Example.hs +53/−65
- eventuo11y.cabal +17/−17
- src/Control/Monad/Cleanup.hs +296/−0
- src/Observe/Event.hs +94/−20
- src/Observe/Event/Backend.hs +16/−13
- src/Observe/Event/Dynamic.hs +0/−61
- src/Observe/Event/Render/IO/JSON.hs +0/−140
- src/Observe/Event/Render/JSON.hs +0/−91
CHANGELOG.md view
@@ -1,8 +1,18 @@ # Revision history for eventuo11y -## 0.4.0.0 -- 2022-10-04+## 0.5.0.0 -- 2022-10-23 -Have SomeJSONException take a RenderExJSON directly+- Add MonadCleanup+- Break out eventuo11y-json+- Include narrowing in subEventBackend and causedEventBackend++## 0.3.2.0 -- 2022-10-04++Add hoistEvent++## 0.3.1.0 -- 2022-10-04++Add causedEventBackend ## 0.3.0.0 -- 2022-10-03
Example.hs view
@@ -2,8 +2,10 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeSynonymInstances #-} module Main where@@ -19,8 +21,10 @@ import Foreign.Ptr import GHC.Generics import Observe.Event-import Observe.Event.Render.IO.JSON+import Observe.Event.DSL import Observe.Event.Render.JSON+import Observe.Event.Render.JSON.DSL.Compile+import Observe.Event.Render.JSON.Handle import System.FilePath import System.IO.Temp import System.Posix.Files@@ -29,6 +33,42 @@ -- Pretend this is in a separate module File where +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++-- Define our selector type and give it instances to render as JSON+compile $+ SelectorSpec+ "file" -- Creates a type FileSelector+ [ ["open", "file"] -- Creates a constructor OpenFile :: FileSelector OpenField+ ≔ FieldSpec+ "open" -- Creates a type OpenField+ [ "filename" ≔ ''FilePath, -- creates a constructor Filename :: !FilePath -> OpenField+ ["file", "fd"] ≔ ''Fd -- creates a constructor FileFd :: !Fd -> OpenField+ ],+ "write"+ ≔ FieldSpec+ "write"+ [ ["bytes", "asked"] ≔ ''ByteCount,+ ["bytes", "actual"] ≔ ''ByteCount+ ]+ ]+ -- 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 = do@@ -50,7 +90,7 @@ let bcSz = fromIntegral sz go :: ByteCount -> IO () go offset = do- newOffset <- withEvent backend WriteFile $ \ev -> do+ newOffset <- withEvent backend Write $ \ev -> do let ct = bcSz - offset addField ev $ BytesAsked ct written <- fdWriteBuf fd (plusPtr ptr (base_off + fromIntegral offset)) ct@@ -62,34 +102,6 @@ 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,@@ -99,52 +111,28 @@ -- Our exception is beneath SomeJSONException in the hierarchy instance Exception BadOpen where- toException = jsonExceptionToException toJSON+ toException = jsonExceptionToException fromException = jsonExceptionFromException -- end module File +compile $+ SelectorSpec+ "main"+ [ ["using", "temp", "dir"] ≔ ''FilePath, -- Creates a constructor UsingTempDir :: MainSelector FilePath+ "writing" ≔ Inject ''FileSelector -- Creates a constructor Writing :: FileSelector x -> MainSelector x+ ]+ -- 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"+ -- Pass a new EventBackend where all parentless events are made children of our current event+ writeToFile (subEventBackend Writing ev) (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+ simpleJsonStderrBackend defaultRenderSelectorJSON >>= instrumentedMain
eventuo11y.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: eventuo11y-version: 0.4.0.0+version: 0.5.0.0 synopsis: An event-oriented observability library description: Instrument your Haskell codebase with wide, semantically meaningful events.@@ -26,8 +26,14 @@ See "Observe.Event.Backend" for documentation on writing an @EventBackend@. - See [Example.hs](https://github.com/shlevy/eventuo11y/tree/v0.4.0.0/Example.hs) for an example.+ See [eventuo11y-dsl](https://hackage.haskell.org/package/eventuo11y-dsl) for simpler syntax for+ creating application-level instrumentation types. + See [eventuo11y-json](https://hackage.haskell.org/package/eventuo11y-json) for JSON-based rendering+ and backends.++ See [Example.hs](https://github.com/shlevy/eventuo11y/tree/v0.5.0.0/Example.hs) for an example.+ See [eventuo11y-batteries](https://hackage.haskell.org/package/eventuo11y-batteries) for miscellaneous framework-specific helpers. @@ -41,8 +47,7 @@ extra-source-files: CHANGELOG.md Example.hs--tested-with: GHC ==8.10.7 || ==9.0.2 || ==9.2.2+tested-with: GHC == { 8.10.7, 9.2.4 } source-repository head type: git@@ -50,24 +55,19 @@ library exposed-modules:+ Control.Monad.Cleanup Observe.Event Observe.Event.Backend Observe.Event.BackendModification- Observe.Event.Dynamic- Observe.Event.Render.IO.JSON- Observe.Event.Render.JSON build-depends:- , aeson ^>=2.0- , base >=4.14 && <4.17- , bytestring >=0.10 && <0.12- , exceptions ^>=0.10- , primitive ^>=0.7- , resourcet ^>=1.2- , text >=1.2 && <2.1- , time >=1.9 && <1.12- , unliftio-core ^>=0.2- , uuid ^>=1.3+ , base ^>= { 4.14, 4.16 }+ , exceptions ^>= 0.10+ , primitive ^>= 0.7+ , resourcet ^>= { 1.2, 1.3 }+ , safe-exceptions ^>= 0.1+ , transformers ^>= 0.5+ , unliftio-core ^>= 0.2 hs-source-dirs: src default-language: Haskell2010
+ src/Control/Monad/Cleanup.hs view
@@ -0,0 +1,296 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}++-- |+-- Description : Monads that can cleanup within a single monaadic scope+-- Copyright : Copyright 2022 Shea Levy.+-- License : Apache-2.0+-- Maintainer : shea@shealevy.com+module Control.Monad.Cleanup where++import Control.Exception.Safe+import Control.Monad.Catch (ExitCase (..))+import Control.Monad.ST+import Control.Monad.Trans.Except (ExceptT (..), runExceptT)+import Control.Monad.Trans.Identity+import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)+import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS+import qualified Control.Monad.Trans.RWS.Strict as StrictRWS+import Control.Monad.Trans.Reader (ReaderT (..), runReaderT)+import qualified Control.Monad.Trans.State.Lazy as LazyS+import qualified Control.Monad.Trans.State.Strict as StrictS+import qualified Control.Monad.Trans.Writer.Lazy as LazyW+import qualified Control.Monad.Trans.Writer.Strict as StrictW+import Data.Coerce+import Data.Functor.Identity++-- | Monads that can cleanup within a single monadic scope.+--+-- 'MonadCleanup's allow for acquiring some resource and+-- guaranteeing that it will be released,+-- __if computation might continue within that monad__.+--+-- This is very similar to 'MonadMask', with the following differences:+--+-- 1. 'MonadCleanup's may not be able to throw or catch exceptions (no 'MonadCatch'+-- superclass) or mask exceptions.+-- 2. The guarantee of 'generalCleanup' is not as absolute as 'generalBracket' (though the latter+-- always has the @SIGKILL@/power goes out exception). If we can't handle exceptions+-- at all in the monad (at least not without @unsafePerformIO@), then sometimes the cleanup+-- function won't be called, but only in cases where the entire computation the monad is+-- running is going to be aborted.+--+-- This allows 'MonadCleanup' to be used in pure contexts (see 'CleanupNoException') and still+-- provide meaningful semantics.+class (Monad m) => MonadCleanup m where+ -- | Acquire some resource, use it, and clean it up.+ --+ -- cleanup is guaranteed to run+ -- __if computation in the surrounding monadic scope might continue__.+ --+ -- Similar to 'generalBracket', see documentation of 'MonadCleanup' for the differences.+ generalCleanup ::+ -- | Acquire some resource+ m a ->+ -- | Release the resource, observing the outcome of the inner action+ (a -> ExitCase b -> m c) ->+ -- | Inner action to perform with the resource+ (a -> m b) ->+ m (b, c)++-- | An 'Exception' corresponding to the 'ExitCaseAbort' exit case.+data AbortException = AbortException+ deriving stock (Show)+ deriving anyclass (Exception)++-- | Acquire some resource, use it, and clean it up.+--+-- This is to 'bracketWithError' as 'generalCleanup' is to 'generalBracket', see documentation+-- of 'generalCleanup' for more details.+withCleanup ::+ (MonadCleanup m) =>+ m a ->+ (Maybe SomeException -> a -> m b) ->+ (a -> m c) ->+ m c+withCleanup acquire cleanup go = fst <$> generalCleanup acquire release go+ where+ release x (ExitCaseSuccess _) = cleanup Nothing x+ release x ExitCaseAbort = cleanup (Just (toException AbortException)) x+ release x (ExitCaseException e) = cleanup (Just e) x++-- | A [DerivingVia](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html) helper for deriving 'MonadCleanup' from 'MonadMask'.+newtype CleanupFromMask m a = CleanupFromMask (m a) deriving newtype (Functor, Applicative, Monad)++instance (MonadMask m) => MonadCleanup (CleanupFromMask m) where+ generalCleanup :: forall a b c. CleanupFromMask m a -> (a -> ExitCase b -> CleanupFromMask m c) -> (a -> CleanupFromMask m b) -> CleanupFromMask m (b, c)+ generalCleanup = coerce $ generalBracket @m @a @b @c++deriving via CleanupFromMask IO instance MonadCleanup IO++-- | A [DerivingVia](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html) for deriving 'MonadCleanup' in a 'Monad' which+-- __can't__ handle exceptions.+--+-- If cleanup runs at all, it will run in 'ExitCaseSuccess'+--+-- __Note that the associated 'MonadCleanup' instance is invalid if it is possible to catch exceptions in the monad!__+newtype CleanupNoException m a = CleanupNoException (m a) deriving newtype (Functor, Applicative, Monad)++instance (Monad m) => MonadCleanup (CleanupNoException m) where+ generalCleanup acquire release go = do+ x <- acquire+ res <- go x+ carry <- release x (ExitCaseSuccess res)+ pure (res, carry)++deriving via CleanupNoException (ST s) instance MonadCleanup (ST s)++deriving via CleanupNoException Identity instance MonadCleanup Identity++instance (MonadCleanup m) => MonadCleanup (IdentityT m) where+ generalCleanup acquire release use =+ IdentityT $+ generalCleanup+ (runIdentityT acquire)+ (\resource exitCase -> runIdentityT (release resource exitCase))+ (\resource -> runIdentityT (use resource))++instance MonadCleanup m => MonadCleanup (LazyS.StateT s m) where+ generalCleanup acquire release use = LazyS.StateT $ \s0 -> do+ ((b, _s2), (c, s3)) <-+ generalCleanup+ (LazyS.runStateT acquire s0)+ ( \(resource, s1) exitCase -> case exitCase of+ ExitCaseSuccess (b, s2) -> LazyS.runStateT (release resource (ExitCaseSuccess b)) s2+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @s1@.+ ExitCaseException e -> LazyS.runStateT (release resource (ExitCaseException e)) s1+ ExitCaseAbort -> LazyS.runStateT (release resource ExitCaseAbort) s1+ )+ (\(resource, s1) -> LazyS.runStateT (use resource) s1)+ return ((b, c), s3)++instance MonadCleanup m => MonadCleanup (StrictS.StateT s m) where+ generalCleanup acquire release use = StrictS.StateT $ \s0 -> do+ ((b, _s2), (c, s3)) <-+ generalCleanup+ (StrictS.runStateT acquire s0)+ ( \(resource, s1) exitCase -> case exitCase of+ ExitCaseSuccess (b, s2) -> StrictS.runStateT (release resource (ExitCaseSuccess b)) s2+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @s1@.+ ExitCaseException e -> StrictS.runStateT (release resource (ExitCaseException e)) s1+ ExitCaseAbort -> StrictS.runStateT (release resource ExitCaseAbort) s1+ )+ (\(resource, s1) -> StrictS.runStateT (use resource) s1)+ return ((b, c), s3)++instance MonadCleanup m => MonadCleanup (ReaderT r m) where+ generalCleanup acquire release use = ReaderT $ \r ->+ generalCleanup+ (runReaderT acquire r)+ (\resource exitCase -> runReaderT (release resource exitCase) r)+ (\resource -> runReaderT (use resource) r)++instance (MonadCleanup m, Monoid w) => MonadCleanup (StrictW.WriterT w m) where+ generalCleanup acquire release use = StrictW.WriterT $ do+ ((b, _w12), (c, w123)) <-+ generalCleanup+ (StrictW.runWriterT acquire)+ ( \(resource, w1) exitCase -> case exitCase of+ ExitCaseSuccess (b, w12) -> do+ (c, w3) <- StrictW.runWriterT (release resource (ExitCaseSuccess b))+ return (c, mappend w12 w3)+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @w1@.+ ExitCaseException e -> do+ (c, w3) <- StrictW.runWriterT (release resource (ExitCaseException e))+ return (c, mappend w1 w3)+ ExitCaseAbort -> do+ (c, w3) <- StrictW.runWriterT (release resource ExitCaseAbort)+ return (c, mappend w1 w3)+ )+ ( \(resource, w1) -> do+ (a, w2) <- StrictW.runWriterT (use resource)+ return (a, mappend w1 w2)+ )+ return ((b, c), w123)++instance (MonadCleanup m, Monoid w) => MonadCleanup (LazyW.WriterT w m) where+ generalCleanup acquire release use = LazyW.WriterT $ do+ ((b, _w12), (c, w123)) <-+ generalCleanup+ (LazyW.runWriterT acquire)+ ( \(resource, w1) exitCase -> case exitCase of+ ExitCaseSuccess (b, w12) -> do+ (c, w3) <- LazyW.runWriterT (release resource (ExitCaseSuccess b))+ return (c, mappend w12 w3)+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @w1@.+ ExitCaseException e -> do+ (c, w3) <- LazyW.runWriterT (release resource (ExitCaseException e))+ return (c, mappend w1 w3)+ ExitCaseAbort -> do+ (c, w3) <- LazyW.runWriterT (release resource ExitCaseAbort)+ return (c, mappend w1 w3)+ )+ ( \(resource, w1) -> do+ (a, w2) <- LazyW.runWriterT (use resource)+ return (a, mappend w1 w2)+ )+ return ((b, c), w123)++instance (MonadCleanup m, Monoid w) => MonadCleanup (LazyRWS.RWST r w s m) where+ generalCleanup acquire release use = LazyRWS.RWST $ \r s0 -> do+ ((b, _s2, _w12), (c, s3, w123)) <-+ generalCleanup+ (LazyRWS.runRWST acquire r s0)+ ( \(resource, s1, w1) exitCase -> case exitCase of+ ExitCaseSuccess (b, s2, w12) -> do+ (c, s3, w3) <- LazyRWS.runRWST (release resource (ExitCaseSuccess b)) r s2+ return (c, s3, mappend w12 w3)+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @s1@ and @w1@.+ ExitCaseException e -> do+ (c, s3, w3) <- LazyRWS.runRWST (release resource (ExitCaseException e)) r s1+ return (c, s3, mappend w1 w3)+ ExitCaseAbort -> do+ (c, s3, w3) <- LazyRWS.runRWST (release resource ExitCaseAbort) r s1+ return (c, s3, mappend w1 w3)+ )+ ( \(resource, s1, w1) -> do+ (a, s2, w2) <- LazyRWS.runRWST (use resource) r s1+ return (a, s2, mappend w1 w2)+ )+ return ((b, c), s3, w123)++instance (MonadCleanup m, Monoid w) => MonadCleanup (StrictRWS.RWST r w s m) where+ generalCleanup acquire release use = StrictRWS.RWST $ \r s0 -> do+ ((b, _s2, _w12), (c, s3, w123)) <-+ generalCleanup+ (StrictRWS.runRWST acquire r s0)+ ( \(resource, s1, w1) exitCase -> case exitCase of+ ExitCaseSuccess (b, s2, w12) -> do+ (c, s3, w3) <- StrictRWS.runRWST (release resource (ExitCaseSuccess b)) r s2+ return (c, s3, mappend w12 w3)+ -- In the two other cases, the base monad overrides @use@'s state+ -- changes and the state reverts to @s1@ and @w1@.+ ExitCaseException e -> do+ (c, s3, w3) <- StrictRWS.runRWST (release resource (ExitCaseException e)) r s1+ return (c, s3, mappend w1 w3)+ ExitCaseAbort -> do+ (c, s3, w3) <- StrictRWS.runRWST (release resource ExitCaseAbort) r s1+ return (c, s3, mappend w1 w3)+ )+ ( \(resource, s1, w1) -> do+ (a, s2, w2) <- StrictRWS.runRWST (use resource) r s1+ return (a, s2, mappend w1 w2)+ )+ return ((b, c), s3, w123)++instance MonadCleanup m => MonadCleanup (MaybeT m) where+ generalCleanup acquire release use = MaybeT $ do+ (eb, ec) <-+ generalCleanup+ (runMaybeT acquire)+ ( \resourceMay exitCase -> case resourceMay of+ Nothing -> return Nothing -- nothing to release, acquire didn't succeed+ Just resource -> case exitCase of+ ExitCaseSuccess (Just b) -> runMaybeT (release resource (ExitCaseSuccess b))+ ExitCaseException e -> runMaybeT (release resource (ExitCaseException e))+ _ -> runMaybeT (release resource ExitCaseAbort)+ )+ ( \resourceMay -> case resourceMay of+ Nothing -> return Nothing+ Just resource -> runMaybeT (use resource)+ )+ -- The order in which we perform those two 'Maybe' effects doesn't matter,+ -- since the error message is the same regardless.+ return ((,) <$> eb <*> ec)++instance MonadCleanup m => MonadCleanup (ExceptT e m) where+ generalCleanup acquire release use = ExceptT $ do+ (eb, ec) <-+ generalCleanup+ (runExceptT acquire)+ ( \eresource exitCase -> case eresource of+ Left e -> return (Left e) -- nothing to release, acquire didn't succeed+ Right resource -> case exitCase of+ ExitCaseSuccess (Right b) -> runExceptT (release resource (ExitCaseSuccess b))+ ExitCaseException e -> runExceptT (release resource (ExitCaseException e))+ _ -> runExceptT (release resource ExitCaseAbort)+ )+ (either (return . Left) (runExceptT . use))+ return $ do+ -- The order in which we perform those two 'Either' effects determines+ -- which error will win if they are both 'Left's. We want the error from+ -- 'release' to win.+ c <- ec+ b <- eb+ return (b, c)
src/Observe/Event.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-}-+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE CPP #-} -- | -- Description : Core interface for instrumentation with eventuo11y -- Copyright : Copyright 2022 Shea Levy.@@ -40,6 +43,7 @@ -- t'Observe.Event.Render.JSON.RenderFieldJSON' to use JSON rendering 'EventBackend's. module Observe.Event ( Event,+ hoistEvent, -- * Event manipulation #eventmanip# reference,@@ -51,6 +55,10 @@ withEvent, withSubEvent, + -- ** MonadMask variants+ withEventMask,+ withSubEventMask,+ -- ** Acquire/MonadResource variants acquireEvent, acquireSubEvent,@@ -58,6 +66,7 @@ -- * 'EventBackend's EventBackend, subEventBackend,+ causedEventBackend, unitEventBackend, pairEventBackend, hoistEventBackend,@@ -75,8 +84,8 @@ ) where -import Control.Exception-import Control.Monad.Catch+import Control.Exception.Safe+import Control.Monad.Cleanup import Control.Monad.IO.Unlift import Data.Acquire import Observe.Event.Backend@@ -104,6 +113,15 @@ finishFlag :: !(OnceFlag m) } +-- | Hoist an 'Event' along a given natural transformation into a new monad.+hoistEvent :: (Functor m, Functor n) => (forall x. m x -> n x) -> Event m r s f -> Event n r s f+hoistEvent nt Event {..} =+ Event+ { backend = hoistEventBackend nt backend,+ impl = hoistEventImpl nt impl,+ finishFlag = hoistOnceFlag nt finishFlag+ }+ -- | Obtain a reference to an 'Event'. -- -- References are used to link 'Event's together, either in@@ -166,7 +184,7 @@ finalize :: (Monad m) => Event m r s f -> m () finalize (Event {..}) = runOnce finishFlag $ finalizeImpl impl --- | Mark an 'Event' as having failed, possibly due to an 'Exception'.+-- | Mark an 'Event' as having failed due to an 'Exception'. -- -- In normal usage, this should be automatically called via the use of -- the [resource-safe event allocation functions](#g:resourcesafe).@@ -175,7 +193,7 @@ -- 'failEvent'ed. As a result, it is likely pointless to call -- 'addField', 'addParent', or 'addProximate' after this call, -- though it still may be reasonable to call 'reference'.-failEvent :: (Monad m) => Event m r s f -> Maybe SomeException -> m ()+failEvent :: (Monad m) => Event m r s f -> SomeException -> m () failEvent (Event {..}) = runOnce finishFlag . failImpl impl -- | Create a new 'Event', selected by the given selector.@@ -237,20 +255,17 @@ -- The 'Event' is automatically 'finalize'd (or, if appropriate, 'failEvent'ed) -- at the end of the function it's passed to. withEvent ::- (MonadMask m) =>+ (MonadCleanup m) => EventBackend m r s -> forall f. -- | The event selector. s f -> (Event m r s f -> m a) -> m a-withEvent backend sel go = do- (res, ()) <- generalBracket (newEvent backend sel) release go- pure res+withEvent backend sel go = withCleanup (newEvent backend sel) cleanup go where- release ev (ExitCaseSuccess _) = finalize ev- release ev (ExitCaseException e) = failEvent ev $ Just e- release ev ExitCaseAbort = failEvent ev Nothing+ cleanup Nothing = finalize+ cleanup (Just e) = flip failEvent e -- | Run an action with a new 'Event' as a child of the given 'Event', selected by the given selector. --@@ -264,7 +279,7 @@ -- The 'Event' is automatically 'finalize'd (or, if appropriate, 'failEvent'ed) -- at the end of the function it's passed to. withSubEvent ::- (MonadMask m) =>+ (MonadCleanup m) => -- | The parent 'Event'. Event m r s f -> forall f'.@@ -276,10 +291,43 @@ addParent child $ referenceImpl impl go child +-- TODO Implement in terms of withEvent + CleanupFromMask++-- | 'withEvent' in 'MonadMask'+withEventMask ::+ forall m r s.+ (MonadMask m) =>+ EventBackend m r s ->+ forall f.+ -- | The event selector.+ s f ->+ forall a.+ (Event m r s f -> m a) ->+ m a+withEventMask backend sel go = bracketWithError (newEvent backend sel) release go+ where+ release Nothing = finalize+ release (Just e) = flip failEvent e++-- TODO implement in terms of withSubEvent + CleanupFromMask++-- | 'withSubEvent' in 'MonadMask'+withSubEventMask ::+ (MonadMask m) =>+ -- | The parent 'Event'.+ Event m r s f ->+ forall f'.+ -- | The child event selector.+ s f' ->+ (Event m r s f' -> m a) ->+ m a+withSubEventMask (Event {..}) sel go = withEventMask backend sel $ \child -> do+ addParent child $ referenceImpl impl+ go child+ -- | An 'Acquire' variant of 'withEvent', usable in a t'Control.Monad.Trans.Resource.MonadResource' with 'allocateAcquire'. ----- Until [snoyberg/conduit#460](https://github.com/snoyberg/conduit/issues/460) is addressed, exception--- information will not be captured.+-- Prior to @resourcet@ version @1.3.0@, exceptional exit will 'failEvent' with 'AbortException'. acquireEvent :: (MonadUnliftIO m) => EventBackend m r s ->@@ -293,13 +341,16 @@ (runInIO $ newEvent backend sel) (release runInIO) where- release runInIO ev ReleaseException = runInIO $ failEvent ev Nothing+#if MIN_VERSION_resourcet(1,3,0)+ release runInIO ev (ReleaseExceptionWith e) = runInIO $ failEvent ev e+#else+ release runInIO ev ReleaseException = runInIO . failEvent ev $ toException AbortException+#endif release runInIO ev _ = runInIO $ finalize ev -- | An 'Acquire' variant of 'withSubEvent', usable in a t'Control.Monad.Trans.Resource.MonadResource' with 'allocateAcquire'. ----- Until [snoyberg/conduit#460](https://github.com/snoyberg/conduit/issues/460) is addressed, exception--- information will not be captured.+-- Prior to @resourcet@ version @1.3.0@, exceptional exit will 'failEvent' with 'AbortException'. acquireSubEvent :: (MonadUnliftIO m) => -- | The parent event.@@ -319,7 +370,30 @@ -- as a child of the given 'Event'. subEventBackend :: (Monad m) =>+ -- | Bring selectors from the new backend into the parent event's backend.+ --+ -- Use 'id' here plus 'narrowEventBackend'' if you need a more general mapping+ -- between selector types.+ (forall f'. s f' -> t f') -> -- | The parent event.- Event m r s f ->+ Event m r t f -> EventBackend m r s-subEventBackend Event {..} = modifyEventBackend (setAncestor $ referenceImpl impl) backend+subEventBackend inj Event {..} =+ narrowEventBackend inj $+ modifyEventBackend (setAncestor $ referenceImpl impl) backend++-- | An 'EventBackend' where every otherwise causeless event will be marked+-- as caused by the given 'Event'.+causedEventBackend ::+ (Monad m) =>+ -- | Bring selectors from the new backend into the causing event's backend.+ --+ -- Use 'id' here plus 'narrowEventBackend'' if you need a more general mapping+ -- between selector types.+ (forall f'. s f' -> t f') ->+ -- | The causing event.+ Event m r t f ->+ EventBackend m r s+causedEventBackend inj Event {..} =+ narrowEventBackend inj $+ modifyEventBackend (setInitialCause $ referenceImpl impl) backend
src/Observe/Event/Backend.hs view
@@ -18,6 +18,7 @@ unitEventBackend, pairEventBackend, hoistEventBackend,+ hoistEventImpl, narrowEventBackend, narrowEventBackend', @@ -89,7 +90,7 @@ addParentImpl :: !(r -> m ()), addProximateImpl :: !(r -> m ()), finalizeImpl :: !(m ()),- failImpl :: !(Maybe SomeException -> m ())+ failImpl :: !(SomeException -> m ()) } -- | A no-op 'EventBackend'.@@ -114,7 +115,7 @@ newOnceFlag = pure alwaysNewOnceFlag } --- | An 'EventBackend' which sequentially generates 'Event's in the two given 'EventBackend's.+-- | An 'EventBackend' which sequentially generates 'Observe.Event.Event's in the two given 'EventBackend's. -- -- This can be used to emit instrumentation in multiple ways (e.g. logs to grafana and metrics on -- a prometheus HTML page).@@ -154,19 +155,21 @@ EventBackend n r s hoistEventBackend nt backend = EventBackend- { newEventImpl = nt . fmap hoistEventImpl . newEventImpl backend,+ { newEventImpl = nt . fmap (hoistEventImpl nt) . newEventImpl backend, newOnceFlag = hoistOnceFlag nt <$> (nt $ newOnceFlag backend) }- where- hoistEventImpl (EventImpl {..}) =- EventImpl- { referenceImpl,- addFieldImpl = nt . addFieldImpl,- addParentImpl = nt . addParentImpl,- addProximateImpl = nt . addProximateImpl,- finalizeImpl = nt $ finalizeImpl,- failImpl = nt . failImpl- }++-- | Hoist an 'EventImpl' along a given natural transformation into a new monad.+hoistEventImpl :: (forall x. m x -> n x) -> EventImpl m r f -> EventImpl n r f+hoistEventImpl nt (EventImpl {..}) =+ EventImpl+ { referenceImpl,+ addFieldImpl = nt . addFieldImpl,+ addParentImpl = nt . addParentImpl,+ addProximateImpl = nt . addProximateImpl,+ finalizeImpl = nt finalizeImpl,+ failImpl = nt . failImpl+ } -- | Narrow an 'EventBackend' to a new selector type via a given injection function. --
− src/Observe/Event/Dynamic.hs
@@ -1,61 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}---- |--- Description : "Dynamically typed" Event selectors and fields.--- Copyright : Copyright 2022 Shea Levy.--- License : Apache-2.0--- Maintainer : shea@shealevy.com------ Instrumentors can use the types in this module if they don't want--- to define domain-specific types for the code they're instrumenting.-module Observe.Event.Dynamic- ( DynamicEventSelector (..),- DynamicField (..),-- -- * Shorthand types- DynamicEventBackend,- DynamicEvent,- )-where--import Data.Aeson-import Data.String-import Data.Text-import Observe.Event---- | A simple type usable as an 'EventBackend' selector.------ All 'Event's have 'DynamicField' field types.------ Individual 'DynamicEventSelector's are typically constructed--- via the 'IsString' instance, e.g. @withEvent backend "foo" go@--- will call @go@ with an @Event m r DynamicEventSelector DynamicField@--- named "foo".-data DynamicEventSelector f where- DynamicEventSelector :: !Text -> DynamicEventSelector DynamicField--instance (f ~ DynamicField) => IsString (DynamicEventSelector f) where- fromString = DynamicEventSelector . fromString---- | A simple type usable as an 'Event' field type.------ Individual 'DynamicField's are typically constructed via--- the 'IsString' instance, using 'ToJSON' for the value,--- e.g. @addField ev $ "foo" x@ will add @DynamicField "foo" (toJSON x)@--- as a field to @ev@.-data DynamicField = DynamicField- { name :: !Text,- value :: !Value- }---- | Treat a string as a function to a 'DynamicField', calling--- 'toJSON' on its argument.-instance (ToJSON x) => IsString (x -> DynamicField) where- fromString s = DynamicField (fromString s) . toJSON---- | Shorthand for an 'EventBackend' using 'DynamicEventSelector's.-type DynamicEventBackend m r = EventBackend m r DynamicEventSelector---- | Shorthand for an 'Event' using 'DynamicField's.-type DynamicEvent m r = Event m r DynamicEventSelector DynamicField
− src/Observe/Event/Render/IO/JSON.hs
@@ -1,140 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}---- |--- Description : EventBackend for rendering events as JSON to a handle--- Copyright : Copyright 2022 Shea Levy.--- License : Apache-2.0--- Maintainer : shea@shealevy.com-module Observe.Event.Render.IO.JSON- ( jsonHandleBackend,- simpleJsonStderrBackend,-- -- * Internals- JSONRef (..),- )-where--import Control.Concurrent.MVar-import Control.Exception-import Data.Aeson-import Data.Aeson.KeyMap (insert)-import Data.ByteString.Lazy.Char8 (hPutStrLn)-import Data.Coerce-import Data.IORef-import Data.Time.Clock-import Data.UUID (UUID)-import Data.UUID.V4-import Observe.Event-import Observe.Event.Backend-import Observe.Event.Render.JSON-import System.IO (Handle, stderr)---- | The reference type for 'EventBackend's generated by 'jsonHandleBackend'.------ Only expected to be used by type inference or by code implementing other backends--- using this one.-newtype JSONRef = JSONRef UUID deriving newtype (ToJSON)---- | An 'EventBackend' which posts events to a given 'Handle' as JSON.------ Each 'Event' is posted as a single line, as it's completed. As a result, child events--- will typically be posted __before__ their parents (though still possible to correlate via--- event IDs).------ The 'EventBackend' must be the exclusive writer to the 'Handle' while any events are live,--- but it does not 'System.IO.hClose' it itself.-jsonHandleBackend ::- -- The type of structured exceptions- (Exception stex) =>- -- | Where to write 'Event's.- Handle ->- -- | Render a structured exception to JSON- RenderExJSON stex ->- -- | Render a selector, and the fields of 'Event's selected by it, to JSON- RenderSelectorJSON s ->- IO (EventBackend IO JSONRef s)-jsonHandleBackend h renderEx renderSel = do- outputLock <- newMVar ()- let emit :: Object -> IO ()- emit o = withMVar outputLock \() ->- hPutStrLn h $ encode o- pure $- EventBackend- { newEventImpl = \sel -> do- let (k, renderField) = renderSel sel- eventRef <- coerce nextRandom- start <- getCurrentTime- fieldsRef <- newIORef mempty- parentsRef <- newIORef mempty- proximatesRef <- newIORef mempty- let finish r = do- end <- getCurrentTime- fields <- readIORef fieldsRef- parents <- readIORef parentsRef- proximates <- readIORef proximatesRef- emit- ( k- .= Object- ( "event-id" .= eventRef- <> "start" .= start- <> "end" .= end- <> ifNotNull "fields" fields- <> ifNotNull "parents" parents- <> ifNotNull "proximate-causes" proximates- <> case r of- Abort -> "abort" .= True- StructuredFail e -> "structured-exception" .= renderEx e- UnstructuredFail e -> "unstructured-exception" .= show e- Finalized -> mempty- )- )- pure $- EventImpl- { referenceImpl = eventRef,- addFieldImpl = \field ->- atomicModifyIORef' fieldsRef \fields ->- (uncurry insert (renderField field) fields, ()),- addParentImpl = \r ->- atomicModifyIORef' parentsRef \refs -> (r : refs, ()),- addProximateImpl = \r ->- atomicModifyIORef' proximatesRef \refs -> (r : refs, ()),- finalizeImpl = finish Finalized,- failImpl = \me ->- finish- ( case me of- Nothing -> Abort- Just e -> case fromException e of- Just se -> StructuredFail se- Nothing -> UnstructuredFail e- )- },- newOnceFlag = newOnceFlagMVar- }---- | An 'EventBackend' which posts events to @stderr@ as JSON.------ Each 'Event' is posted as a single line, as it's completed. As a result, child events--- will typically be posted __before__ their parents (though still possible to correlate via--- event IDs).------ Any instrumented 'Exception's descended from 'SomeJSONException' will be structurally rendered.------ The 'EventBackend' must be the exclusive writer to @stderr@ while any events are live,--- but it does not 'System.IO.hClose' it itself.-simpleJsonStderrBackend :: RenderSelectorJSON s -> IO (EventBackend IO JSONRef s)-simpleJsonStderrBackend = jsonHandleBackend stderr renderJSONException---- | Why did an 'Event' finish?-data FinishReason stex- = Abort- | StructuredFail stex- | UnstructuredFail SomeException- | Finalized---- | Add k .= v if v is not 'null'.-ifNotNull :: (Foldable f, ToJSON (f v)) => Key -> f v -> Object-ifNotNull k v = if null v then mempty else k .= v
− src/Observe/Event/Render/JSON.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RankNTypes #-}---- |--- Description : Renderers for serializing Events as JSON--- Copyright : Copyright 2022 Shea Levy.--- License : Apache-2.0--- Maintainer : shea@shealevy.com------ Instrumentors will need to provide instances of 'RenderSelectorJSON'--- and 'RenderFieldJSON' for their domain-specific types to use their--- t'Observe.Event.Event's with JSON-consuming t'Observe.Event.EventBackend's.-module Observe.Event.Render.JSON- ( RenderSelectorJSON,- RenderFieldJSON,-- -- * Rendering structured exceptions- RenderExJSON,-- -- ** SomeJSONException- renderJSONException,- SomeJSONException (..),- jsonExceptionToException,- jsonExceptionFromException,-- -- * Observe.Event.Dynamic support- renderDynamicEventSelectorJSON,- renderDynamicFieldJSON,- )-where--import Control.Exception-import Data.Aeson-import Data.Aeson.Key-import Data.Typeable-import Observe.Event.Dynamic---- | A function to render a given selector, its fields, as JSON.------ The 'Key' is the event name/category.-type RenderSelectorJSON sel = forall f. sel f -> (Key, RenderFieldJSON f)---- | A function to render a given @field@ as JSON.------ The 'Key' is a field name, the 'Value' is an arbitrary--- rendering of the field value (if any).-type RenderFieldJSON field = field -> (Key, Value)---- | A function to render a given structured exception to JSON.-type RenderExJSON stex = stex -> Value---- | Render a 'DynamicEventSelector' and all its sub-fields.-renderDynamicEventSelectorJSON :: RenderSelectorJSON DynamicEventSelector-renderDynamicEventSelectorJSON (DynamicEventSelector n) =- (fromText n, renderDynamicFieldJSON)---- | Render a 'DynamicField'-renderDynamicFieldJSON :: RenderFieldJSON DynamicField-renderDynamicFieldJSON f = (fromText (name f), value f)---- | Render a 'SomeJSONException' to JSON.------ It is __not__ necessary to use 'SomeJSONException' for the base of your--- structured exceptions in a JSON backend, so long as you provide a--- 'RenderExJSON' for your base exception type.-renderJSONException :: RenderExJSON SomeJSONException-renderJSONException (SomeJSONException render e) = render e---- | A possible base type for structured exceptions renderable to JSON.------ It is __not__ necessary to use 'SomeJSONException' for the base of your--- structured exceptions in a JSON backend, so long as you provide a--- 'RenderExJSON' for your base exception type.-data SomeJSONException- = forall e. Exception e => SomeJSONException (RenderExJSON e) e--instance Show SomeJSONException where- show (SomeJSONException _ e) = show e- showsPrec i (SomeJSONException _ e) = showsPrec i e--instance Exception SomeJSONException---- | Used to create sub-classes of 'SomeJSONException'.-jsonExceptionToException :: (Exception e) => RenderExJSON e -> e -> SomeException-jsonExceptionToException render = toException . SomeJSONException render---- | Used to create sub-classes of 'SomeJSONException'.-jsonExceptionFromException :: (Exception e) => SomeException -> Maybe e-jsonExceptionFromException x = do- SomeJSONException _ a <- fromException x- cast a