feat: add --logging-format CLI option for structured logging#9803
feat: add --logging-format CLI option for structured logging#9803yashhzd wants to merge 1 commit intohyperledger:mainfrom
Conversation
|
Hello @yashhzd! Just a heads-up: I’m not a project maintainer, but I'm looking to contribute to Besu by helping with the review process. I have some comments, please feel free to ignore my comments |
| commandLine.addMixin("Ethstats", ethstatsOptions); | ||
| commandLine.addMixin("Private key file", nodePrivateKeyFileOption); | ||
| commandLine.addMixin("Logging level", loggingLevelOption); | ||
| commandLine.addMixin("Logging format", loggingFormatOption); |
There was a problem hiding this comment.
Could you consider merging this 2 options loggingLevelOption and loggingFormatOption to something like LoggerOptions
There was a problem hiding this comment.
Good idea! I kept them separate initially since they control independent aspects (verbosity vs format), but I can see the benefit of grouping them under a single LoggingOptions mixin for better organization.
I'll consolidate them in the next push.
| GCP("classpath:GcpLayout.json"), | ||
|
|
||
| /** Logstash JSON Event Layout V1. */ | ||
| LOGSTASH("classpath:LogstashJsonEventLayoutV1.json"), |
There was a problem hiding this comment.
can we accept a path to config from console instead like it was made for engineJwtKeyFile
There was a problem hiding this comment.
That's a valid use case. For now, users can already point to a fully custom Log4j2 config via the LOG4J_CONFIGURATION_FILE environment variable (or -Dlog4j.configurationFile system property) — XmlExtensionConfiguration checks for this and skips the console appender setup entirely.
Adding a --logging-config-file CLI flag to wrap that behavior would be a nice usability improvement. I'd prefer to keep this PR focused on the preset formats and add the custom config path as a follow-up — would that work for you?
|
|
||
| private LoggingFormat getLoggingFormat() { | ||
| try { | ||
| return BesuCommand.getSelectedLoggingFormat(); |
There was a problem hiding this comment.
Could you please help me confirm whether BesuCommand has not yet been parsed at this point?
I set the configuration format to ECS, but it appears that the value has not been read, as shown below:

However, when I set a breakpoint inside BesuCommand, the value seems to be read correctly:

My understanding is that this might be happening because the logging configuration is initialized before BesuCommand is parsed. In main, setupLogging() is invoked prior to calling besuCommand.parse(...):
public static void main(final String... args) {
setupLogging();
final BesuComponent besuComponent = DaggerBesuComponent.create();
final BesuCommand besuCommand = besuComponent.getBesuCommand();
int exitCode =
besuCommand.parse(
new RunLast(),
besuCommand.parameterExceptionHandler(),
besuCommand.executionExceptionHandler(),
System.in,
besuComponent,
args);
System.exit(exitCode);
}
There was a problem hiding this comment.
Great catch, and thanks for confirming with the debugger screenshots — you're absolutely right.
The root cause is exactly as you described: setupLogging() triggers Log4j2 initialization which calls getLoggingFormat() before PicoCLI has parsed the arguments, so selectedLoggingFormat is still null and defaults to PLAIN.
The current code does call LogConfigurator.reconfigure() inside configureLogging() (which runs after parsing), but relying on the full Log4j2 reconfiguration chain is fragile.
I'm pushing a fix that adds a direct applyLoggingFormat() method on XmlExtensionConfiguration — it grabs the current Log4j2 configuration, replaces the Console appender with the correct layout, and calls updateLoggers(). This way we don't depend on the reconfiguration chain working end-to-end.
Will push shortly.
|
Consolidated Summary of the latest push (15da4bf):
|
| * called after CLI parsing to apply the user's --logging-format choice, since the initial Log4j2 | ||
| * configuration happens before CLI arguments are available. | ||
| */ | ||
| public static void applyLoggingFormat() { |
There was a problem hiding this comment.
Could you consider to move it to LogConfigurator?
There was a problem hiding this comment.
Done — moved applyLoggingFormat() to LogConfigurator (delegating to Log4j2ConfiguratorUtil), so all logging config operations go through the same public API.
The method now takes the eventTemplateUri as a parameter rather than reading from a static field, which also let me remove the currentInstance tracking entirely from XmlExtensionConfiguration.
BesuCommand.configureLogging() now calls:
LogConfigurator.applyLoggingFormat(selectedLoggingFormat.getEventTemplateUri());instead of XmlExtensionConfiguration.applyLoggingFormat().
| @@ -55,7 +55,8 @@ | |||
| import org.hyperledger.besu.cli.options.InProcessRpcOptions; | |||
There was a problem hiding this comment.
Done — consolidated LoggingLevelOption and LoggingFormatOption into a single LoggingOptions mixin. Both --logging (level) and --logging-format are now grouped together under one class, registered as commandLine.addMixin("Logging", loggingOptions).
| @@ -0,0 +1,54 @@ | |||
| { | |||
There was a problem hiding this comment.
I believe GcpLayout is already part of log4j-layout-template-json, it should work without adding it in resources folder.
There was a problem hiding this comment.
You're right — I missed that GcpLayout.json is already bundled in log4j-layout-template-json. The built-in version is actually better too since it maps log levels to GCP severity names (WARN to WARNING, TRACE to DEBUG, FATAL to EMERGENCY) and includes trace/span ID support from MDC.
Removed the custom file in c22c6ef. The classpath:GcpLayout.json URI in LoggingFormat now resolves to the built-in template directly.
|
@yashhzd Your latest commit missed DCO sign-off. If you often forget This should automatically DCO sign all commits in your Besu project. For now, you need to sign your existing commit and force push your changes. |
c22c6ef to
3888266
Compare
Add a new --logging-format CLI option that enables users to programmatically select structured JSON logging formats without requiring custom Log4j2 configuration files. Supported formats: PLAIN (default), ECS (Elastic Common Schema), GCP (Google Cloud Platform), LOGSTASH (Logstash JSON Event Layout V1), GELF (Graylog Extended Log Format). Uses Log4j2's built-in JsonTemplateLayout with standard event templates from the log4j-layout-template-json module - no custom template files needed. Consolidates LoggingLevelOption into LoggingOptions to host both --logging and --logging-format in a single mixin. Applies the chosen format after CLI parsing via LogConfigurator.applyLoggingFormat() to avoid the timing issue where Log4j2 initializes before CLI arguments are parsed. Closes hyperledger#9626 Signed-off-by: Yash Goel <162511050+yashhzd@users.noreply.github.com>
3888266 to
8ae9d84
Compare
This refactoring changes from a two-phase ConfigurationFactory approach to an execution strategy pattern. This fixes timing issues where logging was initialized before CLI arguments were available. Key changes: - Add LoggingConfigurator with fully programmatic Log4j2 configuration - Migrate all log4j2.xml content to Java (filters, Splunk, appenders) - Add execution strategy for logging initialization (correct timing) - Add isHelpOrVersionRequested() to walk subcommand tree - Remove two-phase initialization (ConfigurationFactory + applyFormat) - Remove BesuLoggingConfigurationFactory and XmlExtensionConfiguration - Remove log4j2.xml (all config now in Java) - Deprecate configureLogging() (now just announces via logger) - Remove static selectedLoggingFormat field Benefits: - Single-phase initialization at correct time (after CLI parsing) - No static state required - Fully programmatic (type-safe, refactorable) - Simpler codebase (-108 net lines) All existing features preserved: - --logging / -l flag (log level) - --logging-format flag (PLAIN, ECS, GCP, LOGSTASH, GELF) - --color-enabled flag and NO_COLOR env var - LOGGER=Splunk environment variable - LOG4J_CONFIGURATION_FILE custom config override - All 6 logger filters (DNS, transactions, Bonsai, etc.) Related to PR hyperledger#9803 and issue hyperledger#9626 Signed-off-by: Usman Saleem <usman@usmans.info>
|
@yashhzd You are on the right track, however, you may have realized that we have two-phase initialization of logging. The correct way to fix this behavior is to initialize logging using execution strategy of PicoCLI BEFORE any of the further parsing happens BUT after PicoCli has initialized itself. Have a look at the PR that I opened against your branch and let me know if changes make sense? yashhzd#1 |
Description
Adds a new
--logging-formatCLI option that enables users to programmatically select structured JSON logging formats without requiring custom Log4j2 configuration files.This addresses the need described in #9626: users who want structured JSON logging for Elasticsearch, Google Cloud Logging, or Graylog currently have to create custom Log4j2 XML configs and inject them via
LOG4J_CONFIGURATION_FILE. This is cumbersome in containerized and cloud environments.Supported Formats
PLAINECSGCPLOGSTASHGELFUsage
Implementation Details
JsonTemplateLayoutwith standard event templates from thelog4j-layout-template-jsonmodule (ECS, GCP, GELF, Logstash are all shipped as built-in classpath resources) - no custom template files neededlog4j-layout-template-jsondependency (part of the Log4j2 BOM already declared in the project)LoggingLevelOptionintoLoggingOptionsto host both--loggingand--logging-formatin a single mixinPLAINwhich preserves current behaviorLOG4J_CONFIGURATION_FILEoverride (custom configs take priority)LogConfigurator.applyLoggingFormat()to avoid the timing issue where Log4j2 initializes before CLI arguments are parsedChanges
LoggingFormat.java- Enum with supported formats and their JsonTemplateLayout URIsLoggingOptionsTest.java- Unit tests for the consolidated option and format enumBesuCommand.java- Registered the mixin, stores selected format, added static getterLoggingLevelOption.java->LoggingOptions.java- Consolidated level and format optionsXmlExtensionConfiguration.java- CreatesJsonTemplateLayoutwhen a JSON format is selectedLog4j2ConfiguratorUtil.java/LogConfigurator.java- AddedapplyLoggingFormat()methodapp/build.gradle/util/build.gradle- Addedlog4j-layout-template-jsondependencyBesuCommandTest.java- Integration tests for the new CLI optioneverything_config.toml- Addedlogging-formatentry for completeness testverification-metadata.xml- Updated checksums for new dependencyFixed Issue(s)
Closes #9626
Testing
LoggingFormatenum andLoggingOptions(level + format)BesuCommandTestfor all format values + invalid inputtomlThatConfiguresEverythingExceptPermissioningTomltest updatedXmlExtensionConfigurationTestcontinues to pass