----------------------------------------------------------------------------------------------------

-- | Streaming interface to journalctl. Use 'entryStream' to stream
--   journalctl entries as they are created.
--
--   Designed with qualified import in mind.
--   For example, if you import it as @Journal@, then 'Entry' becomes
--   @Journal.Entry@, and 'Exception' becomes @Journal.Exception@.
--
module Systemd.Journalctl.Stream (
    -- * Journal entry
    Entry (..)
  , Cursor
    -- * Streaming
  , StreamStart (..)
  , entryStream
    -- * Exceptions
  , Exception (..)
  ) where

-- base
import System.IO (Handle)
import Data.Maybe (fromJust)
import Control.Exception qualified as Base
import System.Posix.Types (CPid (..), ProcessID)
import Data.Foldable (toList)
-- text
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Data.Text qualified as Text
-- bytestring
import Data.ByteString (ByteString)
import Data.ByteString qualified as ByteString
-- aeson
import Data.Aeson (FromJSON, parseJSON, (.:), (.:?), ToJSON)
import Data.Aeson qualified as JSON
import Data.Aeson.Types qualified as JSON
import Data.Aeson.KeyMap qualified as KeyMap
-- time
import Data.Time.Clock (secondsToNominalDiffTime)
import Data.Time.Clock.POSIX (POSIXTime)
import Data.Time.LocalTime (LocalTime)
import Data.Time.Format (formatTime, defaultTimeLocale)
-- process
import System.Process qualified as System
-- conduit
import Conduit (MonadResource, MonadThrow, throwM)
import Data.Conduit (ConduitT, (.|))
import Data.Conduit.Combinators qualified as Conduit

-- | A cursor is an opaque text string that uniquely describes
--   the position of an entry in the journal and is portable
--   across machines, platforms and journal files.
--
--   The 'Ord' instance does not order by time. Given two entries
--   @e1@ and @e2@, @e1@ having an earlier timestamp than @e2@ doesn't
--   mean that @entryCursor e1 < entryCursor e2@.
newtype Cursor = Cursor Text deriving (Cursor -> Cursor -> Bool
(Cursor -> Cursor -> Bool)
-> (Cursor -> Cursor -> Bool) -> Eq Cursor
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Cursor -> Cursor -> Bool
== :: Cursor -> Cursor -> Bool
$c/= :: Cursor -> Cursor -> Bool
/= :: Cursor -> Cursor -> Bool
Eq, Eq Cursor
Eq Cursor =>
(Cursor -> Cursor -> Ordering)
-> (Cursor -> Cursor -> Bool)
-> (Cursor -> Cursor -> Bool)
-> (Cursor -> Cursor -> Bool)
-> (Cursor -> Cursor -> Bool)
-> (Cursor -> Cursor -> Cursor)
-> (Cursor -> Cursor -> Cursor)
-> Ord Cursor
Cursor -> Cursor -> Bool
Cursor -> Cursor -> Ordering
Cursor -> Cursor -> Cursor
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Cursor -> Cursor -> Ordering
compare :: Cursor -> Cursor -> Ordering
$c< :: Cursor -> Cursor -> Bool
< :: Cursor -> Cursor -> Bool
$c<= :: Cursor -> Cursor -> Bool
<= :: Cursor -> Cursor -> Bool
$c> :: Cursor -> Cursor -> Bool
> :: Cursor -> Cursor -> Bool
$c>= :: Cursor -> Cursor -> Bool
>= :: Cursor -> Cursor -> Bool
$cmax :: Cursor -> Cursor -> Cursor
max :: Cursor -> Cursor -> Cursor
$cmin :: Cursor -> Cursor -> Cursor
min :: Cursor -> Cursor -> Cursor
Ord, Int -> Cursor -> ShowS
[Cursor] -> ShowS
Cursor -> String
(Int -> Cursor -> ShowS)
-> (Cursor -> String) -> ([Cursor] -> ShowS) -> Show Cursor
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Cursor -> ShowS
showsPrec :: Int -> Cursor -> ShowS
$cshow :: Cursor -> String
show :: Cursor -> String
$cshowList :: [Cursor] -> ShowS
showList :: [Cursor] -> ShowS
Show)

