diff --git a/README.md b/README.md index 7e8b22b..70dbaf2 100644 --- a/README.md +++ b/README.md @@ -150,14 +150,27 @@ 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`. + +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 diff --git a/cabal.project b/cabal.project index 0c4805c..e71cb61 100644 --- a/cabal.project +++ b/cabal.project @@ -17,33 +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 - -- Ensures colourized output from test runners test-show-details: direct tests: true @@ -73,6 +46,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 @@ -91,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 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/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/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..79f765b 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 @@ -123,6 +125,12 @@ data NodeConfiguration = NodeConfiguration , localConnectionsConfig :: File.LocalConnectionsConfig Identity , testingConfiguration :: File.TestingConfiguration Identity , mempoolConfiguration :: File.MempoolConfiguration Identity + , tracingConfiguration :: File.TraceConfig + -- ^ The tracing configuration resolved from the top-level @HermodTracing@ key + -- by @trace-dispatcher@'s parser (see 'File.resolveTracingConfiguration'), or + -- '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 @@ -302,6 +310,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/File.hs b/src/Cardano/Configuration/File.hs index 1774a5d..1c3d432 100644 --- a/src/Cardano/Configuration/File.hs +++ b/src/Cardano/Configuration/File.hs @@ -31,6 +31,9 @@ module Cardano.Configuration.File , TestingConfiguration (..) , MempoolConfiguration (..) , TracingConfiguration (..) + , TracingConfigSource (..) + , TraceConfig + , defaultCardanoTracingConfig -- * Resolving components , finalizeNetwork @@ -69,6 +72,11 @@ import Cardano.Configuration.File.Protocol import Cardano.Configuration.File.Storage import Cardano.Configuration.File.Testing import Cardano.Configuration.File.Tracing + ( TracingConfigSource (..) + , TracingConfiguration (..) + , defaultCardanoTracingConfig + , resolveTracingConfiguration + ) import Cardano.Configuration.Genesis ( GenesisReadError , genesisErrorFile @@ -83,6 +91,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 +135,14 @@ 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 :: 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. 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 @@ -210,7 +223,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 +255,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..cab6f8a 100644 --- a/src/Cardano/Configuration/File/Tracing.hs +++ b/src/Cardano/Configuration/File/Tracing.hs @@ -1,31 +1,74 @@ -- | 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 ('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'. +-- +-- 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 + , defaultCardanoTracingConfig ) 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 +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 (()) + +-- | 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 + +-- | 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, --- whose value is a path (a string) to a separate file holding that object. It --- is captured opaquely; see the module documentation. +-- | 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 +77,144 @@ 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 ('readConfigurationWithDefault'), with +-- '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'. +-- +-- 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 TraceConfig +resolveTracingConfiguration root (TracingConfiguration mSource) = + case mSource of + SNothing -> pure defaultCardanoTracingConfig + SJust (TracingConfigFile path) -> do + canonPath <- canonicalizePath (root path) + readConfigurationWithDefault (FromFile canonPath) defaultCardanoTracingConfig + SJust (TracingConfigInline obj) -> + 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/src/Cardano/Configuration/Render.hs b/src/Cardano/Configuration/Render.hs index 59466e8..9dd2e69 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'). Always present: absent a @HermodTracing@ + -- key it holds 'File.defaultCardanoTracingConfig'. + tracingFields = + ["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 b87a5d9..bc6dbf3 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -82,6 +82,8 @@ cases = , parseCase "test/examples/fullconfig.json" , parseCase "test/examples/split.json" , parseCase "test/examples/split-all.json" + , tracingCase + , tracingDefaultParityCase , shadowWarnCase , envelopeWarningCase , splitSubfileSchemaCase @@ -295,6 +297,76 @@ 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 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, 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 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 True -- rendered even without a key + then Nothing + else + Just $ + "unexpected tracing resolution: inlineIsDefault=" + <> show (asJSON inline == deflt) + <> " fileIsDefault=" + <> show (asJSON fromFile == deflt) + <> " absentIsDefault=" + <> show (asJSON absent == deflt) + <> " 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" + +-- | 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' 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" +}