diff --git a/cardano-submit-api/CHANGELOG.md b/cardano-submit-api/CHANGELOG.md index b80c679ea8d..2060f060135 100644 --- a/cardano-submit-api/CHANGELOG.md +++ b/cardano-submit-api/CHANGELOG.md @@ -2,6 +2,12 @@ ## vNext +## 10.2 -- Oct 2025 + +* Replace the older tracing & metric system — `iohk-monitoring` with `trace-dispatcher` + * Change prometheus metric type from `gauge` to `counter` + * Use slightly different prometheus suffix for counters: `counter` instead of `count` + ## 10.0 -- Oct 2024 * Bump for Node 10.0 diff --git a/cardano-submit-api/cardano-submit-api.cabal b/cardano-submit-api/cardano-submit-api.cabal index fc79003264b..8deb4d051ad 100644 --- a/cardano-submit-api/cardano-submit-api.cabal +++ b/cardano-submit-api/cardano-submit-api.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: cardano-submit-api -version: 10.1.1 +version: 10.2.0 synopsis: A web server that allows transactions to be POSTed to the cardano chain description: A web server that allows transactions to be POSTed to the cardano chain. homepage: https://github.com/intersectmbo/cardano-node @@ -43,22 +43,24 @@ library , cardano-binary , cardano-cli ^>= 10.13 , cardano-crypto-class ^>= 2.2 + , containers + , ekg-core , http-media - , iohk-monitoring , mtl , network , optparse-applicative-fork , ouroboros-consensus-cardano , ouroboros-network-protocols , prometheus >= 2.2.4 + , ekg-prometheus-adapter , safe-exceptions , servant , servant-server , streaming-commons , text , transformers-except + , trace-dispatcher , warp - , yaml hs-source-dirs: src @@ -66,12 +68,12 @@ library other-modules: Cardano.TxSubmit.CLI.Parsers , Cardano.TxSubmit.CLI.Types - , Cardano.TxSubmit.Config , Cardano.TxSubmit.Metrics , Cardano.TxSubmit.Orphans , Cardano.TxSubmit.Rest.Parsers , Cardano.TxSubmit.Rest.Types , Cardano.TxSubmit.Rest.Web + , Cardano.TxSubmit.Tracing.TraceSubmitApi , Cardano.TxSubmit.Types , Cardano.TxSubmit.Util , Cardano.TxSubmit.Web diff --git a/cardano-submit-api/src/Cardano/TxSubmit.hs b/cardano-submit-api/src/Cardano/TxSubmit.hs index 15eaf9d9ecd..2a12ada0372 100644 --- a/cardano-submit-api/src/Cardano/TxSubmit.hs +++ b/cardano-submit-api/src/Cardano/TxSubmit.hs @@ -7,32 +7,43 @@ module Cardano.TxSubmit , TxSubmitCommand(..) ) where -import qualified Cardano.BM.Setup as Logging -import Cardano.BM.Trace (Trace, logInfo) -import qualified Cardano.BM.Trace as Logging +import Cardano.Logging (BackendConfig (..), ConfigOption (ConfBackend, ConfSeverity), + FormatLogging (HumanFormatColoured), SeverityF (SeverityF), SeverityS (Info), + Trace, TraceConfig, configureTracers, ekgTracer, emptyConfigReflection, + emptyTraceConfig, mkCardanoTracer, readConfigurationWithDefault, standardTracer, + tcOptions, traceWith) import Cardano.TxSubmit.CLI.Parsers (opts) import Cardano.TxSubmit.CLI.Types (ConfigFile (unConfigFile), TxSubmitCommand (..), TxSubmitNodeParams (..)) -import Cardano.TxSubmit.Config (GenTxSubmitNodeConfig (..), ToggleLogging (..), - TxSubmitNodeConfig, readTxSubmitNodeConfig) import Cardano.TxSubmit.Metrics (registerMetricsServer) +import Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi (..)) import Cardano.TxSubmit.Web (runTxSubmitServer) import qualified Control.Concurrent.Async as Async -import Control.Monad.IO.Class (MonadIO (liftIO)) -import Data.Text (Text) +import Data.Map +import qualified System.Metrics as EKG +import System.Metrics.Prometheus.Registry (RegistrySample, sample) +import System.Remote.Monitoring.Prometheus (defaultOptions, toPrometheusRegistry) + +defaultTraceConfig :: TraceConfig +defaultTraceConfig = + emptyTraceConfig + { tcOptions = Data.Map.fromList + [([], [ ConfSeverity (SeverityF (Just Info)) + , ConfBackend [Stdout HumanFormatColoured, EKGBackend]]) + ] + } runTxSubmitWebapi :: TxSubmitNodeParams -> IO () runTxSubmitWebapi tsnp = do - tsnc <- readTxSubmitNodeConfig (unConfigFile tspConfigFile) - trce <- mkTracer tsnc - (metrics, runMetricsServer) <- registerMetricsServer trce tspMetricsPort + tracingConfig <- readConfigurationWithDefault (unConfigFile tspConfigFile) defaultTraceConfig + (trce, registrySample) <- mkTraceDispatcher tracingConfig Async.withAsync - (runTxSubmitServer trce metrics tspWebserverConfig tspProtocol tspNetworkId tspSocketPath) + (runTxSubmitServer trce tspWebserverConfig tspProtocol tspNetworkId tspSocketPath) $ \txSubmitServer -> - Async.withAsync runMetricsServer $ \_ -> + Async.withAsync (registerMetricsServer trce registrySample tspMetricsPort) $ \_ -> Async.wait txSubmitServer - logInfo trce "runTxSubmitWebapi: Stopping TxSubmit API" + traceWith trce ApplicationStopping where TxSubmitNodeParams { tspProtocol @@ -43,7 +54,14 @@ runTxSubmitWebapi tsnp = do , tspConfigFile } = tsnp -mkTracer :: TxSubmitNodeConfig -> IO (Trace IO Text) -mkTracer enc = case tscToggleLogging enc of - LoggingOn -> liftIO $ Logging.setupTrace (Right $ tscLoggingConfig enc) "cardano-tx-submit" - LoggingOff -> pure Logging.nullTracer +mkTraceDispatcher :: TraceConfig -> IO (Trace IO TraceSubmitApi, IO RegistrySample) +mkTraceDispatcher config = do + trBase <- standardTracer + ekgStore <- EKG.newStore + let registry = toPrometheusRegistry ekgStore (defaultOptions mempty) -- Convert EKG metrics store to prometheus metrics registry on-demand + trEkg <- ekgTracer config ekgStore + configReflection <- emptyConfigReflection + tr <- mkCardanoTracer trBase mempty (Just trEkg) ["TxSubmitApi"] + configureTracers configReflection config [tr] + traceWith tr ApplicationInitializeMetrics + pure (tr, registry >>= sample) diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Config.hs b/cardano-submit-api/src/Cardano/TxSubmit/Config.hs deleted file mode 100644 index 366c8035493..00000000000 --- a/cardano-submit-api/src/Cardano/TxSubmit/Config.hs +++ /dev/null @@ -1,63 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} - -module Cardano.TxSubmit.Config - ( TxSubmitNodeConfig - , GenTxSubmitNodeConfig (..) - , readTxSubmitNodeConfig - , ToggleLogging(..) - , ToggleMetrics(..) - ) where - -import qualified Cardano.BM.Configuration as Logging -import qualified Cardano.BM.Configuration.Model as Logging -import qualified Cardano.BM.Data.Configuration as Logging - -import Control.Exception (IOException, catch) -import Data.Aeson (FromJSON (..), Object, Value (..), (.:)) -import qualified Data.Aeson as Aeson -import Data.Aeson.Types (Parser) -import Data.Bool (bool) -import Data.ByteString (ByteString) -import qualified Data.ByteString.Char8 as B8 -import qualified Data.Yaml as Yaml - -type TxSubmitNodeConfig = GenTxSubmitNodeConfig Logging.Configuration - -data ToggleLogging = LoggingOn | LoggingOff deriving (Eq, Show) -data ToggleMetrics = MetricsOn | MetricsOff deriving (Eq, Show) - -data GenTxSubmitNodeConfig a = GenTxSubmitNodeConfig - { tscLoggingConfig :: !a - , tscToggleLogging :: !ToggleLogging - , tscToggleMetrics :: !ToggleMetrics - } - -readTxSubmitNodeConfig :: FilePath -> IO TxSubmitNodeConfig -readTxSubmitNodeConfig fp = do - res <- Yaml.decodeEither' <$> readLoggingConfig - case res of - Left err -> error $ "readTxSubmitNodeConfig: Error parsing config: " <> Yaml.prettyPrintParseException err - Right icr -> convertLogging icr - where - readLoggingConfig :: IO ByteString - readLoggingConfig = - catch (B8.readFile fp) $ \(_ :: IOException) -> - error $ "Cannot find the logging configuration file at : " <> fp - -convertLogging :: GenTxSubmitNodeConfig Logging.Representation -> IO TxSubmitNodeConfig -convertLogging tsc = do - lc <- Logging.setupFromRepresentation $ tscLoggingConfig tsc - pure $ tsc { tscLoggingConfig = lc } - ---------------------------------------------------------------------------------------------------- - -instance FromJSON (GenTxSubmitNodeConfig Logging.Representation) where - parseJSON = Aeson.withObject "top-level" parseGenTxSubmitNodeConfig - -parseGenTxSubmitNodeConfig :: Object -> Parser (GenTxSubmitNodeConfig Logging.Representation) -parseGenTxSubmitNodeConfig o = GenTxSubmitNodeConfig - <$> parseJSON (Object o) - <*> fmap (bool LoggingOff LoggingOn) (o .: "EnableLogging") - <*> fmap (bool MetricsOff MetricsOn) (o .: "EnableLogMetrics") diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Metrics.hs b/cardano-submit-api/src/Cardano/TxSubmit/Metrics.hs index f799b5e13a3..c2ab649aa01 100644 --- a/cardano-submit-api/src/Cardano/TxSubmit/Metrics.hs +++ b/cardano-submit-api/src/Cardano/TxSubmit/Metrics.hs @@ -1,66 +1,39 @@ -{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} module Cardano.TxSubmit.Metrics - ( TxSubmitMetrics (..) - , makeMetrics - , registerMetricsServer - ) + (registerMetricsServer) where -import Cardano.Api.Pretty (textShow) - -import Cardano.BM.Data.Trace (Trace) -import Cardano.BM.Trace (logError, logInfo, logWarning) +import Cardano.Logging (Trace, traceWith) +import Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi (..)) import Control.Exception.Safe -import Control.Monad.Reader (MonadReader (ask), ReaderT (runReaderT)) -import Data.Text (Text) -import qualified Data.Text as T -import System.Metrics.Prometheus.Concurrent.RegistryT (RegistryT (..), registerGauge, - runRegistryT, unRegistryT) -import System.Metrics.Prometheus.Http.Scrape (serveMetricsT) -import System.Metrics.Prometheus.Metric.Gauge (Gauge) - -data TxSubmitMetrics = TxSubmitMetrics - { tsmCount :: Gauge - , tsmFailCount :: Gauge - } +import System.Metrics.Prometheus.Http.Scrape (serveMetrics) +import System.Metrics.Prometheus.Registry (RegistrySample) -- | Register metrics server. Returns metrics and an IO action which starts metrics server and should -- be passed to 'withAsync'. registerMetricsServer - :: Trace IO Text + :: Trace IO TraceSubmitApi + -> IO RegistrySample -> Int - -> IO (TxSubmitMetrics, IO ()) -registerMetricsServer tracer metricsPort = - runRegistryT $ do - metrics <- makeMetrics - registry <- RegistryT ask - let runServer = - tryWithPort metricsPort $ \port -> do - logInfo tracer $ "Starting metrics server on port " <> textShow port - flip runReaderT registry . unRegistryT $ serveMetricsT port [] - pure (metrics, runServer) + -> IO () +registerMetricsServer tracer registrySample metricsPort = do + tryWithPort metricsPort $ \port -> do + traceWith tracer $ MetricsServerStarted port + serveMetrics port [] registrySample where + -- try opening the metrics server on the specified port, if it fails, try using next. Gives up after 1000 attempts and disables metrics server. tryWithPort :: Int -> (Int -> IO ()) -> IO () tryWithPort startingPort f = go startingPort where go port = do catch @_ @IOException (f port) $ \e -> do - logWarning tracer $ T.pack $ "Metrics server error: " <> displayException e + traceWith tracer $ MetricsServerError e if port <= (startingPort + 1000) then do - logWarning tracer $ "Could not allocate metrics server port " <> textShow port <> " - trying next available..." + traceWith tracer $ MetricsServerPortOccupied port go $ port + 1 - else do - logError tracer $ - "Could not allocate any metrics port until " <> textShow port <> " - metrics endpoint disabled" - pure () - -makeMetrics :: RegistryT IO TxSubmitMetrics -makeMetrics = - TxSubmitMetrics - <$> registerGauge "tx_submit_count" mempty - <*> registerGauge "tx_submit_fail_count" mempty + else + traceWith tracer $ MetricsServerPortNotBound port diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Rest/Web.hs b/cardano-submit-api/src/Cardano/TxSubmit/Rest/Web.hs index 14a7dc85095..e7ff08c3de9 100644 --- a/cardano-submit-api/src/Cardano/TxSubmit/Rest/Web.hs +++ b/cardano-submit-api/src/Cardano/TxSubmit/Rest/Web.hs @@ -1,24 +1,22 @@ -{-# LANGUAGE OverloadedStrings #-} - module Cardano.TxSubmit.Rest.Web ( runSettings ) where -import Cardano.Api.Pretty (textShow) -import Cardano.BM.Trace (Trace, logInfo) +import Cardano.Logging.Trace (traceWith) +import qualified Cardano.Logging.Types as TraceD +import Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi (..)) import Control.Exception (bracket) import Data.Streaming.Network (bindPortTCP) -import Data.Text (Text) import Network.Socket (close, getSocketName, withSocketsDo) import Network.Wai.Handler.Warp (Settings, getHost, getPort, runSettingsSocket) import Servant (Application) -- | Like 'Network.Wai.Handler.Warp.runSettings', except with better logging. -runSettings :: Trace IO Text -> Settings -> Application -> IO () +runSettings :: TraceD.Trace IO TraceSubmitApi -> Settings -> Application -> IO () runSettings trace settings app = withSocketsDo $ bracket @@ -26,6 +24,6 @@ runSettings trace settings app = close ( \socket -> do addr <- getSocketName socket - logInfo trace $ "Web API listening on port " <> textShow addr + traceWith trace $ EndpointListeningOnPort addr runSettingsSocket settings socket app ) diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Tracing/TraceSubmitApi.hs b/cardano-submit-api/src/Cardano/TxSubmit/Tracing/TraceSubmitApi.hs new file mode 100644 index 00000000000..5a5e2ea2b49 --- /dev/null +++ b/cardano-submit-api/src/Cardano/TxSubmit/Tracing/TraceSubmitApi.hs @@ -0,0 +1,161 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi(..)) where + +import Cardano.Api (TxId (TxId), TxValidationErrorInCardanoMode (..)) +import Cardano.Api.Pretty (textShow) + +import qualified Cardano.Crypto.Hash.Class as Crypto +import Cardano.Logging +import Cardano.TxSubmit.Types (TxCmdError (..)) + +import Prelude hiding (take) + +import Data.Aeson (Value (String), toJSON) +import Data.Aeson.KeyMap (singleton) +import Data.Aeson.Types ((.=)) +import Data.Text (Text, pack, take) +import Data.Text.Encoding (decodeLatin1) +import GHC.Exception.Type (SomeException, displayException) +import GHC.IO.Exception (IOException) +import Network.Socket (SockAddr) + +data TraceSubmitApi = ApplicationStopping + | ApplicationInitializeMetrics + | EndpointListeningOnPort SockAddr + | EndpointException Text SomeException + | EndpointFailedToSubmitTransaction TxCmdError + | EndpointSubmittedTransaction TxId + | EndpointExiting + | MetricsServerStarted Int + | MetricsServerError IOException + | MetricsServerPortOccupied Int + | MetricsServerPortNotBound Int {- tried ports until that one -} + + +-- | Render the first 16 characters of a transaction ID. +renderMediumTxId :: TxId -> Text +renderMediumTxId (TxId hash) = renderMediumHash hash + +-- | Render the first 16 characters of a hex-encoded hash. +renderMediumHash :: Crypto.Hash crypto a -> Text +renderMediumHash = take 16 . decodeLatin1 . Crypto.hashToBytesAsHex + +renderTxCmdError :: TxCmdError -> Text +renderTxCmdError (TxCmdSocketEnvError socketError) = + "socket env error " <> textShow socketError +renderTxCmdError (TxCmdTxReadError envelopeError) = + "transaction read error " <> textShow envelopeError +renderTxCmdError (TxCmdTxSubmitValidationError e) = + case e of + TxValidationErrorInCardanoMode validationErr -> + "transaction submit error " <> textShow validationErr + TxValidationEraMismatch eraMismatch -> + "transaction submit era mismatch" <> textShow eraMismatch + +instance LogFormatting TraceSubmitApi where + -- TODO (from: @russoul, to: @jutaro) why json object is required instead of, more flexible, arbitrary json value? + forMachine _ ApplicationStopping = mempty + forMachine _ (EndpointListeningOnPort addr) = + singleton "addr" (toJSON (textShow addr)) + forMachine _ (EndpointException txt except) = mconcat + [ + "txt" .= String txt, + "exception" .= String (textShow except) + ] + forMachine _ (EndpointFailedToSubmitTransaction txCmdError) = + singleton "error" (toJSON (renderTxCmdError txCmdError)) + forMachine _ (EndpointSubmittedTransaction txId) = + singleton "txId" (String $ renderMediumTxId txId) + forMachine _ EndpointExiting = mempty + forMachine _ (MetricsServerStarted port) = + singleton "port" (toJSON port) + forMachine _ (MetricsServerError except) = + singleton "exception" (toJSON $ displayException except) + forMachine _ (MetricsServerPortOccupied port) = + singleton "port" (toJSON port) + forMachine _ (MetricsServerPortNotBound port) = + singleton "port" (toJSON port) + forMachine _ ApplicationInitializeMetrics = mempty + + forHuman (MetricsServerStarted port) = + "Starting metrics server on port " <> textShow port + forHuman (EndpointException title except) = + title <> textShow except + forHuman (MetricsServerError txt) = + pack $ "Metrics server error: " <> displayException txt + forHuman (MetricsServerPortOccupied port) = + "Could not allocate metrics server port " <> textShow port <> " - trying next available..." + forHuman (MetricsServerPortNotBound untilPort) = + "Could not allocate any metrics port until " <> textShow untilPort <> " - metrics endpoint disabled" + forHuman ApplicationStopping = + "runTxSubmitWebapi: Stopping TxSubmit API" + forHuman ApplicationInitializeMetrics = "Metrics initialized" + forHuman (EndpointListeningOnPort port) = + "Web API listening on port " <> textShow port + forHuman EndpointExiting = + "txSubmitApp: exiting" + forHuman (EndpointFailedToSubmitTransaction err) = + "txSubmitPost: failed to submit transaction: " <> renderTxCmdError err + forHuman (EndpointSubmittedTransaction txId) = + "txSubmitPost: successfully submitted transaction " <> renderMediumTxId txId + + asMetrics (EndpointFailedToSubmitTransaction _) = [CounterM "tx_submit_fail" Nothing] + asMetrics (EndpointSubmittedTransaction _) = [CounterM "tx_submit" Nothing] + asMetrics ApplicationInitializeMetrics = [CounterM "tx_submit_fail" (Just 0), CounterM "tx_submit" (Just 0)] + asMetrics _ = [] + +instance MetaTrace TraceSubmitApi where + allNamespaces = [ + Namespace [] ["Application", "Stopping"], + Namespace [] ["Application", "InitializeMetrics"], + + Namespace [] ["Endpoint", "ListeningOnPort"], + Namespace [] ["Endpoint", "Exception"], + Namespace [] ["Endpoint", "FailedToSubmitTransaction"], + Namespace [] ["Endpoint", "SubmittedTransaction"], + Namespace [] ["Endpoint", "Exiting"], + + Namespace [] ["Metrics", "Started"], + Namespace [] ["Metrics", "Error"], + Namespace [] ["Metrics", "PortOccupied"], + Namespace [] ["Metrics", "PortNotBound"] + + ] + + namespaceFor ApplicationStopping = Namespace [] ["Application", "Stopping"] + namespaceFor ApplicationInitializeMetrics = Namespace [] ["Application", "InitializeMetrics"] + namespaceFor (EndpointListeningOnPort _) = Namespace [] ["Endpoint", "ListeningOnPort"] + namespaceFor (EndpointException _ _) = Namespace [] ["Endpoint", "Exception"] + namespaceFor (EndpointFailedToSubmitTransaction _) = Namespace [] ["Endpoint", "FailedToSubmitTransaction"] + namespaceFor (EndpointSubmittedTransaction _) = Namespace [] ["Endpoint", "SubmittedTransaction"] + namespaceFor EndpointExiting = Namespace [] ["Endpoint", "Exiting"] + namespaceFor (MetricsServerStarted _) = Namespace [] ["Metrics", "Started"] + namespaceFor (MetricsServerError _) = Namespace [] ["Metrics", "Error"] + namespaceFor (MetricsServerPortOccupied _) = Namespace [] ["Metrics", "PortOccupied"] + namespaceFor (MetricsServerPortNotBound _) = Namespace [] ["Metrics", "PortNotBound"] + + severityFor (Namespace _ ["Application", "Stopping"]) _ = Just Info + severityFor (Namespace _ ["Application", "InitializeMetrics"]) _ = Just Debug + severityFor (Namespace _ ["Endpoint", "ListeningOnPort"]) _ = Just Info + severityFor (Namespace _ ["Endpoint", "Exception"]) _ = Just Error + severityFor (Namespace _ ["Endpoint", "Exiting"]) _ = Just Info + severityFor (Namespace _ ["Endpoint", "FailedToSubmitTransaction"]) _ = Just Info + severityFor (Namespace _ ["Endpoint", "SubmittedTransaction"]) _ = Just Info + severityFor (Namespace _ ["Metrics", "Started"]) _ = Just Info + severityFor (Namespace _ ["Metrics", "Error"]) _ = Just Warning + severityFor (Namespace _ ["Metrics", "PortOccupied"]) _ = Just Warning + severityFor (Namespace _ ["Metrics", "PortNotBound"]) _ = Just Error + severityFor _ _ = Nothing + + -- TODO (@russoul) This seems to be necessary for metrics to work at all, why? + metricsDocFor (Namespace _ ["Endpoint", "FailedToSubmitTransaction"]) = [ ("tx_submit_fail", "Number of failed tx submissions") ] + metricsDocFor (Namespace _ ["Endpoint", "SubmittedTransaction"]) = [ ("tx_submit", "Number of successful tx submissions") ] + metricsDocFor (Namespace _ ["Application", "InitializeMetrics"]) = + [ + ("tx_submit_fail", "Initialize and set the number of successful tx submissions to 0"), + ("tx_submit", "Initialize and set the number of successful tx submissions to 0") + ] + metricsDocFor _ = [] + + documentFor _ = Nothing diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Util.hs b/cardano-submit-api/src/Cardano/TxSubmit/Util.hs index 25f1dab7e3e..04372437809 100644 --- a/cardano-submit-api/src/Cardano/TxSubmit/Util.hs +++ b/cardano-submit-api/src/Cardano/TxSubmit/Util.hs @@ -2,9 +2,8 @@ module Cardano.TxSubmit.Util ( logException ) where -import Cardano.Api (textShow) - -import Cardano.BM.Trace (Trace, logError) +import Cardano.Logging (Trace, traceWith) +import Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi (..)) import Prelude @@ -15,12 +14,12 @@ import Data.Text (Text) -- code, the caught exception will not be logged. Therefore wrap all tx submission code that -- is called from network with an exception logger so at least the exception will be -- logged (instead of silently swallowed) and then rethrown. -logException :: Trace IO Text -> Text -> IO a -> IO a +logException :: Trace IO TraceSubmitApi -> Text -> IO a -> IO a logException tracer txt action = action `catch` logger where logger :: SomeException -> IO a logger e = do - logError tracer $ txt <> textShow e + traceWith tracer (EndpointException txt e) throwIO e diff --git a/cardano-submit-api/src/Cardano/TxSubmit/Web.hs b/cardano-submit-api/src/Cardano/TxSubmit/Web.hs index f7ba8f93cf1..f646bb0422a 100644 --- a/cardano-submit-api/src/Cardano/TxSubmit/Web.hs +++ b/cardano-submit-api/src/Cardano/TxSubmit/Web.hs @@ -21,11 +21,11 @@ import Cardano.Api (AllegraEra, AnyCardanoEra (AnyCardanoEra), AsType getTxBody, getTxId, submitTxToNodeLocal) import Cardano.Binary (DecoderError (..)) -import Cardano.BM.Trace (Trace, logInfo) import qualified Cardano.Crypto.Hash.Class as Crypto -import Cardano.TxSubmit.Metrics (TxSubmitMetrics (..)) +import Cardano.Logging (Trace, traceWith) import Cardano.TxSubmit.Rest.Types (WebserverConfig (..), toWarpSettings) import qualified Cardano.TxSubmit.Rest.Web as Web +import Cardano.TxSubmit.Tracing.TraceSubmitApi (TraceSubmitApi (..)) import Cardano.TxSubmit.Types (EnvSocketError (..), RawCborDecodeError (..), TxCmdError (..), TxSubmitApi, TxSubmitApiRecord (..), TxSubmitWebApiError (TxSubmitFail), renderTxCmdError) @@ -61,7 +61,7 @@ import qualified Data.Text.IO as T import System.Environment (lookupEnv) import qualified System.IO as IO import System.IO (IO) -import qualified System.Metrics.Prometheus.Metric.Gauge as Gauge +import qualified System.Metrics.Prometheus.Metric.Counter as Counter import Text.Show (Show (show)) import qualified Servant @@ -70,31 +70,29 @@ import Servant.API.Generic (toServant) import Servant.Server.Generic (AsServerT) runTxSubmitServer - :: Trace IO Text - -> TxSubmitMetrics + :: Trace IO TraceSubmitApi -> WebserverConfig -> ConsensusModeParams -> NetworkId -> SocketPath -> IO () -runTxSubmitServer trace metrics webserverConfig protocol networkId socketPath = do +runTxSubmitServer trace webserverConfig protocol networkId socketPath = do logException trace "TxSubmit WebAPI: " $ - Web.runSettings trace (toWarpSettings webserverConfig) $ txSubmitApp trace metrics protocol networkId socketPath - logInfo trace "txSubmitApp: exiting" + Web.runSettings trace (toWarpSettings webserverConfig) $ txSubmitApp trace protocol networkId socketPath + traceWith trace EndpointExiting txSubmitApp - :: Trace IO Text - -> TxSubmitMetrics + :: Trace IO TraceSubmitApi -> ConsensusModeParams -> NetworkId -> SocketPath -> Application -txSubmitApp trace metrics connectInfo networkId socketPath = +txSubmitApp trace connectInfo networkId socketPath = Servant.serve (Proxy :: Proxy TxSubmitApi) (toServant handlers) where handlers :: TxSubmitApiRecord (AsServerT Handler) handlers = TxSubmitApiRecord - { _txSubmitPost = txSubmitPost trace metrics connectInfo networkId socketPath + { _txSubmitPost = txSubmitPost trace connectInfo networkId socketPath } deserialiseOne :: forall b. () @@ -124,14 +122,13 @@ readByteStringTx = firstExceptT TxCmdTxReadError . hoistEither . deserialiseAnyO ] txSubmitPost - :: Trace IO Text - -> TxSubmitMetrics + :: Trace IO TraceSubmitApi -> ConsensusModeParams -> NetworkId -> SocketPath -> ByteString -> Handler TxId -txSubmitPost trace metrics p@(CardanoModeParams cModeParams) networkId socketPath txBytes = +txSubmitPost trace p@(CardanoModeParams cModeParams) networkId socketPath txBytes = handle $ do InAnyShelleyBasedEra sbe tx <- readByteStringTx txBytes let txInMode = TxInMode sbe tx @@ -163,22 +160,8 @@ txSubmitPost trace metrics p@(CardanoModeParams cModeParams) networkId socketPat handleSubmitResult res = case res of Left err -> do - liftIO $ logInfo trace $ - "txSubmitPost: failed to submit transaction: " - <> renderTxCmdError err - liftIO $ Gauge.inc (tsmFailCount metrics) + liftIO $ traceWith trace $ EndpointFailedToSubmitTransaction err errorResponse (TxSubmitFail err) Right txid -> do - liftIO $ logInfo trace $ - "txSubmitPost: successfully submitted transaction " - <> renderMediumTxId txid - liftIO $ Gauge.inc (tsmCount metrics) + liftIO $ traceWith trace $ EndpointSubmittedTransaction txid pure txid - --- | Render the first 16 characters of a transaction ID. -renderMediumTxId :: TxId -> Text -renderMediumTxId (TxId hash) = renderMediumHash hash - --- | Render the first 16 characters of a hex-encoded hash. -renderMediumHash :: Crypto.Hash crypto a -> Text -renderMediumHash = T.take 16 . T.decodeLatin1 . Crypto.hashToBytesAsHex diff --git a/nix/nixos/tests/cardano-node-edge.nix b/nix/nixos/tests/cardano-node-edge.nix index a49cabf8e5e..f05df662847 100644 --- a/nix/nixos/tests/cardano-node-edge.nix +++ b/nix/nixos/tests/cardano-node-edge.nix @@ -40,6 +40,7 @@ in { port = 8101; network = environment; socketPath = config.services.cardano-node.socketPath 0; + config = pkgs.cardanoLib.environments.${environment}.submitApiConfig; }; cardano-tracer = {