instance FromJSON Cursor where
  parseJSON :: Value -> Parser Cursor
parseJSON = String -> (Text -> Parser Cursor) -> Value -> Parser Cursor
forall a. String -> (Text -> Parser a) -> Value -> Parser a
JSON.withText String
"Cursor" ((Text -> Parser Cursor) -> Value -> Parser Cursor)
-> (Text -> Parser Cursor) -> Value -> Parser Cursor
forall a b. (a -> b) -> a -> b
$ Cursor -> Parser Cursor
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Cursor -> Parser Cursor)
-> (Text -> Cursor) -> Text -> Parser Cursor
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Cursor
Cursor

instance ToJSON Cursor where
  toJSON :: Cursor -> Value
toJSON (Cursor Text
t) = Text -> Value
JSON.String Text
t

-- | A journal entry.
data Entry = Entry
  { -- | Process ID.
    Entry -> Maybe ProcessID
entryPID :: Maybe ProcessID
    -- | The name of the originating host.
  , Entry -> Text
entryHostname :: Text
    -- | Namespace identifier.
  , Entry -> Maybe Text
entryNamespace :: Maybe Text
    -- | Process name.
  , Entry -> Maybe Text
entryProcess :: Maybe Text
    -- | File path to the executable.
  , Entry -> Maybe String
entryExecutable :: Maybe FilePath
    -- | The cursor for the entry.
  , Entry -> Cursor
entryCursor :: Cursor
    -- | The time the entry was received by the journal.
  , Entry -> POSIXTime
entryTimestamp :: POSIXTime
    -- | Unit name, if present.
  , Entry -> Maybe Text
entryUnit :: Maybe Text
    -- | Entry message. It may come in binary or textual format.
  , Entry -> Maybe (Either ByteString Text)
entryMessage :: Maybe (Either ByteString Text)
    } deriving Int -> Entry -> ShowS
[Entry] -> ShowS
Entry -> String
(Int -> Entry -> ShowS)
-> (Entry -> String) -> ([Entry] -> ShowS) -> Show Entry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Entry -> ShowS
showsPrec :: Int -> Entry -> ShowS
$cshow :: Entry -> String
show :: Entry -> String
$cshowList :: [Entry] -> ShowS
showList :: [Entry] -> ShowS
Show

-- | Utility type to parse values (mainly numbers) that are received
--   as text.
newtype AsText a = AsText { forall a. AsText a -> a
asText :: a } deriving Int -> AsText a -> ShowS
[AsText a] -> ShowS
AsText a -> String
(Int -> AsText a -> ShowS)
-> (AsText a -> String) -> ([AsText a] -> ShowS) -> Show (AsText a)
forall a. Show a => Int -> AsText a -> ShowS
forall a. Show a => [AsText a] -> ShowS
forall a. Show a => AsText a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: forall a. Show a => Int -> AsText a -> ShowS
showsPrec :: Int -> AsText a -> ShowS
$cshow :: forall a. Show a => AsText a -> String
show :: AsText a -> String
$cshowList :: forall a. Show a => [AsText a] -> ShowS
showList :: [AsText a] -> ShowS
Show

instance FromJSON a => FromJSON (AsText a) where
  parseJSON :: Value -> Parser (AsText a)
parseJSON = String -> (Text -> Parser (AsText a)) -> Value -> Parser (AsText a)
forall a. String -> (Text -> Parser a) -> Value -> Parser a
JSON.withText String
"AsText" ((Text -> Parser (AsText a)) -> Value -> Parser (AsText a))
-> (Text -> Parser (AsText a)) -> Value -> Parser (AsText a)
forall a b. (a -> b) -> a -> b
$
    (String -> Parser (AsText a))
