Skip to content

Commit

Permalink
Merge pull request #5566 from thc202/dev/errors-logged
Browse files Browse the repository at this point in the history
  • Loading branch information
kingthorin committed Jul 8, 2024
2 parents c5aae25 + 9226613 commit 8ba6adf
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 0 deletions.
10 changes: 10 additions & 0 deletions addOns/dev/dev.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ zapAddOn {
author.set("ZAP Dev Team")
url.set("https://www.zaproxy.org/docs/desktop/addons/dev-add-on/")

bundle {
baseName.set("org.zaproxy.addon.dev.Messages")
prefix.set("dev")
}

dependencies {
addOns {
register("commonlib") {
Expand All @@ -23,4 +28,9 @@ zapAddOn {
dependencies {
zapAddOn("network")
zapAddOn("commonlib")

compileOnly(libs.log4j.core)

testImplementation(project(":testutils"))
testImplementation(libs.log4j.core)
}
19 changes: 19 additions & 0 deletions addOns/dev/src/main/java/org/zaproxy/addon/dev/ExtensionDev.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.zaproxy.addon.dev.error.LoggedErrorsHandler;
import org.zaproxy.addon.network.ExtensionNetwork;
import org.zaproxy.zap.view.ZapMenuItem;

public class ExtensionDev extends ExtensionAdaptor {

Expand All @@ -33,18 +35,24 @@ public class ExtensionDev extends ExtensionAdaptor {

protected static final String DIRECTORY_NAME = "dev-add-on";

private final LoggedErrorsHandler loggedErrorsHandler;

private TestProxyServer tutorialServer;

private DevParam devParam;

public ExtensionDev() {
super(NAME);

loggedErrorsHandler = new LoggedErrorsHandler();
}

@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);

loggedErrorsHandler.hook(extensionHook);

extensionHook.addOptionsParamSet(this.getDevParam());

if (Constant.isDevMode()) {
Expand All @@ -56,6 +64,15 @@ public void hook(ExtensionHook extensionHook) {
.getExtension(ExtensionNetwork.class));
extensionHook.addApiImplementor(new DevApi());
}

if (hasView()
&& org.zaproxy.zap.extension.log4j.ExtensionLog4j.class.getAnnotation(
Deprecated.class)
!= null) {
ZapMenuItem menuGarbageCollect = new ZapMenuItem("dev.tools.menu.gc");
menuGarbageCollect.addActionListener(e -> Runtime.getRuntime().gc());
extensionHook.getHookMenu().addToolsMenuItem(menuGarbageCollect);
}
}

public DevParam getDevParam() {
Expand All @@ -77,6 +94,8 @@ public void unload() {
if (tutorialServer != null) {
tutorialServer.stop();
}

loggedErrorsHandler.unload();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* 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.
*/
package org.zaproxy.addon.dev.error;

import java.awt.EventQueue;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import javax.swing.SwingUtilities;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.StringLayout;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.filter.LevelMatchFilter;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control.Mode;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.SessionChangedListener;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.utils.DisplayUtils;
import org.zaproxy.zap.view.ScanStatus;

public class LoggedErrorsHandler {

private final boolean loaded;
private ScanStatus scanStatus;

public LoggedErrorsHandler() {
loaded =
org.zaproxy.zap.extension.log4j.ExtensionLog4j.class.getAnnotation(Deprecated.class)
!= null
&& Constant.isDevMode()
&& View.isInitialised();

if (loaded) {
scanStatus =
new ScanStatus(
DisplayUtils.getScaledIcon(
getClass()
.getResource(
"/org/zaproxy/addon/dev/icons/fugue/bug.png")),
Constant.messages.getString("dev.error.icon.title"));

LoggerContext.getContext()
.getConfiguration()
.getRootLogger()
.addAppender(new ErrorAppender(this::handleError), null, null);

View.getSingleton()
.getMainFrame()
.getMainFooterPanel()
.addFooterToolbarRightLabel(scanStatus.getCountLabel());
}
}

public void hook(ExtensionHook extensionHook) {
if (!loaded) {
return;
}

extensionHook.addSessionListener(new ResetCounterOnSessionChange(scanStatus));
}

public void unload() {
if (!loaded) {
return;
}

LoggerContext.getContext()
.getConfiguration()
.getRootLogger()
.removeAppender(ErrorAppender.NAME);

View.getSingleton()
.getMainFrame()
.getMainFooterPanel()
.removeFooterToolbarRightLabel(scanStatus.getCountLabel());
}

private void handleError(String message) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> handleError(message));
return;
}

scanStatus.incScanCount();
View.getSingleton().getOutputPanel().append(message);
}

static class ErrorAppender extends AbstractAppender {

private static final String NAME = "ZAP-ErrorAppender";

private static final Property[] NO_PROPERTIES = {};

private final Consumer<String> logConsumer;

ErrorAppender(Consumer<String> logConsumer) {
super(
NAME,
LevelMatchFilter.newBuilder().setLevel(Level.ERROR).build(),
PatternLayout.newBuilder()
.withDisableAnsi(true)
.withCharset(StandardCharsets.UTF_8)
.withPattern("%m%n")
.build(),
true,
NO_PROPERTIES);
this.logConsumer = logConsumer;
start();
}

@Override
public void append(LogEvent event) {
logConsumer.accept(((StringLayout) getLayout()).toSerializable(event));
}
}

private static class ResetCounterOnSessionChange implements SessionChangedListener {
/** Keep track of errors logged while the session changes. */
private int previousCount;

/** Do not reset the counter if ZAP is starting. */
private boolean starting;

private ScanStatus scanStatus;

public ResetCounterOnSessionChange(ScanStatus scanStatus) {
this.scanStatus = scanStatus;
this.starting = true;
}

@Override
public void sessionAboutToChange(Session session) {
EventQueue.invokeLater(() -> previousCount = scanStatus.getScanCount());
}

@Override
public void sessionChanged(Session session) {
if (starting) {
starting = false;
return;
}

EventQueue.invokeLater(
() -> {
scanStatus.setScanCount(scanStatus.getScanCount() - previousCount);
previousCount = 0;
});
}

@Override
public void sessionScopeChanged(Session session) {
// Nothing to do.
}

@Override
public void sessionModeChanged(Mode mode) {
// Nothing to do.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ dev.api.desc = Dev related API endpoints.
dev.api.other.openapi = Provides the OpenAPI definition of the ZAP API, in YAML format.

dev.desc = An add-on to help with development of ZAP

dev.error.icon.title = Errors

dev.tools.menu.gc = Run the Garbage Collector
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* 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.
*/
package org.zaproxy.zap.extension.log4j;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.startsWith;

import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.zaproxy.zap.extension.log4j.ExtensionLog4j.ErrorAppender;

/** Unit test for {@link LoggedErrorsHandler}. */
class LoggedErrorsHandlerUnitTest {

/** Unit test for {@link ErrorAppender}. */
static class ErrorAppenderUnitTest {

private Logger logger;
private List<String> logEvents;

private ErrorAppender errorAppender;

@BeforeEach
void setup() {
logEvents = new ArrayList<>();
errorAppender = new ErrorAppender(logEvents::add);

LoggerContext context = LoggerContext.getContext();
LoggerConfig rootLoggerconfig = context.getConfiguration().getRootLogger();
rootLoggerconfig
.getAppenders()
.values()
.forEach(context.getRootLogger()::removeAppender);
rootLoggerconfig.addAppender(errorAppender, null, null);
rootLoggerconfig.setLevel(Level.ALL);
context.updateLoggers();

logger = LogManager.getLogger(ErrorAppenderUnitTest.class);
}

@AfterEach
void cleanup() throws Exception {
Configurator.reconfigure(getClass().getResource("/log4j2-test.properties").toURI());
}

@Test
void shouldLogError() {
// Given
Level level = Level.ERROR;
String message = "Log Message";
// When
logger.log(level, message);
// Then
assertThat(logEvents, hasSize(1));
assertThat(logEvents.get(0), startsWith(message));
}

@ParameterizedTest
@ValueSource(strings = {"FATAL", "WARN", "INFO", "DEBUG", "TRACE"})
void shouldIgnoreLogEventsOtherThanError(String levelName) {
// Given
Level level = Level.getLevel(levelName);
String message = "Log Message";
// When
logger.log(level, message);
// Then
assertThat(logEvents, hasSize(0));
}
}
}

0 comments on commit 8ba6adf

Please sign in to comment.