Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add errorprone to prevent Log4j direct usage #3759

Merged
merged 9 commits into from
May 3, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.logging.log4j.ThreadContext;
import org.junit.After;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class AcceptanceTestBase {

Expand Down Expand Up @@ -184,8 +184,8 @@ private void printOutput(final Process process) {

@Override
protected void starting(final Description description) {
ThreadContext.put("test", description.getMethodName());
ThreadContext.put("class", description.getClassName());
MDC.put("test", description.getMethodName());
MDC.put("class", description.getClassName());

final String errorMessage = "Uncaught exception in thread \"{}\"";
Thread.currentThread()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode;
import org.hyperledger.besu.tests.web3j.generated.EventEmitter;
import org.hyperledger.besu.util.Log4j2ConfiguratorUtil;
import org.hyperledger.enclave.testutil.EnclaveType;

import java.io.IOException;
Expand All @@ -28,10 +29,6 @@
import java.util.Optional;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
Expand Down Expand Up @@ -90,12 +87,7 @@ public PrivacyGroupAcceptanceTest(final EnclaveType enclaveType) throws IOExcept

@Test
public void nodeCanCreatePrivacyGroup() {

final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
final LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
loggerConfig.setLevel(Level.DEBUG);
ctx.updateLoggers();
Log4j2ConfiguratorUtil.setLevel("", Level.DEBUG);
final String privacyGroupId =
alice.execute(
privacyTransactions.createPrivacyGroup(
Expand Down
6 changes: 3 additions & 3 deletions besu/src/main/java/org/hyperledger/besu/Besu.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ private static Logger setupLogging() {
+ e.getMessage());
}
final Logger logger = LoggerFactory.getLogger(Besu.class);
Thread.setDefaultUncaughtExceptionHandler(log4jExceptionHandler(logger));
Thread.currentThread().setUncaughtExceptionHandler(log4jExceptionHandler(logger));
Thread.setDefaultUncaughtExceptionHandler(slf4jExceptionHandler(logger));
Thread.currentThread().setUncaughtExceptionHandler(slf4jExceptionHandler(logger));

return logger;
}

private static Thread.UncaughtExceptionHandler log4jExceptionHandler(final Logger logger) {
private static Thread.UncaughtExceptionHandler slf4jExceptionHandler(final Logger logger) {
return (thread, error) -> {
if (logger.isErrorEnabled()) {
logger.error(String.format("Uncaught exception in thread \"%s\"", thread.getName()), error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,37 @@
*/
package org.hyperledger.besu.cli.options.stable;

import java.util.Set;

import org.apache.logging.log4j.Level;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Spec;

public class LoggingLevelOption {

public static LoggingLevelOption create() {
return new LoggingLevelOption();
}

private static final Set<String> ACCEPTED_VALUES =
Set.of("OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "ALL");
@Spec CommandSpec spec;
private Level logLevel;

@CommandLine.Option(
names = {"--logging", "-l"},
paramLabel = "<LOG VERBOSITY LEVEL>",
description = "Logging verbosity levels: OFF, ERROR, WARN, INFO, DEBUG, TRACE, ALL")
public void setLogLevel(final Level logLevel) {
if (Level.FATAL.equals(logLevel)) {
public void setLogLevel(final String logLevel) {
if ("FATAL".equalsIgnoreCase(logLevel)) {
System.out.println("FATAL level is deprecated");
this.logLevel = Level.ERROR;
} else if (ACCEPTED_VALUES.contains(logLevel.toUpperCase())) {
this.logLevel = Level.getLevel(logLevel.toUpperCase());
} else {
this.logLevel = logLevel;
throw new CommandLine.ParameterException(
spec.commandLine(), "Unknown logging value: " + logLevel);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@
import java.util.OptionalLong;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MergeBesuControllerBuilder extends BesuControllerBuilder {
private final AtomicReference<SyncState> syncState = new AtomicReference<>();
private static final Logger LOG = LogManager.getLogger(MergeBesuControllerBuilder.class);
private static final Logger LOG = LoggerFactory.getLogger(MergeBesuControllerBuilder.class);

@Override
protected MiningCoordinator createMiningCoordinator(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.cli.options.stable;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;

import java.util.Arrays;

import org.apache.logging.log4j.Level;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.ParameterException;

public class LoggingLevelOptionTest {

private LoggingLevelOption levelOption;

@Before
public void setUp() {
levelOption = new LoggingLevelOption();
}

@Test
public void fatalLevelEqualsToError() {
levelOption.setLogLevel("fatal");
assertThat(levelOption.getLogLevel()).isEqualTo(Level.ERROR);
}

@Test
public void setsExpectedLevels() {
Arrays.stream(Level.values())
.filter(level -> !Level.FATAL.equals(level))
.forEach(
level -> {
levelOption.setLogLevel(level.name());
assertThat(levelOption.getLogLevel()).isEqualTo(level);
});
}

@Test(expected = ParameterException.class)
public void failsOnUnknownLevel() {
levelOption.spec = Mockito.mock(CommandSpec.class, RETURNS_DEEP_STUBS);
levelOption.setLogLevel("unknown");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public class BannedMethod extends BugChecker implements MethodInvocationTreeMatc
staticMethod().onClass("com.google.common.base.Objects").withAnyName(),
"Do not use com.google.common.base.Objects methods, use java.util.Objects methods instead.",
staticMethod().onClass("org.junit.Assert"),
"Do not use junit assertions. Use assertj assertions instead.");
"Do not use junit assertions. Use assertj assertions instead.",
staticMethod().onClass("org.apache.logging.log4j.LogManager"),
"Do not use org.apache.logging.log4j.LogManager, use org.slf4j.LoggerFactory methods instead.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this include the other log4j methods that were removed in this PR? static methods on org.apache.logging.log4j.core.config.Configurator, org.apache.logging.log4j.ThreadContext, instance methods on import org.apache.logging.log4j.core.LoggerContext, org.apache.logging.log4j.core.config.Configuration,
org.apache.logging.log4j.core.config.LoggerConfig? Or is there a way to go full Ender-Wiggin on it and have it fail for any class from the log4j packages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we cannot rip off the whole package because we still have hard dependencies on it. The most visible one is o.h.b.util.Log4j2ConfiguratorUtil but we also depend on o.a.l.log4j.Level for the CLI. I went on banning just o.a.l.log4j.LogManager b/c it's the most common entry point to the Log4j framework from the day to day developer perspective


@Override
public Description matchMethodInvocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;

import org.apache.logging.log4j.Level;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this use of Log4J be removed? How needed is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We can make o.h.b.util.Log4j2ConfiguratorUtil#setAllLevels to receive a string instead of the o.a.l.log4j.Level. That's a good point, it would encapsulate Log4j2 even further

import org.slf4j.Logger;
Expand All @@ -33,6 +34,8 @@
public class AdminChangeLogLevel implements JsonRpcMethod {

private static final Logger LOG = LoggerFactory.getLogger(AdminChangeLogLevel.class);
private static final Set<String> VALID_PARAMS =
Set.of("OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "ALL");

@Override
public String getName() {
Expand All @@ -42,7 +45,12 @@ public String getName() {
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
try {
final Level logLevel = requestContext.getRequiredParameter(0, Level.class);
final String rawLogLevel = requestContext.getRequiredParameter(0, String.class);
if (!VALID_PARAMS.contains(rawLogLevel)) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}
final Level logLevel = Level.toLevel(rawLogLevel);
final Optional<String[]> optionalLogFilters =
requestContext.getOptionalParameter(1, String[].class);
optionalLogFilters.ifPresentOrElse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.util.Log4j2ConfiguratorUtil;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -40,7 +40,7 @@ public class AdminChangeLogLevelTest {
@Before
public void before() {
adminChangeLogLevel = new AdminChangeLogLevel();
Configurator.setAllLevels("", Level.INFO);
Log4j2ConfiguratorUtil.setAllLevels("", Level.INFO);
}

@Test
Expand All @@ -52,7 +52,7 @@ public void shouldReturnExpectedMethodName() {
public void shouldReturnCorrectResponseWhenRequestHasLogLevel() {
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "admin_changeLogLevel", new Object[] {Level.DEBUG}));
new JsonRpcRequest("2.0", "admin_changeLogLevel", new Object[] {Level.DEBUG.name()}));
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId());

Expand All @@ -71,7 +71,9 @@ public void shouldReturnCorrectResponseWhenRequestHasLogLevelAndFilters() {
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest(
"2.0", "admin_changeLogLevel", new Object[] {Level.DEBUG, new String[] {"com"}}));
"2.0",
"admin_changeLogLevel",
new Object[] {Level.DEBUG.name(), new String[] {"com"}}));
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(request.getRequest().getId());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
*/
package org.hyperledger.besu.ethereum.vm;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.hyperledger.besu.util.Log4j2ConfiguratorUtil;

import org.junit.Before;
import org.junit.Test;

Expand Down Expand Up @@ -73,7 +73,7 @@ public void execution() {
}

private void resetLogging() {
((LoggerContext) LogManager.getContext(false)).reconfigure();
Log4j2ConfiguratorUtil.reconfigure();
}

/** Subclasses should implement this method to run the actual JUnit test. */
Expand Down