From d5bc455ff0dd6415cbd3a61fdc4ce0e60f156a70 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Wed, 1 Jul 2026 16:41:37 +0200 Subject: [PATCH 1/8] Implement parsing of tracing configuration --- README.md | 23 +++-- cabal.project | 11 +++ cardano-config.cabal | 1 + schemas/config.legacy-one-file.schema.json | 18 +++- schemas/config.schema.json | 18 +++- src/Cardano/Configuration.hs | 2 + src/Cardano/Configuration/File.hs | 23 ++++- src/Cardano/Configuration/File/Tracing.hs | 105 +++++++++++++++++---- 8 files changed, 160 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 7e8b22b..4ffa98a 100644 --- a/README.md +++ b/README.md @@ -150,14 +150,21 @@ The flag names, metavars and help text match those historically accepted by `cardano-node`, so existing operator scripts keep working. You can inspect the parsed options with `cabal run cardano-config -- resolve --help`. -## Tracing options are **not** parsed by this library - -Tracing is **not** parsed: it is owned by the node's tracing system (hermod / -`trace-dispatcher`) and given under a single top-level `HermodTracing` key whose -value is a path to a separate file. The key is recognised and captured opaquely -(it appears in the schema), but its contents are not interpreted here. The -authoritative schema lives in -[`hermod-tracing`](https://github.com/IntersectMBO/hermod-tracing). +## Tracing options are owned by `trace-dispatcher` + +Tracing is owned by the node's tracing system (hermod / `trace-dispatcher`), +given under a single top-level `HermodTracing` key whose value is **either** a +path to a separate file holding the tracing configuration **or** that +configuration object inline. This library does not define or validate the shape +of that object — the authoritative schema lives in +[`hermod-tracing`](https://github.com/IntersectMBO/hermod-tracing), so the +configuration schema describes `HermodTracing` only as "a path or a JSON +object". + +Instead, the parser hands the `HermodTracing` value to `trace-dispatcher`'s own +parser (`readConfiguration`), which resolves it into a `TraceConfig`: a file +reference is read via `FromFile` (after resolving the path to its canonical +location), an inline object via `FromJSONObject`. ## Mandatory keys diff --git a/cabal.project b/cabal.project index 0c4805c..c9a574d 100644 --- a/cabal.project +++ b/cabal.project @@ -44,6 +44,16 @@ source-repository-package libs/non-integral libs/small-steps +-- trace-dispatcher (hermod tracing) — provides the tracing configuration parser +-- (ConfigSource/readConfiguration) used to resolve the HermodTracing key. +-- Pinned to the tip of hermod-tracing#18 (mkarg/config-source). +source-repository-package + type: git + location: https://github.com/IntersectMBO/hermod-tracing + tag: 3d7aeb85d9ef01df48a44c751e45609b4c6dea00 + subdir: + trace-dispatcher + -- Ensures colourized output from test runners test-show-details: direct tests: true @@ -73,6 +83,7 @@ if impl(ghc >=9.14) , canonical-json:containers , cborg:base , cborg:containers + , cborg-json:base , constrained-generators:QuickCheck , dependent-map:containers , dictionary-sharing:containers diff --git a/cardano-config.cabal b/cardano-config.cabal index 793e1a0..aee9c0b 100644 --- a/cardano-config.cabal +++ b/cardano-config.cabal @@ -97,6 +97,7 @@ library internal scientific, text, time, + trace-dispatcher, transformers, yaml, diff --git a/schemas/config.legacy-one-file.schema.json b/schemas/config.legacy-one-file.schema.json index 0daeaa2..81eba19 100644 --- a/schemas/config.legacy-one-file.schema.json +++ b/schemas/config.legacy-one-file.schema.json @@ -236,10 +236,20 @@ "type": "string" }, "HermodTracing": { - "description": "Tracing configuration as a path to a separate file holding it. Consumed by the node tracing system (trace-dispatcher), not parsed or validated by cardano-config.", - "format": "path", - "title": "HermodTracing", - "type": "string" + "anyOf": [ + { + "format": "path", + "title": "String", + "type": "string" + }, + { + "additionalProperties": {}, + "title": "Object", + "type": "object" + } + ], + "description": "Tracing configuration, given as a path to a separate file holding it or as that configuration object inline. Consumed by the node tracing system (trace-dispatcher), which owns its schema; not described further by cardano-config.", + "title": "HermodTracing" }, "LedgerDB": { "description": "The LedgerDB configuration\nLedgerDB", diff --git a/schemas/config.schema.json b/schemas/config.schema.json index 48fc9cb..750de8b 100644 --- a/schemas/config.schema.json +++ b/schemas/config.schema.json @@ -97,10 +97,20 @@ "title": "ConsensusConfig" }, "HermodTracing": { - "description": "Tracing configuration as a path to a separate file holding it. Consumed by the node tracing system (trace-dispatcher), not parsed or validated by cardano-config.", - "format": "path", - "title": "HermodTracing", - "type": "string" + "anyOf": [ + { + "format": "path", + "title": "String", + "type": "string" + }, + { + "additionalProperties": {}, + "title": "Object", + "type": "object" + } + ], + "description": "Tracing configuration, given as a path to a separate file holding it or as that configuration object inline. Consumed by the node tracing system (trace-dispatcher), which owns its schema; not described further by cardano-config.", + "title": "HermodTracing" }, "LocalConnectionsConfig": { "anyOf": [ diff --git a/src/Cardano/Configuration.hs b/src/Cardano/Configuration.hs index 623a138..03d1d78 100644 --- a/src/Cardano/Configuration.hs +++ b/src/Cardano/Configuration.hs @@ -86,6 +86,8 @@ module Cardano.Configuration -- * Configuration file , File.NodeConfigurationFromFile , File.TracingConfiguration (..) + , File.TracingConfigSource (..) + , File.TraceConfig , File.parseConfigurationFiles , File.ConfigWarning (..) , File.renderConfigWarning diff --git a/src/Cardano/Configuration/File.hs b/src/Cardano/Configuration/File.hs index 1774a5d..200126a 100644 --- a/src/Cardano/Configuration/File.hs +++ b/src/Cardano/Configuration/File.hs @@ -31,6 +31,8 @@ module Cardano.Configuration.File , TestingConfiguration (..) , MempoolConfiguration (..) , TracingConfiguration (..) + , TracingConfigSource (..) + , TraceConfig -- * Resolving components , finalizeNetwork @@ -69,6 +71,10 @@ import Cardano.Configuration.File.Protocol import Cardano.Configuration.File.Storage import Cardano.Configuration.File.Testing import Cardano.Configuration.File.Tracing + ( TracingConfigSource (..) + , TracingConfiguration (..) + , resolveTracingConfiguration + ) import Cardano.Configuration.Genesis ( GenesisReadError , genesisErrorFile @@ -83,6 +89,7 @@ import Cardano.Ledger.BaseTypes (StrictMaybe (..), maybeToStrictMaybe, strictMay import Cardano.Ledger.Conway.Genesis (ConwayGenesis) import Cardano.Ledger.Dijkstra.Genesis (DijkstraGenesis) import Cardano.Ledger.Shelley.Genesis (ShelleyGenesis) +import Cardano.Logging.Types (TraceConfig) import Control.Exception (throwIO) import Data.Aeson (FromJSON, Value) import qualified Data.Aeson.Key as K @@ -126,10 +133,12 @@ data NodeConfigurationFromFileF f , localConnectionsConfig :: f (LocalConnectionsConfig StrictMaybe) , testingConfiguration :: f (TestingConfiguration StrictMaybe) , mempoolConfiguration :: f (MempoolConfiguration StrictMaybe) - , tracingConfiguration :: TracingConfiguration - -- ^ Tracing keys, captured opaquely; see 'TracingConfiguration'. Unlike the - -- other components this is never read from a sub-file: the node's tracing - -- system resolves its own @HermodTracing@ file indirection. + , tracingConfiguration :: StrictMaybe TraceConfig + -- ^ The tracing configuration referenced by the top-level @HermodTracing@ key, + -- resolved by @trace-dispatcher@'s own parser ('resolveTracingConfiguration'): + -- a @HermodTracing@ file path is read from that file, an inline object is read + -- directly. 'SNothing' when no @HermodTracing@ key is present. Its schema is + -- owned by @trace-dispatcher@, not described here (see 'TracingConfiguration'). , byronGenesisConfig :: ByronGenesisConfig -- ^ The parsed Byron genesis (read from the @ByronGenesisFile@). , shelleyGenesisConfig :: ShelleyGenesis @@ -210,7 +219,11 @@ parseConfigurationVersion1 root minNodeVer configValue = do localConnections <- parseSection root configValue "LocalConnectionsConfig" testing <- parseSection root configValue "TestingConfig" mempool <- parseSection root configValue "MempoolConfig" + -- The @HermodTracing@ value is captured (as a file path or an inline object) + -- and then handed to trace-dispatcher's own parser, which resolves it to a + -- 'TraceConfig' — reading the referenced file, or the inline object directly. tracing <- runCodec Nothing "Tracing" configValue + traceConfig <- resolveTracingConfiguration root tracing -- The genesis files referenced by the configuration are read and decoded -- here, so that JSON resolution happens entirely within this library. let byronCfg = byronGenesis protocol @@ -238,7 +251,7 @@ parseConfigurationVersion1 root minNodeVer configValue = do , localConnectionsConfig = Identity localConnections , testingConfiguration = Identity testing , mempoolConfiguration = Identity mempool - , tracingConfiguration = tracing + , tracingConfiguration = traceConfig , byronGenesisConfig = byronGenesisData , shelleyGenesisConfig = shelleyGenesisData , alonzoGenesisConfig = alonzoGenesisData diff --git a/src/Cardano/Configuration/File/Tracing.hs b/src/Cardano/Configuration/File/Tracing.hs index 75a9bff..67820d5 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -1,31 +1,73 @@ -- | Tracing configuration. -- -- Tracing is owned by the node's tracing system (hermod / @trace-dispatcher@), --- not by @cardano-config@. The @HermodTracing@ key below is accepted but parsed --- /opaquely/: its contents are neither interpreted nor validated here. The --- authoritative schema for them lives in the @trace-dispatcher@ package. +-- not by @cardano-config@. The configuration is given under a single top-level +-- @HermodTracing@ key, whose value is /either/ a path (a string) to a separate +-- file holding the tracing configuration, /or/ that configuration object inline. -- --- This type exists as an /informational placeholder/, so that the tracing key is --- visible in the configuration schema (rather than silently ignored) and is --- preserved when round-tripping a configuration through the parser. +-- We do not interpret the tracing configuration ourselves: its authoritative +-- schema lives in @trace-dispatcher@. What we do is hand the @HermodTracing@ +-- value to @trace-dispatcher@'s own parser ('readConfiguration'), which turns it +-- into a 'TraceConfig' — a file reference via 'FromFile' (after resolving the +-- path to its canonical location), an inline object via 'FromJSONObject'. +-- +-- Correspondingly, the configuration schema describes @HermodTracing@ only as +-- \"a path or a JSON object\"; the shape of that object is @trace-dispatcher@'s +-- responsibility to describe, not this library's. module Cardano.Configuration.File.Tracing ( TracingConfiguration (..) + , TracingConfigSource (..) + , resolveTracingConfiguration ) where import Autodocodec -import Cardano.Configuration.Basic (optionalFieldWithStrict) -import Cardano.Configuration.Common (filePathFormatMarker) -import Cardano.Ledger.BaseTypes (StrictMaybe) -import Data.Aeson (FromJSON, ToJSON) -import Data.Text (Text) +import Cardano.Configuration.Basic (optionalFieldStrict) +import Cardano.Configuration.Common (filePathCodec) +import Cardano.Ledger.BaseTypes (StrictMaybe (..)) +import Cardano.Logging.ConfigurationParser (ConfigSource (..), readConfiguration) +import Cardano.Logging.Types (TraceConfig) +import Data.Aeson (FromJSON, Object, ToJSON) +import qualified Data.Aeson.KeyMap as KM import GHC.Generics (Generic) +import System.Directory (canonicalizePath) +import System.FilePath (()) + +-- | Where the @HermodTracing@ configuration comes from: either a path to a +-- separate file holding it, or the configuration object given inline. Which one +-- was written is decided purely by the JSON shape (a string vs. an object); the +-- contents are captured opaquely and only interpreted by @trace-dispatcher@. +data TracingConfigSource + = -- | @HermodTracing@ was a path (a string) to a separate file. + TracingConfigFile FilePath + | -- | @HermodTracing@ held the tracing configuration object inline. + TracingConfigInline Object + deriving (Generic, Show) + +-- | A single JSON string is read as a file path; a single JSON object is read as +-- an inline configuration. We dispatch on that shape (as 'NodeDatabasePaths' +-- does) so the two never compete in error messages. +instance HasCodec TracingConfigSource where + codec = + matchChoiceCodec + (dimapCodec TracingConfigFile id filePathCodec) + (dimapCodec TracingConfigInline id inlineObjectCodec) + selector + where + selector (TracingConfigFile fp) = Left fp + selector (TracingConfigInline o) = Right o --- | The tracing configuration is given under a single @HermodTracing@ key, --- whose value is a path (a string) to a separate file holding that object. It --- is captured opaquely; see the module documentation. +-- | A codec for an arbitrary JSON object. Its schema is just @type: object@ with +-- no described properties — this library deliberately says nothing about what +-- the tracing configuration object holds (that is @trace-dispatcher@'s domain). +inlineObjectCodec :: JSONCodec Object +inlineObjectCodec = dimapCodec KM.fromMapText KM.toMapText (mapCodec valueCodec) + +-- | The tracing configuration is given under a single @HermodTracing@ key. It is +-- captured (as a path or an inline object) but not interpreted here; see the +-- module documentation and 'resolveTracingConfiguration'. newtype TracingConfiguration = TracingConfiguration - { hermodTracing :: StrictMaybe Text - -- ^ A path to a file holding the tracing configuration. + { hermodTracing :: StrictMaybe TracingConfigSource + -- ^ The @HermodTracing@ value: a path to a file, or an inline object. } deriving (Generic, Show) deriving (FromJSON, ToJSON) via (Autodocodec TracingConfiguration) @@ -34,10 +76,33 @@ instance HasCodec TracingConfiguration where codec = object "TracingConfiguration" $ TracingConfiguration - <$> optionalFieldWithStrict + <$> optionalFieldStrict "HermodTracing" - (codec @Text filePathFormatMarker) - ( "Tracing configuration as a path to a separate file holding it. " - <> "Consumed by the node tracing system (trace-dispatcher), not parsed or validated by cardano-config." + ( "Tracing configuration, given as a path to a separate file holding it " + <> "or as that configuration object inline. Consumed by the node tracing " + <> "system (trace-dispatcher), which owns its schema; not described further " + <> "by cardano-config." ) .= hermodTracing + +-- | Resolve the captured @HermodTracing@ value into a 'TraceConfig' by handing it +-- to @trace-dispatcher@'s own parser ('readConfiguration'): +-- +-- * a file reference is resolved to its canonical location (relative paths are +-- taken against the configuration directory @root@) and read via 'FromFile'; +-- * an inline object is read via 'FromJSONObject'. +-- +-- 'SNothing' when no @HermodTracing@ key is present. +resolveTracingConfiguration :: + -- | The directory a relative @HermodTracing@ file path is resolved against. + FilePath -> + TracingConfiguration -> + IO (StrictMaybe TraceConfig) +resolveTracingConfiguration root (TracingConfiguration mSource) = + case mSource of + SNothing -> pure SNothing + SJust (TracingConfigFile path) -> do + canonPath <- canonicalizePath (root path) + SJust <$> readConfiguration (FromFile canonPath) + SJust (TracingConfigInline obj) -> + SJust <$> readConfiguration (FromJSONObject obj) From cc3be83bd48df9108ddb8ecf1bb166cf9e330658 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Wed, 1 Jul 2026 16:47:16 +0200 Subject: [PATCH 2/8] Expose the parsed tracing config downstream --- src/Cardano/Configuration.hs | 6 ++++++ src/Cardano/Configuration/Render.hs | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Cardano/Configuration.hs b/src/Cardano/Configuration.hs index 03d1d78..e7f9358 100644 --- a/src/Cardano/Configuration.hs +++ b/src/Cardano/Configuration.hs @@ -125,6 +125,11 @@ data NodeConfiguration = NodeConfiguration , localConnectionsConfig :: File.LocalConnectionsConfig Identity , testingConfiguration :: File.TestingConfiguration Identity , mempoolConfiguration :: File.MempoolConfiguration Identity + , tracingConfiguration :: StrictMaybe File.TraceConfig + -- ^ The tracing configuration resolved from the top-level @HermodTracing@ key + -- by @trace-dispatcher@'s parser (see 'File.resolveTracingConfiguration'), or + -- 'SNothing' when no @HermodTracing@ key is present. Carried through unchanged + -- from the file-parse result so consumers get the parsed 'File.TraceConfig'. , byronGenesisConfig :: ByronGenesisConfig -- ^ The parsed Byron genesis. , shelleyGenesisConfig :: ShelleyGenesis @@ -304,6 +309,7 @@ resolveConfigurationWith checks cli file = do , localConnectionsConfig = localConnections , testingConfiguration = testing , mempoolConfiguration = mempool + , tracingConfiguration = File.tracingConfiguration file , byronGenesisConfig = File.byronGenesisConfig file , shelleyGenesisConfig = File.shelleyGenesisConfig file , alonzoGenesisConfig = File.alonzoGenesisConfig file diff --git a/src/Cardano/Configuration/Render.hs b/src/Cardano/Configuration/Render.hs index 59466e8..7ecc2a6 100644 --- a/src/Cardano/Configuration/Render.hs +++ b/src/Cardano/Configuration/Render.hs @@ -24,6 +24,7 @@ import qualified Cardano.Configuration.CliArgs as CLI import qualified Cardano.Configuration.File as File import Cardano.Configuration.Genesis.Byron (byronGenesisToJSON) import Cardano.Ledger.BaseTypes (StrictMaybe (..), strictMaybeToMaybe) +import Cardano.Logging.ConfigurationParser () import Data.Aeson (Value, object, toJSON, (.=)) import Data.Functor.Identity (Identity, runIdentity) import Data.Maybe (mapMaybe) @@ -53,8 +54,15 @@ nodeConfigurationToJSON geneses nc = , "TestingConfig" .= toJSON (weakenTesting (testingConfiguration nc)) , "Runtime" .= runtimeValue nc ] + <> tracingFields <> genesisFields where + -- The tracing configuration resolved by trace-dispatcher, rendered under the + -- same @HermodTracing@ key it is read from (as an inline object, via + -- trace-dispatcher's own 'ToJSON'). Present only when the configuration set a + -- @HermodTracing@ key. + tracingFields = + mapMaybe strictMaybeToMaybe [("HermodTracing" .=) <$> tracingConfiguration nc] -- The resolved (parsed) era geneses, rendered through the ledger's @aeson@ -- 'toJSON' instances (and, for Byron, its canonical-JSON form), so the dump -- shows the decoded genesis content rather than just the file reference and From 27299528ac61771e42a3ae87e432af9bf025071e Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Wed, 1 Jul 2026 16:55:52 +0200 Subject: [PATCH 3/8] Add tracing tests --- README.md | 6 ++++ test/Main.hs | 49 ++++++++++++++++++++++++++++++- test/examples/tracing-file.json | 31 +++++++++++++++++++ test/examples/tracing-inline.json | 45 ++++++++++++++++++++++++++++ test/examples/tracing-sub.json | 12 ++++++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 test/examples/tracing-file.json create mode 100644 test/examples/tracing-inline.json create mode 100644 test/examples/tracing-sub.json diff --git a/README.md b/README.md index 4ffa98a..70dbaf2 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,12 @@ parser (`readConfiguration`), which resolves it into a `TraceConfig`: a file reference is read via `FromFile` (after resolving the path to its canonical location), an inline object via `FromJSONObject`. +The resolved `TraceConfig` is carried through to the final `NodeConfiguration` +(as `tracingConfiguration :: Maybe TraceConfig`), so a consumer of the library +gets the tracing configuration already parsed, and `cardano-config resolve` +emits it back under the `HermodTracing` key (as an inline object). It is +`Nothing`/absent when the configuration has no `HermodTracing` key. + ## Mandatory keys Only **eight** keys are mandatory (no default; parsing fails if absent): diff --git a/test/Main.hs b/test/Main.hs index b87a5d9..be0f019 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -46,7 +46,7 @@ import qualified Data.Aeson.Key as K import qualified Data.Aeson.KeyMap as KM import Data.Functor.Identity (runIdentity) import Data.List (isInfixOf) -import Data.Maybe (fromJust) +import Data.Maybe (fromJust, isJust, isNothing) import qualified Data.Text as T import Data.Word (Word64) import Options.Applicative (defaultPrefs, execParserPure, getParseResult, info) @@ -82,6 +82,7 @@ cases = , parseCase "test/examples/fullconfig.json" , parseCase "test/examples/split.json" , parseCase "test/examples/split-all.json" + , tracingCase , shadowWarnCase , envelopeWarningCase , splitSubfileSchemaCase @@ -295,6 +296,52 @@ resolveCase = Left err -> assertFailure (show err) Right (nc, _) -> () <$ evaluate (length (show nc)) +-- | The top-level @HermodTracing@ key is resolved by trace-dispatcher's parser +-- into a 'TraceConfig' — whether given inline (an object) or as a path to a +-- separate file — and surfaced both on the parse result and, when the resolved +-- configuration is dumped, back under a @HermodTracing@ key. A configuration +-- without the key resolves to no tracing config and renders no such key. +tracingCase :: TestTree +tracingCase = + testCase "HermodTracing resolves to a TraceConfig (inline and file) and is rendered" $ do + inline <- parsed "test/examples/tracing-inline.json" + fromFile <- parsed "test/examples/tracing-file.json" + absent <- parsed "test/examples/fullconfig.json" + renderedInline <- rendersTracing "test/examples/tracing-inline.json" + renderedAbsent <- rendersTracing "test/examples/fullconfig.json" + expectOk $ + if isJust (strictMaybeToMaybe (tracingConfiguration inline)) + && isJust (strictMaybeToMaybe (tracingConfiguration fromFile)) + && isNothing (strictMaybeToMaybe (tracingConfiguration absent)) + && renderedInline == Right True + && renderedAbsent == Right False + then Nothing + else + Just $ + "unexpected tracing resolution: inline=" + <> show (isJust (strictMaybeToMaybe (tracingConfiguration inline))) + <> " file=" + <> show (isJust (strictMaybeToMaybe (tracingConfiguration fromFile))) + <> " absent=" + <> show (isNothing (strictMaybeToMaybe (tracingConfiguration absent))) + <> " renderedInline=" + <> show renderedInline + <> " renderedAbsent=" + <> show renderedAbsent + where + parsed fp = getDataFileName fp >>= fmap fst . parseConfigurationFiles + -- Whether the resolved configuration renders a HermodTracing key. + rendersTracing fp = do + path <- getDataFileName fp + (cfg, _) <- parseConfigurationFiles path + pure $ case cliArgs [] of + Nothing -> Left "could not build CLI arguments" + Just cli -> case resolveConfiguration cli cfg of + Left e -> Left ("resolve failed: " <> show e) + Right (nc, _) -> case nodeConfigurationToJSON OmitGeneses nc of + Object o -> Right (KM.member (K.fromString "HermodTracing") o) + _ -> Left "rendered configuration was not an object" + -- | With 'IncludeGeneses' the resolved configuration renders the decoded value -- of every era genesis (Byron via its canonical-JSON form, the rest via the -- ledger's aeson instances), not just the file references; with 'OmitGeneses' diff --git a/test/examples/tracing-file.json b/test/examples/tracing-file.json new file mode 100644 index 0000000..b0da48d --- /dev/null +++ b/test/examples/tracing-file.json @@ -0,0 +1,31 @@ +{ + "AlonzoGenesisFile": "mainnet-alonzo-genesis.json", + "AlonzoGenesisHash": "7e94a15f55d1e82d10f09203fa1d40f8eede58fd8066542cf6566008068ed874", + "ByronGenesisFile": "mainnet-byron-genesis.json", + "ByronGenesisHash": "5f20df933584822601f9e3f8c024eb5eb252fe8cefb24d1317dc3d432e940ebb", + "CheckpointsFile": "mainnet-checkpoints.json", + "CheckpointsFileHash": "3e6dee5bae7acc6d870187e72674b37c929be8c66e62a552cf6a876b1af31ade", + "ConsensusMode": "PraosMode", + "ConwayGenesisFile": "mainnet-conway-genesis.json", + "ConwayGenesisHash": "15a199f895e461ec0ffc6dd4e4028af28a492ab4e806d39cb674c88f7643ef62", + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 3, + "LastKnownBlockVersion-Minor": 0, + "LedgerDB": { + "Backend": "V2LSM", + "LSMDatabasePath": "lsm", + "LSMExportPath": "lsm-export", + "QueryBatchSize": 100000, + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SlotOffset": 388800, + "SnapshotInterval": 432000 + } + }, + "MaxKnownMajorProtocolVersion": 2, + "MinNodeVersion": "10.6.0", + "RequiresNetworkMagic": "RequiresNoMagic", + "ShelleyGenesisFile": "mainnet-shelley-genesis.json", + "ShelleyGenesisHash": "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81", + "HermodTracing": "tracing-sub.json" +} diff --git a/test/examples/tracing-inline.json b/test/examples/tracing-inline.json new file mode 100644 index 0000000..34282c5 --- /dev/null +++ b/test/examples/tracing-inline.json @@ -0,0 +1,45 @@ +{ + "AlonzoGenesisFile": "mainnet-alonzo-genesis.json", + "AlonzoGenesisHash": "7e94a15f55d1e82d10f09203fa1d40f8eede58fd8066542cf6566008068ed874", + "ByronGenesisFile": "mainnet-byron-genesis.json", + "ByronGenesisHash": "5f20df933584822601f9e3f8c024eb5eb252fe8cefb24d1317dc3d432e940ebb", + "CheckpointsFile": "mainnet-checkpoints.json", + "CheckpointsFileHash": "3e6dee5bae7acc6d870187e72674b37c929be8c66e62a552cf6a876b1af31ade", + "ConsensusMode": "PraosMode", + "ConwayGenesisFile": "mainnet-conway-genesis.json", + "ConwayGenesisHash": "15a199f895e461ec0ffc6dd4e4028af28a492ab4e806d39cb674c88f7643ef62", + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 3, + "LastKnownBlockVersion-Minor": 0, + "LedgerDB": { + "Backend": "V2LSM", + "LSMDatabasePath": "lsm", + "LSMExportPath": "lsm-export", + "QueryBatchSize": 100000, + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SlotOffset": 388800, + "SnapshotInterval": 432000 + } + }, + "MaxKnownMajorProtocolVersion": 2, + "MinNodeVersion": "10.6.0", + "RequiresNetworkMagic": "RequiresNoMagic", + "ShelleyGenesisFile": "mainnet-shelley-genesis.json", + "ShelleyGenesisHash": "1a3be38bcbb7911969283716ad7aa550250226b76a61fc51cc9a9a35d9276d81", + "HermodTracing": { + "TraceOptions": { + "": { + "severity": "Info", + "detail": "DNormal", + "backends": [ + "Stdout MachineFormat" + ] + }, + "ChainDB": { + "severity": "Debug" + } + }, + "TraceOptionNodeName": "inline-node" + } +} diff --git a/test/examples/tracing-sub.json b/test/examples/tracing-sub.json new file mode 100644 index 0000000..af5858c --- /dev/null +++ b/test/examples/tracing-sub.json @@ -0,0 +1,12 @@ +{ + "Options": { + "": { + "severity": "Notice", + "detail": "DNormal", + "backends": [ + "Stdout MachineFormat" + ] + } + }, + "ApplicationName": "node-from-file" +} From 51dc070897d83dcc7ed0786a9b69ec333817d2e6 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 2 Jul 2026 14:33:49 +0200 Subject: [PATCH 4/8] Cleanup cabal.project --- cabal.project | 75 ++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/cabal.project b/cabal.project index c9a574d..e71cb61 100644 --- a/cabal.project +++ b/cabal.project @@ -17,43 +17,6 @@ index-state: packages: . --- cardano-ledger at current master (provides the genesis types we decode here). --- Kept to the minimum set of subdirs we actually depend on; add more only if the --- solver requires them. -source-repository-package - type: git - location: https://github.com/IntersectMBO/cardano-ledger - tag: 29c7e3040621f323f3af27fd4e89a223ba429021 - --sha256: sha256-XPXfHvYjuzN0ASL/z4HwWJwDPQn7vJbTPlEa5zV3w9k= - subdir: - eras/byron/ledger/impl - eras/byron/crypto - eras/byron/ledger/executable-spec - eras/byron/chain/executable-spec - eras/shelley/impl - eras/allegra/impl - eras/mary/impl - eras/alonzo/impl - eras/babbage/impl - eras/conway/impl - eras/dijkstra/impl - libs/cardano-ledger-core - libs/cardano-ledger-binary - libs/cardano-data - libs/vector-map - libs/non-integral - libs/small-steps - --- trace-dispatcher (hermod tracing) — provides the tracing configuration parser --- (ConfigSource/readConfiguration) used to resolve the HermodTracing key. --- Pinned to the tip of hermod-tracing#18 (mkarg/config-source). -source-repository-package - type: git - location: https://github.com/IntersectMBO/hermod-tracing - tag: 3d7aeb85d9ef01df48a44c751e45609b4c6dea00 - subdir: - trace-dispatcher - -- Ensures colourized output from test runners test-show-details: direct tests: true @@ -102,3 +65,41 @@ if impl(ghc >=9.14) constraints: -- Happy version 2.2.1 fails to compile haskell-src-exts , happy < 2.2.1 + +-- cardano-ledger at current master (provides the genesis types we decode here). +-- Kept to the minimum set of subdirs we actually depend on; add more only if the +-- solver requires them. +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-ledger + tag: 29c7e3040621f323f3af27fd4e89a223ba429021 + --sha256: sha256-XPXfHvYjuzN0ASL/z4HwWJwDPQn7vJbTPlEa5zV3w9k= + subdir: + eras/byron/ledger/impl + eras/byron/crypto + eras/byron/ledger/executable-spec + eras/byron/chain/executable-spec + eras/shelley/impl + eras/allegra/impl + eras/mary/impl + eras/alonzo/impl + eras/babbage/impl + eras/conway/impl + eras/dijkstra/impl + libs/cardano-ledger-core + libs/cardano-ledger-binary + libs/cardano-data + libs/vector-map + libs/non-integral + libs/small-steps + +-- trace-dispatcher (hermod tracing) — provides the tracing configuration parser +-- (ConfigSource/readConfiguration) used to resolve the HermodTracing key. +-- Pinned to the merge commit after hermod-tracing#18 (mkarg/config-source). +source-repository-package + type: git + location: https://github.com/IntersectMBO/hermod-tracing + tag: 3d7aeb85d9ef01df48a44c751e45609b4c6dea00 + --sha256: sha256-r4bVm4x2akrCLcg5f0TG6aovCGqNnZkiiBckA3YCz7Q= + subdir: + trace-dispatcher From 6216553f495990c2e7616225fbf03b19e1ac0929 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 2 Jul 2026 14:55:15 +0200 Subject: [PATCH 5/8] Use readConfigurationWithDefault --- src/Cardano/Configuration/File/Tracing.hs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Cardano/Configuration/File/Tracing.hs b/src/Cardano/Configuration/File/Tracing.hs index 67820d5..c73aa71 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -7,7 +7,7 @@ -- -- We do not interpret the tracing configuration ourselves: its authoritative -- schema lives in @trace-dispatcher@. What we do is hand the @HermodTracing@ --- value to @trace-dispatcher@'s own parser ('readConfiguration'), which turns it +-- value to @trace-dispatcher@'s own parser ('readConfigurationWithDefault'), which turns it -- into a 'TraceConfig' — a file reference via 'FromFile' (after resolving the -- path to its canonical location), an inline object via 'FromJSONObject'. -- @@ -24,7 +24,7 @@ import Autodocodec import Cardano.Configuration.Basic (optionalFieldStrict) import Cardano.Configuration.Common (filePathCodec) import Cardano.Ledger.BaseTypes (StrictMaybe (..)) -import Cardano.Logging.ConfigurationParser (ConfigSource (..), readConfiguration) +import Cardano.Logging.ConfigurationParser (ConfigSource (..), mkConfiguration, readConfigurationWithDefault) import Cardano.Logging.Types (TraceConfig) import Data.Aeson (FromJSON, Object, ToJSON) import qualified Data.Aeson.KeyMap as KM @@ -86,7 +86,9 @@ instance HasCodec TracingConfiguration where .= hermodTracing -- | Resolve the captured @HermodTracing@ value into a 'TraceConfig' by handing it --- to @trace-dispatcher@'s own parser ('readConfiguration'): +-- to @trace-dispatcher@'s own parser ('readConfigurationWithDefault'), with +-- 'mkConfiguration' (the minimal viable config) supplying defaults for any +-- top-level fields the source leaves unspecified: -- -- * a file reference is resolved to its canonical location (relative paths are -- taken against the configuration directory @root@) and read via 'FromFile'; @@ -103,6 +105,6 @@ resolveTracingConfiguration root (TracingConfiguration mSource) = SNothing -> pure SNothing SJust (TracingConfigFile path) -> do canonPath <- canonicalizePath (root path) - SJust <$> readConfiguration (FromFile canonPath) + SJust <$> readConfigurationWithDefault (FromFile canonPath) mkConfiguration SJust (TracingConfigInline obj) -> - SJust <$> readConfiguration (FromJSONObject obj) + SJust <$> readConfigurationWithDefault (FromJSONObject obj) mkConfiguration From 2daa14b23c05cb22edd5077fdeba20cdf8f315c9 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 2 Jul 2026 15:32:02 +0200 Subject: [PATCH 6/8] Bring in cardano-node's default TraceConfig --- defaults/HermodTracing.json | 79 +++++++++++++++++++++++ src/Cardano/Configuration/File.hs | 2 + src/Cardano/Configuration/File/Tracing.hs | 68 +++++++++++++++++-- test/Main.hs | 22 +++++++ 4 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 defaults/HermodTracing.json diff --git a/defaults/HermodTracing.json b/defaults/HermodTracing.json new file mode 100644 index 0000000..c6b34d1 --- /dev/null +++ b/defaults/HermodTracing.json @@ -0,0 +1,79 @@ +{ + "ApplicationName": null, + "Forwarder": null, + "MetricsPrefix": "cardano.node.metrics.", + "Options": { + "": { + "backends": [ + "Stdout MachineFormat", + "EKGBackend" + ], + "detail": "DNormal", + "severity": "Notice" + }, + "BlockFetch.Client.CompletedBlockFetch": { + "maxFrequency": 2 + }, + "BlockFetch.Decision": { + "severity": "Silence" + }, + "ChainDB": { + "severity": "Info" + }, + "ChainDB.AddBlockEvent.AddBlockValidation": { + "severity": "Silence" + }, + "ChainDB.AddBlockEvent.AddBlockValidation.ValidCandidate": { + "maxFrequency": 2 + }, + "ChainDB.AddBlockEvent.AddedBlockToQueue": { + "maxFrequency": 2 + }, + "ChainDB.AddBlockEvent.AddedBlockToVolatileDB": { + "maxFrequency": 2 + }, + "ChainDB.CopyToImmutableDBEvent.CopiedBlockToImmutableDB": { + "maxFrequency": 2 + }, + "ChainSync.Client": { + "severity": "Warning" + }, + "Forge.Loop": { + "severity": "Info" + }, + "Forge.StateInfo": { + "severity": "Info" + }, + "LedgerMetrics": { + "severity": "Silence" + }, + "Mempool": { + "severity": "Info" + }, + "Net.ConnectionManager.Remote": { + "severity": "Info" + }, + "Net.ErrorPolicy": { + "severity": "Info" + }, + "Net.InboundGovernor": { + "severity": "Warning" + }, + "Net.InboundGovernor.Remote": { + "severity": "Info" + }, + "Net.Mux.Remote": { + "severity": "Info" + }, + "Net.PeerSelection": { + "severity": "Silence" + }, + "Resources": { + "severity": "Silence" + }, + "Startup.DiffusionInit": { + "severity": "Info" + } + }, + "PrometheusSimpleRun": null +} diff --git a/src/Cardano/Configuration/File.hs b/src/Cardano/Configuration/File.hs index 200126a..54d7331 100644 --- a/src/Cardano/Configuration/File.hs +++ b/src/Cardano/Configuration/File.hs @@ -33,6 +33,7 @@ module Cardano.Configuration.File , TracingConfiguration (..) , TracingConfigSource (..) , TraceConfig + , defaultCardanoTracingConfig -- * Resolving components , finalizeNetwork @@ -73,6 +74,7 @@ import Cardano.Configuration.File.Testing import Cardano.Configuration.File.Tracing ( TracingConfigSource (..) , TracingConfiguration (..) + , defaultCardanoTracingConfig , resolveTracingConfiguration ) import Cardano.Configuration.Genesis diff --git a/src/Cardano/Configuration/File/Tracing.hs b/src/Cardano/Configuration/File/Tracing.hs index c73aa71..43b61db 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -18,19 +18,20 @@ module Cardano.Configuration.File.Tracing ( TracingConfiguration (..) , TracingConfigSource (..) , resolveTracingConfiguration + , defaultCardanoTracingConfig ) where import Autodocodec import Cardano.Configuration.Basic (optionalFieldStrict) import Cardano.Configuration.Common (filePathCodec) +import Cardano.Logging import Cardano.Ledger.BaseTypes (StrictMaybe (..)) -import Cardano.Logging.ConfigurationParser (ConfigSource (..), mkConfiguration, readConfigurationWithDefault) -import Cardano.Logging.Types (TraceConfig) import Data.Aeson (FromJSON, Object, ToJSON) import qualified Data.Aeson.KeyMap as KM import GHC.Generics (Generic) import System.Directory (canonicalizePath) import System.FilePath (()) +import qualified Data.Map.Strict as Map -- | Where the @HermodTracing@ configuration comes from: either a path to a -- separate file holding it, or the configuration object given inline. Which one @@ -105,6 +106,65 @@ resolveTracingConfiguration root (TracingConfiguration mSource) = SNothing -> pure SNothing SJust (TracingConfigFile path) -> do canonPath <- canonicalizePath (root path) - SJust <$> readConfigurationWithDefault (FromFile canonPath) mkConfiguration + SJust <$> readConfigurationWithDefault (FromFile canonPath) defaultCardanoTracingConfig SJust (TracingConfigInline obj) -> - SJust <$> readConfigurationWithDefault (FromJSONObject obj) mkConfiguration + SJust <$> readConfigurationWithDefault (FromJSONObject obj) defaultCardanoTracingConfig + +defaultCardanoTracingConfig :: TraceConfig +defaultCardanoTracingConfig = emptyTraceConfig { + tcMetricsPrefix = Just "cardano.node.metrics." + , tcLedgerMetricsFrequency = Nothing -- discard the default from 'trace-dispatcher'; Cardano has own ones, different for block producers and relays + , tcOptions = Map.fromList + [([], + [ ConfSeverity (SeverityF (Just Notice)) + , ConfDetail DNormal + , ConfBackend [ Stdout MachineFormat + , EKGBackend + ]]) + + -- more important tracers going here + ,(["BlockFetch", "Decision"], + [ ConfSeverity (SeverityF Nothing)]) + ,(["ChainDB"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["ChainDB", "AddBlockEvent", "AddBlockValidation"], + [ ConfSeverity (SeverityF Nothing)]) + ,(["ChainSync", "Client"], + [ ConfSeverity (SeverityF (Just Warning))]) + ,(["Net", "ConnectionManager", "Remote"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Startup", "DiffusionInit"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Net", "ErrorPolicy"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Forge", "Loop"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Forge", "StateInfo"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Net", "InboundGovernor", "Remote"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Mempool"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Net", "Mux", "Remote"], + [ ConfSeverity (SeverityF (Just Info))]) + ,(["Net", "InboundGovernor"], + [ ConfSeverity (SeverityF (Just Warning))]) + ,(["Net", "PeerSelection"], + [ ConfSeverity (SeverityF Nothing)]) + ,(["LedgerMetrics"], + [ ConfSeverity (SeverityF Nothing)]) + ,(["Resources"], + [ ConfSeverity (SeverityF Nothing)]) + -- Limiters + ,(["ChainDB","AddBlockEvent","AddedBlockToQueue"], + [ ConfLimiter 2.0]) + ,(["ChainDB","AddBlockEvent","AddedBlockToVolatileDB"], + [ ConfLimiter 2.0]) + ,(["ChainDB","AddBlockEvent","AddBlockValidation", "ValidCandidate"], + [ ConfLimiter 2.0]) + ,(["ChainDB", "CopyToImmutableDBEvent", "CopiedBlockToImmutableDB"], + [ ConfLimiter 2.0]) + ,(["BlockFetch", "Client", "CompletedBlockFetch"], + [ ConfLimiter 2.0]) + ] + } diff --git a/test/Main.hs b/test/Main.hs index be0f019..4af5502 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -83,6 +83,7 @@ cases = , parseCase "test/examples/split.json" , parseCase "test/examples/split-all.json" , tracingCase + , tracingDefaultParityCase , shadowWarnCase , envelopeWarningCase , splitSubfileSchemaCase @@ -342,6 +343,27 @@ tracingCase = Object o -> Right (KM.member (K.fromString "HermodTracing") o) _ -> Left "rendered configuration was not an object" +-- | The committed @defaults/HermodTracing.json@ must equal the JSON of the +-- in-tree 'defaultCardanoTracingConfig' literal (encoded through +-- trace-dispatcher's own 'TraceConfig' codec), so the checked-in default cannot +-- drift from the Haskell source. Regenerate the file from +-- 'defaultCardanoTracingConfig' if this fails. +tracingDefaultParityCase :: TestTree +tracingDefaultParityCase = + testCase "defaults/HermodTracing.json matches defaultCardanoTracingConfig" $ do + path <- getDataFileName "defaults/HermodTracing.json" + committed <- eitherDecodeFileStrict' path :: IO (Either String Value) + expectOk $ case committed of + Left e -> Just ("could not read defaults/HermodTracing.json: " <> e) + Right v + | toJSON defaultCardanoTracingConfig == v -> Nothing + | otherwise -> + Just $ + "defaults/HermodTracing.json is out of date; regenerate from defaultCardanoTracingConfig: " + <> show (toJSON defaultCardanoTracingConfig) + <> " /= " + <> show v + -- | With 'IncludeGeneses' the resolved configuration renders the decoded value -- of every era genesis (Byron via its canonical-JSON form, the rest via the -- ledger's aeson instances), not just the file references; with 'OmitGeneses' From 4f398ad943cd60f273646c426b9fdc0436cbb6fe Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 2 Jul 2026 15:36:49 +0200 Subject: [PATCH 7/8] Fourmolu --- src/Cardano/Configuration/File/Tracing.hs | 164 ++++++++++++++-------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/src/Cardano/Configuration/File/Tracing.hs b/src/Cardano/Configuration/File/Tracing.hs index 43b61db..7202c6d 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -24,14 +24,14 @@ module Cardano.Configuration.File.Tracing import Autodocodec import Cardano.Configuration.Basic (optionalFieldStrict) import Cardano.Configuration.Common (filePathCodec) -import Cardano.Logging import Cardano.Ledger.BaseTypes (StrictMaybe (..)) +import Cardano.Logging import Data.Aeson (FromJSON, Object, ToJSON) import qualified Data.Aeson.KeyMap as KM +import qualified Data.Map.Strict as Map import GHC.Generics (Generic) import System.Directory (canonicalizePath) import System.FilePath (()) -import qualified Data.Map.Strict as Map -- | Where the @HermodTracing@ configuration comes from: either a path to a -- separate file holding it, or the configuration object given inline. Which one @@ -111,60 +111,108 @@ resolveTracingConfiguration root (TracingConfiguration mSource) = SJust <$> readConfigurationWithDefault (FromJSONObject obj) defaultCardanoTracingConfig defaultCardanoTracingConfig :: TraceConfig -defaultCardanoTracingConfig = emptyTraceConfig { - tcMetricsPrefix = Just "cardano.node.metrics." - , tcLedgerMetricsFrequency = Nothing -- discard the default from 'trace-dispatcher'; Cardano has own ones, different for block producers and relays - , tcOptions = Map.fromList - [([], - [ ConfSeverity (SeverityF (Just Notice)) - , ConfDetail DNormal - , ConfBackend [ Stdout MachineFormat - , EKGBackend - ]]) +defaultCardanoTracingConfig = + emptyTraceConfig + { tcMetricsPrefix = Just "cardano.node.metrics." + , tcLedgerMetricsFrequency = Nothing -- discard the default from 'trace-dispatcher'; Cardano has own ones, different for block producers and relays + , tcOptions = + Map.fromList + [ + ( [] + , + [ ConfSeverity (SeverityF (Just Notice)) + , ConfDetail DNormal + , ConfBackend + [ Stdout MachineFormat + , EKGBackend + ] + ] + ) + , -- more important tracers going here - -- more important tracers going here - ,(["BlockFetch", "Decision"], - [ ConfSeverity (SeverityF Nothing)]) - ,(["ChainDB"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["ChainDB", "AddBlockEvent", "AddBlockValidation"], - [ ConfSeverity (SeverityF Nothing)]) - ,(["ChainSync", "Client"], - [ ConfSeverity (SeverityF (Just Warning))]) - ,(["Net", "ConnectionManager", "Remote"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Startup", "DiffusionInit"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Net", "ErrorPolicy"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Forge", "Loop"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Forge", "StateInfo"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Net", "InboundGovernor", "Remote"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Mempool"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Net", "Mux", "Remote"], - [ ConfSeverity (SeverityF (Just Info))]) - ,(["Net", "InboundGovernor"], - [ ConfSeverity (SeverityF (Just Warning))]) - ,(["Net", "PeerSelection"], - [ ConfSeverity (SeverityF Nothing)]) - ,(["LedgerMetrics"], - [ ConfSeverity (SeverityF Nothing)]) - ,(["Resources"], - [ ConfSeverity (SeverityF Nothing)]) - -- Limiters - ,(["ChainDB","AddBlockEvent","AddedBlockToQueue"], - [ ConfLimiter 2.0]) - ,(["ChainDB","AddBlockEvent","AddedBlockToVolatileDB"], - [ ConfLimiter 2.0]) - ,(["ChainDB","AddBlockEvent","AddBlockValidation", "ValidCandidate"], - [ ConfLimiter 2.0]) - ,(["ChainDB", "CopyToImmutableDBEvent", "CopiedBlockToImmutableDB"], - [ ConfLimiter 2.0]) - ,(["BlockFetch", "Client", "CompletedBlockFetch"], - [ ConfLimiter 2.0]) - ] - } + ( ["BlockFetch", "Decision"] + , [ConfSeverity (SeverityF Nothing)] + ) + , + ( ["ChainDB"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["ChainDB", "AddBlockEvent", "AddBlockValidation"] + , [ConfSeverity (SeverityF Nothing)] + ) + , + ( ["ChainSync", "Client"] + , [ConfSeverity (SeverityF (Just Warning))] + ) + , + ( ["Net", "ConnectionManager", "Remote"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Startup", "DiffusionInit"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Net", "ErrorPolicy"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Forge", "Loop"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Forge", "StateInfo"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Net", "InboundGovernor", "Remote"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Mempool"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Net", "Mux", "Remote"] + , [ConfSeverity (SeverityF (Just Info))] + ) + , + ( ["Net", "InboundGovernor"] + , [ConfSeverity (SeverityF (Just Warning))] + ) + , + ( ["Net", "PeerSelection"] + , [ConfSeverity (SeverityF Nothing)] + ) + , + ( ["LedgerMetrics"] + , [ConfSeverity (SeverityF Nothing)] + ) + , + ( ["Resources"] + , [ConfSeverity (SeverityF Nothing)] + ) + , -- Limiters + + ( ["ChainDB", "AddBlockEvent", "AddedBlockToQueue"] + , [ConfLimiter 2.0] + ) + , + ( ["ChainDB", "AddBlockEvent", "AddedBlockToVolatileDB"] + , [ConfLimiter 2.0] + ) + , + ( ["ChainDB", "AddBlockEvent", "AddBlockValidation", "ValidCandidate"] + , [ConfLimiter 2.0] + ) + , + ( ["ChainDB", "CopyToImmutableDBEvent", "CopiedBlockToImmutableDB"] + , [ConfLimiter 2.0] + ) + , + ( ["BlockFetch", "Client", "CompletedBlockFetch"] + , [ConfLimiter 2.0] + ) + ] + } From 553a6053c3bed38a072e294fea510629a3a01dbe Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 2 Jul 2026 15:43:45 +0200 Subject: [PATCH 8/8] Always apply the default configuration for tracing --- src/Cardano/Configuration.hs | 7 +++--- src/Cardano/Configuration/File.hs | 8 ++++--- src/Cardano/Configuration/File/Tracing.hs | 16 +++++++------ src/Cardano/Configuration/Render.hs | 6 ++--- test/Main.hs | 29 +++++++++++++---------- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/Cardano/Configuration.hs b/src/Cardano/Configuration.hs index e7f9358..79f765b 100644 --- a/src/Cardano/Configuration.hs +++ b/src/Cardano/Configuration.hs @@ -125,11 +125,12 @@ data NodeConfiguration = NodeConfiguration , localConnectionsConfig :: File.LocalConnectionsConfig Identity , testingConfiguration :: File.TestingConfiguration Identity , mempoolConfiguration :: File.MempoolConfiguration Identity - , tracingConfiguration :: StrictMaybe File.TraceConfig + , tracingConfiguration :: File.TraceConfig -- ^ The tracing configuration resolved from the top-level @HermodTracing@ key -- by @trace-dispatcher@'s parser (see 'File.resolveTracingConfiguration'), or - -- 'SNothing' when no @HermodTracing@ key is present. Carried through unchanged - -- from the file-parse result so consumers get the parsed 'File.TraceConfig'. + -- 'File.defaultCardanoTracingConfig' when no @HermodTracing@ key is present. + -- Carried through unchanged from the file-parse result so consumers get the + -- parsed 'File.TraceConfig'. , byronGenesisConfig :: ByronGenesisConfig -- ^ The parsed Byron genesis. , shelleyGenesisConfig :: ShelleyGenesis diff --git a/src/Cardano/Configuration/File.hs b/src/Cardano/Configuration/File.hs index 54d7331..1c3d432 100644 --- a/src/Cardano/Configuration/File.hs +++ b/src/Cardano/Configuration/File.hs @@ -135,12 +135,14 @@ data NodeConfigurationFromFileF f , localConnectionsConfig :: f (LocalConnectionsConfig StrictMaybe) , testingConfiguration :: f (TestingConfiguration StrictMaybe) , mempoolConfiguration :: f (MempoolConfiguration StrictMaybe) - , tracingConfiguration :: StrictMaybe TraceConfig + , tracingConfiguration :: TraceConfig -- ^ The tracing configuration referenced by the top-level @HermodTracing@ key, -- resolved by @trace-dispatcher@'s own parser ('resolveTracingConfiguration'): -- a @HermodTracing@ file path is read from that file, an inline object is read - -- directly. 'SNothing' when no @HermodTracing@ key is present. Its schema is - -- owned by @trace-dispatcher@, not described here (see 'TracingConfiguration'). + -- directly. When no @HermodTracing@ key is present it falls back to + -- 'defaultCardanoTracingConfig', so a tracing configuration is always present. + -- Its schema is owned by @trace-dispatcher@, not described here (see + -- 'TracingConfiguration'). , byronGenesisConfig :: ByronGenesisConfig -- ^ The parsed Byron genesis (read from the @ByronGenesisFile@). , shelleyGenesisConfig :: ShelleyGenesis diff --git a/src/Cardano/Configuration/File/Tracing.hs b/src/Cardano/Configuration/File/Tracing.hs index 7202c6d..cab6f8a 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -88,27 +88,29 @@ instance HasCodec TracingConfiguration where -- | Resolve the captured @HermodTracing@ value into a 'TraceConfig' by handing it -- to @trace-dispatcher@'s own parser ('readConfigurationWithDefault'), with --- 'mkConfiguration' (the minimal viable config) supplying defaults for any --- top-level fields the source leaves unspecified: +-- 'defaultCardanoTracingConfig' supplying defaults for any top-level fields the +-- source leaves unspecified: -- -- * a file reference is resolved to its canonical location (relative paths are -- taken against the configuration directory @root@) and read via 'FromFile'; -- * an inline object is read via 'FromJSONObject'. -- --- 'SNothing' when no @HermodTracing@ key is present. +-- When no @HermodTracing@ key is present, the tracing system still needs a +-- configuration, so we fall back to 'defaultCardanoTracingConfig' unchanged +-- rather than to no configuration at all. resolveTracingConfiguration :: -- | The directory a relative @HermodTracing@ file path is resolved against. FilePath -> TracingConfiguration -> - IO (StrictMaybe TraceConfig) + IO TraceConfig resolveTracingConfiguration root (TracingConfiguration mSource) = case mSource of - SNothing -> pure SNothing + SNothing -> pure defaultCardanoTracingConfig SJust (TracingConfigFile path) -> do canonPath <- canonicalizePath (root path) - SJust <$> readConfigurationWithDefault (FromFile canonPath) defaultCardanoTracingConfig + readConfigurationWithDefault (FromFile canonPath) defaultCardanoTracingConfig SJust (TracingConfigInline obj) -> - SJust <$> readConfigurationWithDefault (FromJSONObject obj) defaultCardanoTracingConfig + readConfigurationWithDefault (FromJSONObject obj) defaultCardanoTracingConfig defaultCardanoTracingConfig :: TraceConfig defaultCardanoTracingConfig = diff --git a/src/Cardano/Configuration/Render.hs b/src/Cardano/Configuration/Render.hs index 7ecc2a6..9dd2e69 100644 --- a/src/Cardano/Configuration/Render.hs +++ b/src/Cardano/Configuration/Render.hs @@ -59,10 +59,10 @@ nodeConfigurationToJSON geneses nc = where -- The tracing configuration resolved by trace-dispatcher, rendered under the -- same @HermodTracing@ key it is read from (as an inline object, via - -- trace-dispatcher's own 'ToJSON'). Present only when the configuration set a - -- @HermodTracing@ key. + -- trace-dispatcher's own 'ToJSON'). Always present: absent a @HermodTracing@ + -- key it holds 'File.defaultCardanoTracingConfig'. tracingFields = - mapMaybe strictMaybeToMaybe [("HermodTracing" .=) <$> tracingConfiguration nc] + ["HermodTracing" .= tracingConfiguration nc] -- The resolved (parsed) era geneses, rendered through the ledger's @aeson@ -- 'toJSON' instances (and, for Byron, its canonical-JSON form), so the dump -- shows the decoded genesis content rather than just the file reference and diff --git a/test/Main.hs b/test/Main.hs index 4af5502..bc6dbf3 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -46,7 +46,7 @@ import qualified Data.Aeson.Key as K import qualified Data.Aeson.KeyMap as KM import Data.Functor.Identity (runIdentity) import Data.List (isInfixOf) -import Data.Maybe (fromJust, isJust, isNothing) +import Data.Maybe (fromJust) import qualified Data.Text as T import Data.Word (Word64) import Options.Applicative (defaultPrefs, execParserPure, getParseResult, info) @@ -301,30 +301,33 @@ resolveCase = -- into a 'TraceConfig' — whether given inline (an object) or as a path to a -- separate file — and surfaced both on the parse result and, when the resolved -- configuration is dumped, back under a @HermodTracing@ key. A configuration --- without the key resolves to no tracing config and renders no such key. +-- without the key falls back to 'defaultCardanoTracingConfig', which is likewise +-- surfaced and rendered (so the @HermodTracing@ key always appears). tracingCase :: TestTree tracingCase = - testCase "HermodTracing resolves to a TraceConfig (inline and file) and is rendered" $ do + testCase "HermodTracing resolves to a TraceConfig (inline, file, default) and is always rendered" $ do inline <- parsed "test/examples/tracing-inline.json" fromFile <- parsed "test/examples/tracing-file.json" absent <- parsed "test/examples/fullconfig.json" renderedInline <- rendersTracing "test/examples/tracing-inline.json" renderedAbsent <- rendersTracing "test/examples/fullconfig.json" + let asJSON = toJSON . tracingConfiguration + deflt = toJSON defaultCardanoTracingConfig expectOk $ - if isJust (strictMaybeToMaybe (tracingConfiguration inline)) - && isJust (strictMaybeToMaybe (tracingConfiguration fromFile)) - && isNothing (strictMaybeToMaybe (tracingConfiguration absent)) + if asJSON inline /= deflt -- the inline object was applied + && asJSON fromFile /= deflt -- the referenced file was applied + && asJSON absent == deflt -- no key falls back to the default && renderedInline == Right True - && renderedAbsent == Right False + && renderedAbsent == Right True -- rendered even without a key then Nothing else Just $ - "unexpected tracing resolution: inline=" - <> show (isJust (strictMaybeToMaybe (tracingConfiguration inline))) - <> " file=" - <> show (isJust (strictMaybeToMaybe (tracingConfiguration fromFile))) - <> " absent=" - <> show (isNothing (strictMaybeToMaybe (tracingConfiguration absent))) + "unexpected tracing resolution: inlineIsDefault=" + <> show (asJSON inline == deflt) + <> " fileIsDefault=" + <> show (asJSON fromFile == deflt) + <> " absentIsDefault=" + <> show (asJSON absent == deflt) <> " renderedInline=" <> show renderedInline <> " renderedAbsent="