-> (a -> Parser (AsText a)) -> Either String a -> Parser (AsText a)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Parser (AsText a)
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (AsText a -> Parser (AsText a)
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AsText a -> Parser (AsText a))
-> (a -> AsText a) -> a -> Parser (AsText a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> AsText a
forall a. a -> AsText a
AsText) (Either String a -> Parser (AsText a))
-> (Text -> Either String a) -> Text -> Parser (AsText a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Either String a
forall a. FromJSON a => ByteString -> Either String a
JSON.eitherDecodeStrict (ByteString -> Either String a)
-> (Text -> ByteString) -> Text -> Either String a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> ByteString
encodeUtf8

{- Journal fields

For a more complete list of fields and documentation, go to:

https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html

-}

instance FromJSON Entry where
  parseJSON :: Value -> Parser Entry
parseJSON = String -> (Object -> Parser Entry) -> Value -> Parser Entry
forall a. String -> (Object -> Parser a) -> Value -> Parser a
JSON.withObject String
"Entry" ((Object -> Parser Entry) -> Value -> Parser Entry)
-> (Object -> Parser Entry) -> Value -> Parser Entry
forall a b. (a -> b) -> a -> b
$ \Object
o -> Maybe ProcessID
-> Text
-> Maybe Text
-> Maybe Text
-> Maybe String
-> Cursor
-> POSIXTime
-> Maybe Text
-> Maybe (Either ByteString Text)
-> Entry
Entry
    (Maybe ProcessID
 -> Text
 -> Maybe Text
 -> Maybe Text
 -> Maybe String
 -> Cursor
 -> POSIXTime
 -> Maybe Text
 -> Maybe (Either ByteString Text)
 -> Entry)
-> Parser (Maybe ProcessID)
-> Parser
     (Text
      -> Maybe Text
      -> Maybe Text
      -> Maybe String
      -> Cursor
      -> POSIXTime
      -> Maybe Text
      -> Maybe (Either ByteString Text)
      -> Entry)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ((AsText Int32 -> ProcessID)
-> Maybe (AsText Int32) -> Maybe ProcessID
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int32 -> ProcessID
CPid (Int32 -> ProcessID)
-> (AsText Int32 -> Int32) -> AsText Int32 -> ProcessID
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AsText Int32 -> Int32
forall a. AsText a -> a
asText) (Maybe (AsText Int32) -> Maybe ProcessID)
-> Parser (Maybe (AsText Int32)) -> Parser (Maybe ProcessID)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe (AsText Int32))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"_PID")
    Parser
  (Text
   -> Maybe Text
   -> Maybe Text
   -> Maybe String
   -> Cursor
   -> POSIXTime
   -> Maybe Text
   -> Maybe (Either ByteString Text)
   -> Entry)
