{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module Systemd.Journal
( -- * Logging
logMessage
, logMessageWith
, logProperties
-- * Log Properties
, LogProperties
-- ** Standard systemd log properties
, message
, messageId
, priority
, Syslog.Priority(..)
, codeFile
, codeLine
, codeFunc
, errno
, syslogFacility
, syslogIdentifier
, syslogPid
-- ** Custom Properties
, LogKey
, mkLogKey
) where
import Control.Monad (void)
import Data.Char (toUpper)
import Data.Data (Data)
import Data.Hashable (Hashable)
import Data.Monoid (Monoid, mappend)
import Data.String (IsString (..))
import Data.Typeable (Typeable)
import Foreign (Ptr, throwIfNeg)
import System.Posix.Types (CPid(..))
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.UUID as UUID
import qualified Data.Vector.Storable as V
import qualified System.Posix.Syslog as Syslog
import qualified System.Posix.Types.Iovec as Iovec
--------------------------------------------------------------------------------
foreign import ccall "sd_journal_sendv"
sdJournalSendV :: Ptr Iovec.CIovec -> Int -> IO Int
--------------------------------------------------------------------------------
newtype LogKey = LogKey Text.Text
deriving (Eq, Data, Hashable, Ord, Read, Show, Typeable, Monoid)
instance IsString LogKey where
fromString = LogKey . Text.pack . map toUpper
--------------------------------------------------------------------------------
-- | Construct a 'LogKey' by converting to uppercase, as required by the
-- journal.
mkLogKey :: Text.Text -> LogKey
mkLogKey = LogKey . Text.toUpper
--------------------------------------------------------------------------------
-- | A structured object of properties to log. You generally don't construct
-- this yourself, but you use the monoid instance and smart constructors below.
-- For example,
--
-- > logProperties (message "Oh god, it burns!" <> priority Emergency)
type LogProperties = HashMap.HashMap LogKey Text.Text
--------------------------------------------------------------------------------
-- | The human readable message string for this entry. This is supposed to be
-- the primary text shown to the user. It is usually not translated (but might be
-- in some cases), and is not supposed to be parsed for meta data.
message :: Text.Text -> LogProperties
message = HashMap.singleton (LogKey "MESSAGE")
--------------------------------------------------------------------------------
-- | A 128bit message identifier ID for recognizing certain message types, if
-- this is desirable. Developers can generate a new ID for this purpose with
-- @journalctl --new-id@.
messageId :: UUID.UUID -> LogProperties
messageId =
HashMap.singleton (LogKey "MESSAGE_ID") . Text.pack . UUID.toString
--------------------------------------------------------------------------------
-- | A priority value compatible with syslog's priority concept.
priority :: Syslog.Priority -> LogProperties
priority =
HashMap.singleton (LogKey "PRIORITY") . Text.pack . show . fromEnum
--------------------------------------------------------------------------------
-- | The source code file generating this message.
codeFile :: FilePath -> LogProperties
codeFile =
HashMap.singleton (LogKey "CODE_FILE") . Text.pack
--------------------------------------------------------------------------------
-- | The source code line number generating this message.
codeLine :: Int -> LogProperties
codeLine = HashMap.singleton (LogKey "CODE_LINE") . Text.pack . show
--------------------------------------------------------------------------------
-- | The source code function name generating this message.
codeFunc :: Text.Text -> LogProperties
codeFunc = HashMap.singleton (LogKey "CODE_FUNC")
--------------------------------------------------------------------------------
-- | The low-level Unix error number causing this entry, if any. Contains the
-- numeric value of @errno(3)@.
errno :: Int -> LogProperties
errno = HashMap.singleton (LogKey "ERRNO") . Text.pack . show
--------------------------------------------------------------------------------
-- | Syslog compatibility field.
syslogFacility :: Syslog.Facility -> LogProperties
syslogFacility =
HashMap.singleton (LogKey "SYSLOG_FACILITY") . Text.pack . show . fromEnum
--------------------------------------------------------------------------------
-- | Syslog compatibility field.
syslogIdentifier :: Text.Text -> LogProperties
syslogIdentifier =
HashMap.singleton (LogKey "SYSLOG_IDENTIFIER")
--------------------------------------------------------------------------------
-- | Syslog compatibility field.
syslogPid :: CPid -> LogProperties
syslogPid (CPid pid) =
HashMap.singleton (LogKey "SYSLOG_PID") (Text.pack $ show pid)
--------------------------------------------------------------------------------
-- | Log a message to the systemd journal.
logMessage :: Text.Text -> IO ()
logMessage = logProperties . message
--------------------------------------------------------------------------------
-- | Log a message and supply extra metadata. All metadata keys will be
-- converted to uppercase for you.
--
-- Note: The 'Message' meta-data property will be replaced with the first
-- parameter to this function. If you don't want this, use 'logMap'
logMessageWith :: Text.Text -> LogProperties -> IO ()
logMessageWith text meta = logProperties $ mappend meta $ message text
--------------------------------------------------------------------------------
logProperties :: LogProperties -> IO ()
logProperties meta = void $
throwIfNeg (("sd_journal_send returned :" ++) . show) $
go id 0 (HashMap.toList meta)
where
go f n [] = V.unsafeWith (V.fromList (f [])) $ \iovecs ->
sdJournalSendV iovecs n
go f n ((LogKey k, v) : xs) =
Iovec.unsafeUseAsCIovec (Text.encodeUtf8 (Text.intercalate "=" [k, v])) $
\messageIovec -> go (f . (++ [messageIovec])) (n + 1) xs