Push to journalctl

This commit is contained in:
2026-09-30 12:33:31 +03:00
parent a427acd6ed
commit 1098be227d
4 changed files with 94 additions and 7 deletions
+6 -5
View File
@@ -2,9 +2,9 @@
, cereal, cereal-conduit, cereal-text, conduit, containers , cereal, cereal-conduit, cereal-text, conduit, containers
, data-default, directory, ekg-core, exceptions, filepath, hedgehog , data-default, directory, ekg-core, exceptions, filepath, hedgehog
, hspec, hspec-hedgehog, http-media, http-types, katip, lens , hspec, hspec-hedgehog, http-media, http-types, katip, lens
, lens-aeson, lib, network, process, retry, servant, servant-server , lens-aeson, lib, libsystemd-journal, network, process, retry
, stm, temporary, text, time, unliftio, unordered-containers, uuid , servant, servant-server, stm, temporary, text, time, unliftio
, wai, warp, websockets , unordered-containers, uuid, wai, warp, websockets
}: }:
mkDerivation { mkDerivation {
pname = "home-assistant-controller"; pname = "home-assistant-controller";
@@ -16,8 +16,9 @@ mkDerivation {
aeson annotated-exception async base bytestring cereal aeson annotated-exception async base bytestring cereal
cereal-conduit cereal-text conduit containers data-default cereal-conduit cereal-text conduit containers data-default
directory ekg-core exceptions filepath http-media http-types katip directory ekg-core exceptions filepath http-media http-types katip
lens lens-aeson network process retry servant servant-server stm lens lens-aeson libsystemd-journal network process retry servant
text time unliftio unordered-containers uuid wai warp websockets servant-server stm text time unliftio unordered-containers uuid wai
warp websockets
]; ];
executableHaskellDepends = [ base ]; executableHaskellDepends = [ base ];
testHaskellDepends = [ testHaskellDepends = [
+2
View File
@@ -87,6 +87,7 @@ library
, HomeAssistant.Runtime.Supervisor , HomeAssistant.Runtime.Supervisor
, HomeAssistant.Runtime.RateLimit , HomeAssistant.Runtime.RateLimit
, HttpServer , HttpServer
, Katip.Scribes.Journal
-- Modules included in this library but not exported. -- Modules included in this library but not exported.
-- other-modules: -- other-modules:
@@ -130,6 +131,7 @@ library
, warp , warp
, wai , wai
, retry , retry
, libsystemd-journal
-- Directories containing source files. -- Directories containing source files.
hs-source-dirs: src hs-source-dirs: src
+16 -2
View File
@@ -34,6 +34,9 @@ import Data.UUID (toText)
import AFRP (Request(..), Event(..)) import AFRP (Request(..), Event(..))
import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import HomeAssistant.Runtime.Flags (Flags) import HomeAssistant.Runtime.Flags (Flags)
import Katip.Scribes.Journal (mkJournalScribe)
import System.Environment (lookupEnv)
import Data.Maybe (isJust)
-- | Shared runtime state: inbound is a broadcast channel (controllers -- | Shared runtime state: inbound is a broadcast channel (controllers
-- read from 'dupTChan' copies), outbound queues service calls for the -- read from 'dupTChan' copies), outbound queues service calls for the
@@ -50,8 +53,7 @@ data Bus = Bus
withBus :: Severity -> AppMetrics -> Flags -> (Bus -> IO a) -> IO a withBus :: Severity -> AppMetrics -> Flags -> (Bus -> IO a) -> IO a
withBus severity metrics flags callback = do withBus severity metrics flags callback = do
handleScribe <- mkHandleScribe ColorIfTerminal stdout (permitItem severity) V2 let makeLogEnv = registerJournalScribe =<< registerHandleScribe =<< initLogEnv "hass-controller" "production"
let makeLogEnv = registerScribe "stdout" handleScribe defaultScribeSettings =<< initLogEnv "hass-controller" "production"
-- closeScribes will stop accepting new logs, flush existing ones and clean up resources -- closeScribes will stop accepting new logs, flush existing ones and clean up resources
bracket makeLogEnv closeScribes $ \le -> do bracket makeLogEnv closeScribes $ \le -> do
bus <- Bus bus <- Bus
@@ -63,6 +65,18 @@ withBus severity metrics flags callback = do
<*> pure metrics <*> pure metrics
<*> pure flags <*> pure flags
callback bus callback bus
where
registerJournalScribe :: LogEnv -> IO LogEnv
registerJournalScribe le = do
journalScribe <- mkJournalScribe (permitItem severity) V2
registerScribe "journalctl" journalScribe defaultScribeSettings le
registerHandleScribe le = do
systemdUnit <- isJust <$> lookupEnv "INVOCATION_ID"
if systemdUnit
then pure le -- ignore stdout when running in systemd
else do
handleScribe <- mkHandleScribe ColorIfTerminal stdout (permitItem severity) V2
registerScribe "stdout" handleScribe defaultScribeSettings le
recordInbound :: Bus -> IO () recordInbound :: Bus -> IO ()
recordInbound bus = inc (amTriggersIn (busMetrics bus)) recordInbound bus = inc (amTriggersIn (busMetrics bus))
+70
View File
@@ -0,0 +1,70 @@
{-# LANGUAGE OverloadedStrings #-}
module Katip.Scribes.Journal where
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Builder qualified as Builder
import Katip (Item (..), LogStr (..), PermitFunc, Scribe (..), Verbosity (..), permitItem, Severity (..), registerScribe, defaultScribeSettings, initLogEnv, closeScribes, runKatipContextT, logLocM, LogItem, Namespace (..), getThreadIdText, payloadObject, katipAddContext, sl, getEnvironment)
import Systemd.Journal qualified as J
import Control.Exception (bracket)
import GHC.Stack (HasCallStack)
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text.Encoding as TE
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Aeson.Key as Key
import qualified Data.ByteString as B
mkJournalScribe :: PermitFunc -> Verbosity -> IO Scribe
mkJournalScribe permitFunc verbosity =
pure $
Scribe
{ liPush = \i ->
J.sendMessageWith (TL.toStrict $ Builder.toLazyText $ unLogStr $ _itemMessage i) (itemToJournalFields verbosity i),
scribeFinalizer = pure (), -- no cleanup?
scribePermitItem = permitFunc
}
itemToJournalFields :: forall a. LogItem a => Verbosity -> Item a -> J.JournalFields
itemToJournalFields verbosity item = mconcat
[ J.syslogIdentifier (unNS item)
, J.priority (mapPriority item)
, thread item
, payload item
, environment item
]
where
mapPriority i =
case _itemSeverity i of
DebugS -> J.Debug
InfoS -> J.Info
NoticeS -> J.Notice
WarningS -> J.Warning
ErrorS -> J.Error
CriticalS -> J.Critical
AlertS -> J.Alert
EmergencyS -> J.Emergency
unNS = T.intercalate "." . unNamespace . _itemNamespace
thread i = HashMap.singleton (katipField "thread") (TE.encodeUtf8 $ getThreadIdText $ _itemThread i)
environment i = HashMap.singleton (katipField "environment") (TE.encodeUtf8 $ getEnvironment $ _itemEnv i)
payload :: Item a -> J.JournalFields
payload i = -- HashMap.singleton (J.mkJournalField "payload") (BL.toStrict $ A.encode $ payloadObject verbosity $ _itemPayload i)
HashMap.fromList $ map (\(k,v) -> (katipField (Key.toText k), encodeValue v)) $ KeyMap.toList $ payloadObject verbosity $ _itemPayload i
-- The upstream doesn't handle '-'
katipField = J.mkJournalField . T.replace "-" "_" . ("KATIP_" <>)
encodeValue :: A.Value -> B.ByteString
encodeValue = \case
-- Handle simple scalar values as is
A.String t -> TE.encodeUtf8 t
x -> BL.toStrict $ A.encode x
test :: HasCallStack => IO ()
test = do
scribe <- mkJournalScribe (permitItem InfoS) V2
let makeLogEnv = registerScribe "journald" scribe defaultScribeSettings =<< initLogEnv "hass-controller" "dev"
bracket makeLogEnv closeScribes $ \le -> do
runKatipContextT le () "findme-context" $
katipAddContext (sl "trace-id" ("abcdefg" :: String)) $
logLocM InfoS "Hello world"
pure ()