-> Parser Text
-> Parser
     (Maybe Text
      -> Maybe Text
      -> Maybe String
      -> Cursor
      -> POSIXTime
      -> Maybe Text
      -> Maybe (Either ByteString Text)
      -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"_HOSTNAME"
    Parser
  (Maybe Text
   -> Maybe Text
   -> Maybe String
   -> Cursor
   -> POSIXTime
   -> Maybe Text
   -> Maybe (Either ByteString Text)
   -> Entry)
-> Parser (Maybe Text)
-> Parser
     (Maybe Text
      -> Maybe String
      -> Cursor
      -> POSIXTime
      -> Maybe Text
      -> Maybe (Either ByteString Text)
      -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"_NAMESPACE"
    Parser
  (Maybe Text
   -> Maybe String
   -> Cursor
   -> POSIXTime
   -> Maybe Text
   -> Maybe (Either ByteString Text)
   -> Entry)
-> Parser (Maybe Text)
-> Parser
     (Maybe String
      -> Cursor
      -> POSIXTime
      -> Maybe Text
      -> Maybe (Either ByteString Text)
      -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"_COMM"
    Parser
  (Maybe String
   -> Cursor
   -> POSIXTime
   -> Maybe Text
   -> Maybe (Either ByteString Text)
   -> Entry)
-> Parser (Maybe String)
-> Parser
     (Cursor
      -> POSIXTime
      -> Maybe Text
      -> Maybe (Either ByteString Text)
      -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe String)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"_EXE"
    Parser
  (Cursor
   -> POSIXTime
   -> Maybe Text
   -> Maybe (Either ByteString Text)
   -> Entry)
-> Parser Cursor
-> Parser
     (POSIXTime
      -> Maybe Text -> Maybe (Either ByteString Text) -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Cursor
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"__CURSOR"
    Parser
  (POSIXTime
   -> Maybe Text -> Maybe (Either ByteString Text) -> Entry)
-> Parser POSIXTime
-> Parser (Maybe Text -> Maybe (Either ByteString Text) -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Pico -> POSIXTime
secondsToNominalDiffTime (Pico -> POSIXTime)
-> (AsText Pico -> Pico) -> AsText Pico -> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Pico -> Pico -> Pico
forall a. Fractional a => a -> a -> a
/Pico
1000000) (Pico -> Pico) -> (AsText Pico -> Pico) -> AsText Pico -> Pico
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AsText Pico -> Pico
forall a. AsText a -> a
asText (AsText Pico -> POSIXTime)
-> Parser (AsText Pico) -> Parser POSIXTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (AsText Pico)
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"__REALTIME_TIMESTAMP")
    Parser (Maybe Text -> Maybe (Either ByteString Text) -> Entry)
-> Parser (Maybe Text)
-> Parser (Maybe (Either ByteString Text) -> Entry)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"UNIT"
    Parser (Maybe (Either ByteString Text) -> Entry)
-> Parser (Maybe (Either ByteString Text)) -> Parser Entry
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Parser (Maybe (Either ByteString Text))
messageParser Object
o

messageParser :: JSON.Object -> JSON.Parser (Maybe (Either ByteString Text))
messageParser :: Object -> Parser (Maybe (Either ByteString Text))
messageParser Object
obj =
  case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup Key
"MESSAGE" Object
obj of
    Just (JSON.String Text
t) -> Maybe (Either ByteString Text)
-> Parser (Maybe (Either ByteString Text))
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe (Either ByteString Text)
 -> Parser (Maybe (Either ByteString Text)))
-> Maybe (Either ByteString Text)
-> Parser (Maybe (Either ByteString Text))
forall a b. (a -> b) -> a -> b
$ Either ByteString Text -> Maybe (Either ByteString Text)
forall a. a -> Maybe a
Just (Either ByteString Text -> Maybe (Either ByteString Text))
-> Either ByteString Text -> Maybe (Either ByteString Text)
forall a b. (a -> b) -> a -> b
$ Text -> Either ByteString Text
forall a b. b -> Either a b
Right Text
t
    Just (JSON.Array Array
arr) -> Either ByteString Text -> Maybe (Either ByteString Text)
forall a. a -> Maybe a
Just (Either ByteString Text -> Maybe (Either ByteString Text))
-> ([Word8] -> Either ByteString Text)
-> [Word8]
-> Maybe (Either ByteString Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Either ByteString Text
forall a b. a -> Either a b
Left (ByteString -> Either ByteString Text)
-> ([Word8] -> ByteString) -> [Word8] -> Either ByteString Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Word8] -> ByteString
ByteString.pack ([Word8] -> Maybe (Either ByteString Text))
-> Parser [Word8] -> Parser (Maybe (Either ByteString Text))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Value -> Parser Word8) -> [Value] -> Parser [Word8]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM Value -> Parser Word8
forall a. FromJSON a => Value -> Parser a
parseJSON (Array -> [Value]
forall a. Vector a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList Array
arr)
    Just Value
JSON.Null -> Maybe (Either ByteString Text)
-> Parser (Maybe (Either ByteString Text))
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe (Either ByteString Text)
forall a. Maybe a
Nothing
    Maybe Value
Nothing -> Maybe (Either ByteString Text)
-> Parser (Maybe (Either ByteString Text))
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe (Either ByteString Text)
forall a. Maybe a
Nothing
    Maybe Value
_ -> String -> Parser (Maybe (Either ByteString Text))
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Parser (Maybe (Either ByteString Text)))
-> String -> Parser (Maybe (Either ByteString Text))
forall a b. (a -> b) -> a -> b
$ String
"Couldn't parse MESSAGE. Expected String, Array or Null."

-- | Exception raised while streaming entries from journalctl.
data Exception = JSONError String deriving Int -> Exception -> ShowS
[Exception] -> ShowS
Exception -> String
(Int -> Exception -> ShowS)
-> (Exception -> String)
-> ([Exception] -> ShowS)
-> Show Exception
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Exception -> ShowS
showsPrec :: Int -> Exception -> ShowS
$cshow :: Exception -> String
show :: Exception -> String
$cshowList :: [Exception] -> ShowS
showList :: [Exception] -> ShowS
Show

instance Base.Exception Exception where

-- | Where to start a stream.
data StreamStart =
    -- | Start from the given time.
    StartTime LocalTime
    -- | Start from the given number of lines back.
    --   You can use @Lines 0@ to start the stream without
    --   looking for previous lines.
  | Lines Int
    -- | Start /at/ the given cursor.
  | AtCursor Cursor
    -- | Start /after/ the given cursor.
  | AfterCursor Cursor

-- | Translate a 'StreamStart' into the arguments required
--   for journalctl.
streamStartArgs :: StreamStart -> [String]
streamStartArgs :: StreamStart -> [String]
streamStartArgs (StartTime LocalTime
t) =
  [ String
"--since", TimeLocale -> String -> LocalTime -> String
forall t. FormatTime t => TimeLocale -> String -> t -> String
formatTime TimeLocale
defaultTimeLocale String
"%F %T" LocalTime
t ]
streamStartArgs (Lines Int
n) =
  [ String
"--lines" , Int -> String
forall a. Show a => a -> String
show Int
n ]
streamStartArgs (AtCursor (Cursor Text
t)) =
  [ String
"--cursor", Text -> String
Text.unpack Text
t ]
streamStartArgs (AfterCursor (Cursor Text
t)) =
  [ String
"--after-cursor", Text -> String
Text.unpack Text
t ]

-- | Stream all journal entries starting from the given point.
--   If an entry fails to parse, a 'JSONError' will be thrown.
entryStream
  :: (MonadResource m, MonadThrow m)
  => StreamStart -- ^ Where to start streaming entries.
  -> ConduitT i Entry m () -- ^ Stream of journal entries.
entryStream :: forall (m :: * -> *) i.
(MonadResource m, MonadThrow m) =>
StreamStart -> ConduitT i Entry m ()
entryStream StreamStart
start =
  let args :: [String]
      args :: [String]
args = StreamStart -> [String]
streamStartArgs StreamStart
start [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [ String
"--follow", String
"--output", String
"json" ]
      hdl :: IO Handle
      hdl :: IO Handle
hdl = ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
 -> Handle)
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO Handle
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Maybe Handle
_, Maybe Handle
h, Maybe Handle
_, ProcessHandle
_) -> Maybe Handle -> Handle
forall a. HasCallStack => Maybe a -> a
fromJust Maybe Handle
h)
          (IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
 -> IO Handle)
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO Handle
forall a b. (a -> b) -> a -> b
$ CreateProcess
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
System.createProcess
          (CreateProcess
 -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle))
-> CreateProcess
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
forall a b. (a -> b) -> a -> b
$ (String -> [String] -> CreateProcess
System.proc String
"journalctl" [String]
args)
              { System.std_out = System.CreatePipe
                }
  in  IO Handle -> ConduitT i ByteString m ()
forall (m :: * -> *) i.
MonadResource m =>
IO Handle -> ConduitT i ByteString m ()
Conduit.sourceIOHandle IO Handle
hdl
        ConduitT i ByteString m ()
-> ConduitT ByteString Entry m () -> ConduitT i Entry m ()
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| ConduitT ByteString ByteString m ()
forall (m :: * -> *) seq.
(Monad m, IsSequence seq, Element seq ~ Word8) =>
ConduitT seq seq m ()
Conduit.linesUnboundedAscii
        ConduitT ByteString ByteString m ()
-> ConduitT ByteString Entry m () -> ConduitT ByteString Entry m ()
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| (ByteString -> m Entry) -> ConduitT ByteString Entry m ()
forall (m :: * -> *) a b.
Monad m =>
(a -> m b) -> ConduitT a b m ()
Conduit.mapM ((String -> m Entry)
-> (Entry -> m Entry) -> Either String Entry -> m Entry
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Exception -> m Entry
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
throwM (Exception -> m Entry)
-> (String -> Exception) -> String -> m Entry
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Exception
JSONError) Entry -> m Entry
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Entry -> m Entry)
-> (ByteString -> Either String Entry) -> ByteString -> m Entry
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Either String Entry
forall a. FromJSON a => ByteString -> Either String a
JSON.eitherDecodeStrict)