You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by or...@apache.org on 2020/08/22 23:50:38 UTC

[qpid-broker-j] 01/05: QPID-8368: [Broker-J] Graylog support

This is an automated email from the ASF dual-hosted git repository.

orudyy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git

commit c4d463282dd506aff8dd150792be980b48339a9e
Author: Marek Laca <ma...@deutsche-boerse.com>
AuthorDate: Thu Jul 30 13:37:22 2020 +0200

    QPID-8368: [Broker-J] Graylog support
---
 .../apache/qpid/server/logging/EventLogger.java    |  16 +-
 broker-plugins/graylog-logging-logback/pom.xml     |  96 +++
 .../qpid/server/logging/CallerDataFilter.java      |  84 ++
 .../logging/logback/BrokerGraylogLogger.java       |  31 +
 .../logging/logback/BrokerGraylogLoggerImpl.java   | 223 +++++
 .../logging/logback/GelfAppenderConfiguration.java |  61 ++
 .../logging/logback/GelfAppenderDefaults.java      |  64 ++
 .../logging/logback/GelfEncoderConfiguration.java  |  64 ++
 .../logging/logback/GelfEncoderDefaults.java       |  62 ++
 .../server/logging/logback/GraylogAppender.java    | 124 +++
 .../qpid/server/logging/logback/GraylogLogger.java | 128 +++
 .../logging/logback/VirtualHostGraylogLogger.java  |  32 +
 .../logback/VirtualHostGraylogLoggerImpl.java      | 224 +++++
 .../server/logging/logback/event/LoggingEvent.java | 149 ++++
 .../server/logging/logback/validator/AtLeast.java  |  66 ++
 .../logging/logback/validator/AtLeastOne.java      |  44 +
 .../logging/logback/validator/AtLeastZero.java     |  44 +
 .../validator/GelfConfigurationValidator.java      | 120 +++
 .../logback/validator/GelfMessageStaticFields.java | 143 ++++
 .../server/logging/logback/validator/Port.java     |  70 ++
 .../logging/logback/validator/Validator.java       |  30 +
 .../org/apache/qpid/server/util/ArrayUtils.java    |  37 +
 .../management/logger/brokerlogger/graylog/add.js  |  49 ++
 .../management/logger/brokerlogger/graylog/show.js |  56 ++
 .../logger/virtualhostlogger/graylog/add.js        |  49 ++
 .../logger/virtualhostlogger/graylog/show.js       |  56 ++
 .../main/java/resources/logger/graylog/add.html    | 305 +++++++
 .../main/java/resources/logger/graylog/show.html   | 135 +++
 .../resources/logger/graylog/showStaticField.html  |   4 +
 .../qpid/server/logging/CallerDataFilterTest.java  | 167 ++++
 .../logging/logback/GraylogAppenderTest.java       | 932 +++++++++++++++++++++
 .../logging/logback/event/LoggingEventTest.java    | 298 +++++++
 .../logging/logback/event/TestLoggingEvent.java    | 224 +++++
 .../logging/logback/validator/AtLeastOneTest.java  |  93 ++
 .../logging/logback/validator/AtLeastTest.java     |  91 ++
 .../logging/logback/validator/AtLeastZeroTest.java |  93 ++
 .../validator/GelfConfigurationValidatorTest.java  | 788 +++++++++++++++++
 .../validator/GelfMessageStaticFieldsTest.java     | 142 ++++
 .../server/logging/logback/validator/PortTest.java |  91 ++
 .../logback/validator/TestConfiguredObject.java    | 486 +++++++++++
 .../validator/TestConfiguredObjectFactory.java     | 100 +++
 .../validator/TestConfiguredObjectTypeFactory.java |  88 ++
 .../logging/logback/validator/TestModel.java       | 118 +++
 .../apache/qpid/server/util/ArrayUtilsTest.java    |  73 ++
 .../src/main/java/resources/addLogger.html         |   4 +-
 .../src/main/java/resources/css/common.css         |  19 +
 .../resources/js/qpid/common/MapInputWidget.js     | 450 ++++++++++
 .../src/main/java/resources/js/qpid/common/util.js |  54 +-
 .../resources/js/qpid/common/widgetconfigurer.js   |   2 +-
 .../java/resources/js/qpid/management/addLogger.js | 103 ++-
 broker/pom.xml                                     |  19 +
 .../runtime/Java-Broker-Runtime-Log-Files.xml      |   9 +
 pom.xml                                            |  28 +
 53 files changed, 7005 insertions(+), 33 deletions(-)

diff --git a/broker-core/src/main/java/org/apache/qpid/server/logging/EventLogger.java b/broker-core/src/main/java/org/apache/qpid/server/logging/EventLogger.java
index ed97e14..40f3ad5 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/logging/EventLogger.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/logging/EventLogger.java
@@ -20,7 +20,7 @@
  */
 package org.apache.qpid.server.logging;
 
-public class EventLogger
+public class EventLogger implements MessageLogger
 {
     private MessageLogger _messageLogger;
 
@@ -40,6 +40,7 @@ public class EventLogger
      * @param subject The subject that is being logged
      * @param message The message to log
      */
+    @Override
     public void message(LogSubject subject, LogMessage message)
     {
         _messageLogger.message(subject, message);
@@ -50,11 +51,24 @@ public class EventLogger
      *
      * @param message The message to log
      */
+    @Override
     public void message(LogMessage message)
     {
         _messageLogger.message((message));
     }
 
+    @Override
+    public boolean isEnabled()
+    {
+        return _messageLogger.isEnabled();
+    }
+
+    @Override
+    public boolean isMessageEnabled(String logHierarchy)
+    {
+        return _messageLogger.isMessageEnabled(logHierarchy);
+    }
+
     public void setMessageLogger(final MessageLogger messageLogger)
     {
         _messageLogger = messageLogger;
diff --git a/broker-plugins/graylog-logging-logback/pom.xml b/broker-plugins/graylog-logging-logback/pom.xml
new file mode 100644
index 0000000..7371dfa
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.qpid</groupId>
+    <artifactId>qpid-broker-parent</artifactId>
+    <version>9.0.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>qpid-broker-plugins-graylog-logging-logback</artifactId>
+  <name>Apache Qpid Broker-J LogBack GrayLog Logging Plug-in</name>
+  <description>LogBack GrayLog Logging broker plug-in</description>
+
+  <properties>
+    <logback-gelf-version>3.0.0</logback-gelf-version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-broker-plugins-logging-logback</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-broker-core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-broker-codegen</artifactId>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>de.siegmar</groupId>
+      <artifactId>logback-gelf</artifactId>
+      <version>${logback-gelf-version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+    </dependency>
+
+    <!-- test dependencies -->
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-test-utils</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-broker-core</artifactId>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+      <resource>
+        <directory>src/main/java</directory>
+        <includes>
+          <include>resources/</include>
+        </includes>
+      </resource>
+    </resources>
+  </build>
+
+</project>
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/CallerDataFilter.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/CallerDataFilter.java
new file mode 100644
index 0000000..b16284e
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/CallerDataFilter.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class CallerDataFilter
+{
+    private static final Set<String> METHOD_NAMES = buildMethodNames();
+
+    private final ClassLoader _classLoader = Thread.currentThread().getContextClassLoader();
+
+    public StackTraceElement[] filter(StackTraceElement[] elements)
+    {
+        if (elements == null)
+        {
+            return new StackTraceElement[0];
+        }
+
+        for (int depth = elements.length - 1; depth >= 0; --depth)
+        {
+            final StackTraceElement element = elements[depth];
+            if (isMessageMethod(element.getMethodName()) && isMessageLogger(element.getClassName()))
+            {
+                final int length = elements.length - (depth + 1);
+                if (length > 0)
+                {
+                    final StackTraceElement[] stackTrace = new StackTraceElement[length];
+                    System.arraycopy(elements, depth + 1, stackTrace, 0, length);
+                    return stackTrace;
+                }
+                return elements;
+            }
+        }
+        return elements;
+    }
+
+    private boolean isMessageMethod(String method)
+    {
+        return METHOD_NAMES.contains(method);
+    }
+
+    private boolean isMessageLogger(String className)
+    {
+        try
+        {
+            return MessageLogger.class.isAssignableFrom(Class.forName(className, false, _classLoader));
+        }
+        catch (ClassNotFoundException ignored)
+        {
+            return false;
+        }
+    }
+
+    private static Set<String> buildMethodNames()
+    {
+        return Arrays.stream(MessageLogger.class.getDeclaredMethods())
+                .filter(method -> Void.TYPE.equals(method.getReturnType()))
+                .map(Method::getName)
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLogger.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLogger.java
new file mode 100644
index 0000000..4617672
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLogger.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import org.apache.qpid.server.model.BrokerLogger;
+import org.apache.qpid.server.model.ManagedObject;
+
+@ManagedObject(category = false, type = GraylogLogger.TYPE,
+        description = "Logger implementation that writes log events to a remote graylog server",
+        validChildTypes = "org.apache.qpid.server.logging.logback.AbstractLogger#getSupportedBrokerLoggerChildTypes()")
+public interface BrokerGraylogLogger<X extends BrokerGraylogLogger<X>> extends BrokerLogger<X>, GraylogLogger<X>
+{
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLoggerImpl.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLoggerImpl.java
new file mode 100644
index 0000000..26e4e26
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerGraylogLoggerImpl.java
@@ -0,0 +1,223 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.Context;
+import org.apache.qpid.server.logging.logback.validator.GelfConfigurationValidator;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ManagedAttributeField;
+import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class BrokerGraylogLoggerImpl extends AbstractBrokerLogger<BrokerGraylogLoggerImpl> implements BrokerGraylogLogger<BrokerGraylogLoggerImpl>
+{
+    @ManagedObjectFactoryConstructor
+    public BrokerGraylogLoggerImpl(Map<String, Object> attributes, Broker<?> broker)
+    {
+        super(attributes, broker);
+    }
+
+    @ManagedAttributeField
+    private String _remoteHost;
+
+    @ManagedAttributeField
+    private int _port = GelfAppenderDefaults.PORT.value();
+
+    @ManagedAttributeField
+    private int _reconnectionInterval = GelfAppenderDefaults.RECONNECTION_INTERVAL.value();
+
+    @ManagedAttributeField
+    private int _connectionTimeout = GelfAppenderDefaults.CONNECTION_TIMEOUT.value();
+
+    @ManagedAttributeField
+    private int _maximumReconnectionAttempts = GelfAppenderDefaults.MAXIMUM_RECONNECTION_ATTEMPTS.value();
+
+    @ManagedAttributeField
+    private int _retryDelay = GelfAppenderDefaults.RETRY_DELAY.value();
+
+    @ManagedAttributeField
+    private int _messagesFlushTimeOut = GelfAppenderDefaults.MESSAGES_FLUSH_TIMEOUT.value();
+
+    @ManagedAttributeField
+    private int _messageBufferCapacity = GelfAppenderDefaults.MESSAGE_BUFFER_CAPACITY.value();
+
+    @ManagedAttributeField
+    private boolean _rawMessageIncluded = GelfEncoderDefaults.RAW_MESSAGE_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _eventMarkerIncluded = GelfEncoderDefaults.EVENT_MARKER_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _mdcPropertiesIncluded = GelfEncoderDefaults.MDC_PROPERTIES_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _callerDataIncluded = GelfEncoderDefaults.CALLER_DATA_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _rootExceptionDataIncluded = GelfEncoderDefaults.ROOT_EXCEPTION_DATA_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _logLevelNameIncluded = GelfEncoderDefaults.LOG_LEVEL_NAME_INCLUDED.value();
+
+    @ManagedAttributeField
+    private Map<String, Object> _staticFields = Collections.emptyMap();
+
+    @ManagedAttributeField
+    private String _messageOriginHost;
+
+    private AsyncAppender _appender;
+
+    @Override
+    public String getRemoteHost()
+    {
+        return _remoteHost;
+    }
+
+    @Override
+    public int getPort()
+    {
+        return _port;
+    }
+
+    @Override
+    public int getReconnectionInterval()
+    {
+        return _reconnectionInterval;
+    }
+
+    @Override
+    public int getConnectionTimeout()
+    {
+        return _connectionTimeout;
+    }
+
+    @Override
+    public int getMaximumReconnectionAttempts()
+    {
+        return _maximumReconnectionAttempts;
+    }
+
+    @Override
+    public int getRetryDelay()
+    {
+        return _retryDelay;
+    }
+
+    @Override
+    public int getMessagesFlushTimeOut()
+    {
+        return _messagesFlushTimeOut;
+    }
+
+    @Override
+    public int getMessageBufferCapacity()
+    {
+        return _messageBufferCapacity;
+    }
+
+    @Override
+    public boolean isRawMessageIncluded()
+    {
+        return _rawMessageIncluded;
+    }
+
+    @Override
+    public boolean isEventMarkerIncluded()
+    {
+        return _eventMarkerIncluded;
+    }
+
+    @Override
+    public boolean hasMdcPropertiesIncluded()
+    {
+        return _mdcPropertiesIncluded;
+    }
+
+    @Override
+    public boolean isCallerDataIncluded()
+    {
+        return _callerDataIncluded;
+    }
+
+    @Override
+    public boolean hasRootExceptionDataIncluded()
+    {
+        return _rootExceptionDataIncluded;
+    }
+
+    @Override
+    public boolean isLogLevelNameIncluded()
+    {
+        return _logLevelNameIncluded;
+    }
+
+    @Override
+    public Map<String, Object> getStaticFields()
+    {
+        return _staticFields;
+    }
+
+    @Override
+    public String getMessageOriginHost()
+    {
+        return _messageOriginHost;
+    }
+
+    @Override
+    public AsyncAppender appender()
+    {
+        return _appender;
+    }
+
+    @Override
+    protected Appender<ILoggingEvent> createAppenderInstance(Context context)
+    {
+        _appender = GraylogAppender.newInstance(context, this);
+        return _appender;
+    }
+
+    @Override
+    protected void validateOnCreate()
+    {
+        super.validateOnCreate();
+        GelfConfigurationValidator.validateConfiguration(this, this);
+    }
+
+    @Override
+    protected void onOpen()
+    {
+        super.onOpen();
+        GelfConfigurationValidator.validateConfiguration(this, this);
+    }
+
+    @Override
+    protected void postSetAttributes(Set<String> actualUpdatedAttributes)
+    {
+        super.postSetAttributes(actualUpdatedAttributes);
+        GelfConfigurationValidator.validateConfiguration(this, this, actualUpdatedAttributes);
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderConfiguration.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderConfiguration.java
new file mode 100644
index 0000000..7d2fc24
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderConfiguration.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+public interface GelfAppenderConfiguration extends GelfEncoderConfiguration
+{
+    String getRemoteHost();
+
+    default int getPort()
+    {
+        return GelfAppenderDefaults.PORT.value();
+    }
+
+    default int getReconnectionInterval()
+    {
+        return GelfAppenderDefaults.RECONNECTION_INTERVAL.value();
+    }
+
+    default int getConnectionTimeout()
+    {
+        return GelfAppenderDefaults.CONNECTION_TIMEOUT.value();
+    }
+
+    default int getMaximumReconnectionAttempts()
+    {
+        return GelfAppenderDefaults.MAXIMUM_RECONNECTION_ATTEMPTS.value();
+    }
+
+    default int getRetryDelay()
+    {
+        return GelfAppenderDefaults.RETRY_DELAY.value();
+    }
+
+    default int getMessagesFlushTimeOut()
+    {
+        return GelfAppenderDefaults.MESSAGES_FLUSH_TIMEOUT.value();
+    }
+
+    default int getMessageBufferCapacity()
+    {
+        return GelfAppenderDefaults.MESSAGE_BUFFER_CAPACITY.value();
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderDefaults.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderDefaults.java
new file mode 100644
index 0000000..6749af9
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfAppenderDefaults.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+public enum GelfAppenderDefaults
+{
+    PORT(GelfAppenderDefaults.PORT_AS_STRING),
+
+    RECONNECTION_INTERVAL(GelfAppenderDefaults.RECONNECTION_INTERVAL_AS_STRING),
+
+    CONNECTION_TIMEOUT(GelfAppenderDefaults.CONNECTION_TIMEOUT_AS_STRING),
+
+    MAXIMUM_RECONNECTION_ATTEMPTS(GelfAppenderDefaults.MAXIMUM_RECONNECTION_ATTEMPTS_AS_STRING),
+
+    RETRY_DELAY(GelfAppenderDefaults.RETRY_DELAY_AS_STRING),
+
+    MESSAGES_FLUSH_TIMEOUT(GelfAppenderDefaults.MESSAGES_FLUSH_TIMEOUT_AS_STRING),
+
+    MESSAGE_BUFFER_CAPACITY(GelfAppenderDefaults.MESSAGE_BUFFER_CAPACITY_AS_STRING);
+
+    public static final String PORT_AS_STRING = "12201";
+
+    public static final String RECONNECTION_INTERVAL_AS_STRING = "60000";
+
+    public static final String CONNECTION_TIMEOUT_AS_STRING = "15000";
+
+    public static final String MAXIMUM_RECONNECTION_ATTEMPTS_AS_STRING = "2";
+
+    public static final String RETRY_DELAY_AS_STRING = "3000";
+
+    public static final String MESSAGES_FLUSH_TIMEOUT_AS_STRING = "1000";
+
+    public static final String MESSAGE_BUFFER_CAPACITY_AS_STRING = "256";
+
+    private final int _value;
+
+    GelfAppenderDefaults(String value)
+    {
+        this._value = Integer.parseInt(value);
+    }
+
+    public int value()
+    {
+        return _value;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderConfiguration.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderConfiguration.java
new file mode 100644
index 0000000..500c571
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import java.util.Collections;
+import java.util.Map;
+
+public interface GelfEncoderConfiguration
+{
+    String getMessageOriginHost();
+
+    default boolean isRawMessageIncluded()
+    {
+        return GelfEncoderDefaults.RAW_MESSAGE_INCLUDED.value();
+    }
+
+    default boolean isEventMarkerIncluded()
+    {
+        return GelfEncoderDefaults.EVENT_MARKER_INCLUDED.value();
+    }
+
+    default boolean hasMdcPropertiesIncluded()
+    {
+        return GelfEncoderDefaults.MDC_PROPERTIES_INCLUDED.value();
+    }
+
+    default boolean isCallerDataIncluded()
+    {
+        return GelfEncoderDefaults.CALLER_DATA_INCLUDED.value();
+    }
+
+    default boolean hasRootExceptionDataIncluded()
+    {
+        return GelfEncoderDefaults.ROOT_EXCEPTION_DATA_INCLUDED.value();
+    }
+
+    default boolean isLogLevelNameIncluded()
+    {
+        return GelfEncoderDefaults.LOG_LEVEL_NAME_INCLUDED.value();
+    }
+
+    default Map<String, Object> getStaticFields()
+    {
+        return Collections.emptyMap();
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderDefaults.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderDefaults.java
new file mode 100644
index 0000000..62890ec
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GelfEncoderDefaults.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+public enum GelfEncoderDefaults
+{
+    RAW_MESSAGE_INCLUDED(GelfEncoderDefaults.RAW_MESSAGE_INCLUDED_AS_STRING),
+
+    EVENT_MARKER_INCLUDED(GelfEncoderDefaults.EVENT_MARKER_INCLUDED_AS_STRING),
+
+    MDC_PROPERTIES_INCLUDED(GelfEncoderDefaults.MDC_PROPERTIES_INCLUDED_AS_STRING),
+
+    CALLER_DATA_INCLUDED(GelfEncoderDefaults.CALLER_DATA_INCLUDED_AS_STRING),
+
+    ROOT_EXCEPTION_DATA_INCLUDED(GelfEncoderDefaults.ROOT_EXCEPTION_DATA_INCLUDED_AS_STRING),
+
+    LOG_LEVEL_NAME_INCLUDED(GelfEncoderDefaults.LOG_LEVEL_NAME_INCLUDED_AS_STRING);
+
+    public static final String FALSE = "false";
+    public static final String TRUE = "true";
+
+    public static final String RAW_MESSAGE_INCLUDED_AS_STRING = FALSE;
+
+    public static final String EVENT_MARKER_INCLUDED_AS_STRING = TRUE;
+
+    public static final String MDC_PROPERTIES_INCLUDED_AS_STRING = TRUE;
+
+    public static final String CALLER_DATA_INCLUDED_AS_STRING = FALSE;
+
+    public static final String ROOT_EXCEPTION_DATA_INCLUDED_AS_STRING = FALSE;
+
+    public static final String LOG_LEVEL_NAME_INCLUDED_AS_STRING = FALSE;
+
+    private final boolean _value;
+
+    GelfEncoderDefaults(String value)
+    {
+        _value = Boolean.parseBoolean(value);
+    }
+
+    public boolean value() {
+        return _value;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogAppender.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogAppender.java
new file mode 100644
index 0000000..841bb8b
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogAppender.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Context;
+import de.siegmar.logbackgelf.GelfEncoder;
+import de.siegmar.logbackgelf.GelfTcpAppender;
+import org.apache.qpid.server.logging.logback.event.LoggingEvent;
+
+import java.util.Objects;
+
+final class GraylogAppender extends AsyncAppender
+{
+    private final GelfAppenderConfiguration _configuration;
+
+    static GraylogAppender newInstance(Context context, GelfAppenderConfiguration config)
+    {
+        final GraylogAppender appender = new GraylogAppender(config);
+        appender.setContext(context);
+        appender.setQueueSize(config.getMessageBufferCapacity());
+        appender.setNeverBlock(true);
+        appender.setMaxFlushTime(config.getMessagesFlushTimeOut());
+        appender.setIncludeCallerData(config.isCallerDataIncluded());
+        return appender;
+    }
+
+    private GraylogAppender(GelfAppenderConfiguration configuration)
+    {
+        super();
+        this._configuration = Objects.requireNonNull(configuration);
+    }
+
+    @Override
+    public void start()
+    {
+        if (!isStarted())
+        {
+            final GelfEncoder encoder = buildEncoder(getContext(), _configuration);
+            encoder.start();
+
+            final GelfTcpAppender appender = newGelfTcpAppender(getContext(), _configuration);
+            appender.setEncoder(encoder);
+            appender.setName(getName());
+            appender.start();
+            addAppender(appender);
+        }
+        super.start();
+    }
+
+    @Override
+    public void setName(final String name)
+    {
+        super.setName(name);
+        iteratorForAppenders().forEachRemaining(item -> item.setName(name));
+    }
+
+    @Override
+    public void setContext(final Context context)
+    {
+        super.setContext(context);
+        iteratorForAppenders().forEachRemaining(item -> item.setContext(context));
+    }
+
+    @Override
+    public void doAppend(ILoggingEvent eventObject)
+    {
+        super.doAppend(LoggingEvent.wrap(eventObject));
+    }
+
+    private GelfTcpAppender newGelfTcpAppender(Context context, GelfAppenderConfiguration logger)
+    {
+        final GelfTcpAppender appender = new GelfTcpAppender();
+        appender.setContext(context);
+        appender.setGraylogHost(logger.getRemoteHost());
+        appender.setGraylogPort(logger.getPort());
+        appender.setReconnectInterval(calculateReconnectionInterval(logger));
+        appender.setConnectTimeout(logger.getConnectionTimeout());
+        appender.setMaxRetries(logger.getMaximumReconnectionAttempts());
+        appender.setRetryDelay(logger.getRetryDelay());
+        return appender;
+    }
+
+    private int calculateReconnectionInterval(GelfAppenderConfiguration logger)
+    {
+        final int modulo = logger.getReconnectionInterval() % 1000;
+        final int auxiliary = logger.getReconnectionInterval() / 1000;
+        return modulo > 0 ? auxiliary + 1 : auxiliary;
+    }
+
+    private GelfEncoder buildEncoder(Context context, GelfEncoderConfiguration settings)
+    {
+        final GelfEncoder encoder = new GelfEncoder();
+        encoder.setContext(context);
+        encoder.setOriginHost(settings.getMessageOriginHost());
+        encoder.setIncludeRawMessage(settings.isRawMessageIncluded());
+        encoder.setIncludeMarker(settings.isEventMarkerIncluded());
+        encoder.setIncludeMdcData(settings.hasMdcPropertiesIncluded());
+        encoder.setIncludeCallerData(settings.isCallerDataIncluded());
+        encoder.setIncludeRootCauseData(settings.hasRootExceptionDataIncluded());
+        encoder.setIncludeLevelName(settings.isLogLevelNameIncluded());
+        encoder.setStaticFields(settings.getStaticFields());
+        return encoder;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogLogger.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogLogger.java
new file mode 100644
index 0000000..f47230e
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/GraylogLogger.java
@@ -0,0 +1,128 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import ch.qos.logback.classic.AsyncAppender;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ManagedAttribute;
+import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.ManagedStatistic;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
+
+import java.util.Map;
+
+@ManagedObject
+public interface GraylogLogger<X extends GraylogLogger<X>> extends GelfAppenderConfiguration, ConfiguredObject<X>
+{
+    String TYPE = "Graylog";
+
+    @Override
+    @ManagedAttribute(mandatory = true, description = "The graylog server remote host.")
+    String getRemoteHost();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.PORT_AS_STRING,
+            description = "The graylog server port number.")
+    int getPort();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.RECONNECTION_INTERVAL_AS_STRING,
+            description = "The reconnection interval.")
+    int getReconnectionInterval();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.CONNECTION_TIMEOUT_AS_STRING,
+            description = "The connection timeout.")
+    int getConnectionTimeout();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.MAXIMUM_RECONNECTION_ATTEMPTS_AS_STRING,
+            description = "The maximum reconnection attempts.")
+    int getMaximumReconnectionAttempts();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.RETRY_DELAY_AS_STRING,
+            description = "The retry delay.")
+    int getRetryDelay();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.MESSAGES_FLUSH_TIMEOUT_AS_STRING,
+            description = "The messages flush time out at logger stop.")
+    int getMessagesFlushTimeOut();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfAppenderDefaults.MESSAGE_BUFFER_CAPACITY_AS_STRING,
+            description = "The capacity of the message buffer.")
+    int getMessageBufferCapacity();
+
+    @Override
+    @ManagedAttribute(mandatory = true, description = "The origin host that is included in the GELF log message.")
+    String getMessageOriginHost();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.RAW_MESSAGE_INCLUDED_AS_STRING,
+            description = "Include the raw error in the GELF log message.")
+    boolean isRawMessageIncluded();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.EVENT_MARKER_INCLUDED_AS_STRING,
+            description = "Include the event marker in the GELF log message.")
+    boolean isEventMarkerIncluded();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.MDC_PROPERTIES_INCLUDED_AS_STRING,
+            description = "Include the MDC properties in the GELF log message.")
+    boolean hasMdcPropertiesIncluded();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.CALLER_DATA_INCLUDED_AS_STRING,
+            description = "Include the caller data in the GELF log message.")
+    boolean isCallerDataIncluded();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.ROOT_EXCEPTION_DATA_INCLUDED_AS_STRING,
+            description = "Include the root cause of error in the GELF log message.")
+    boolean hasRootExceptionDataIncluded();
+
+    @Override
+    @ManagedAttribute(defaultValue = GelfEncoderDefaults.LOG_LEVEL_NAME_INCLUDED_AS_STRING,
+            description = "Include the log level in the GELF log message.")
+    boolean isLogLevelNameIncluded();
+
+    @Override
+    @ManagedAttribute(description = "Additional static fields for the GELF log message.")
+    Map<String, Object> getStaticFields();
+
+    AsyncAppender appender();
+
+    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Graylog Appender Buffer Size",
+            description = "The buffer size of the Broker Graylog appender.")
+    default int getAppenderBufferUsage()
+    {
+        final AsyncAppender appender = appender();
+        if (appender != null)
+        {
+            return appender.getQueueSize() - appender.getRemainingCapacity();
+        }
+        return 0;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLogger.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLogger.java
new file mode 100644
index 0000000..8582cde
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLogger.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.VirtualHostLogger;
+
+@ManagedObject(category = false,
+        type = GraylogLogger.TYPE,
+        validChildTypes = "org.apache.qpid.server.logging.logback.AbstractLogger#getSupportedVirtualHostLoggerChildTypes()",
+        amqpName = "org.apache.qpid.VirtualHostGraylogLogger")
+public interface VirtualHostGraylogLogger<X extends VirtualHostGraylogLogger<X>> extends VirtualHostLogger<X>, GraylogLogger<X>
+{
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLoggerImpl.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLoggerImpl.java
new file mode 100644
index 0000000..2c07913
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/VirtualHostGraylogLoggerImpl.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.Context;
+import org.apache.qpid.server.logging.logback.validator.GelfConfigurationValidator;
+import org.apache.qpid.server.model.ManagedAttributeField;
+import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
+import org.apache.qpid.server.model.VirtualHost;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class VirtualHostGraylogLoggerImpl extends AbstractVirtualHostLogger<VirtualHostGraylogLoggerImpl> implements VirtualHostGraylogLogger<VirtualHostGraylogLoggerImpl>
+{
+    @ManagedAttributeField
+    private String _remoteHost;
+
+    @ManagedAttributeField
+    private int _port = GelfAppenderDefaults.PORT.value();
+
+    @ManagedAttributeField
+    private int _reconnectionInterval = GelfAppenderDefaults.RECONNECTION_INTERVAL.value();
+
+    @ManagedAttributeField
+    private int _connectionTimeout = GelfAppenderDefaults.CONNECTION_TIMEOUT.value();
+
+    @ManagedAttributeField
+    private int _maximumReconnectionAttempts = GelfAppenderDefaults.MAXIMUM_RECONNECTION_ATTEMPTS.value();
+
+    @ManagedAttributeField
+    private int _retryDelay = GelfAppenderDefaults.RETRY_DELAY.value();
+
+    @ManagedAttributeField
+    private int _messagesFlushTimeOut = GelfAppenderDefaults.MESSAGES_FLUSH_TIMEOUT.value();
+
+    @ManagedAttributeField
+    private int _messageBufferCapacity = GelfAppenderDefaults.MESSAGE_BUFFER_CAPACITY.value();
+
+    @ManagedAttributeField
+    private boolean _rawMessageIncluded = GelfEncoderDefaults.RAW_MESSAGE_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _eventMarkerIncluded = GelfEncoderDefaults.EVENT_MARKER_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _mdcPropertiesIncluded = GelfEncoderDefaults.MDC_PROPERTIES_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _callerDataIncluded = GelfEncoderDefaults.CALLER_DATA_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _rootExceptionDataIncluded = GelfEncoderDefaults.ROOT_EXCEPTION_DATA_INCLUDED.value();
+
+    @ManagedAttributeField
+    private boolean _logLevelNameIncluded = GelfEncoderDefaults.LOG_LEVEL_NAME_INCLUDED.value();
+
+    @ManagedAttributeField
+    private Map<String, Object> _staticFields = Collections.emptyMap();
+
+    @ManagedAttributeField
+    private String _messageOriginHost;
+
+    private AsyncAppender _appender;
+
+    @ManagedObjectFactoryConstructor
+    public VirtualHostGraylogLoggerImpl(Map<String, Object> attributes, VirtualHost<?> virtualHost)
+    {
+        super(attributes, virtualHost);
+    }
+
+    @Override
+    public String getRemoteHost()
+    {
+        return _remoteHost;
+    }
+
+    @Override
+    public int getPort()
+    {
+        return _port;
+    }
+
+    @Override
+    public int getReconnectionInterval()
+    {
+        return _reconnectionInterval;
+    }
+
+    @Override
+    public int getConnectionTimeout()
+    {
+        return _connectionTimeout;
+    }
+
+    @Override
+    public int getMaximumReconnectionAttempts()
+    {
+        return _maximumReconnectionAttempts;
+    }
+
+    @Override
+    public int getRetryDelay()
+    {
+        return _retryDelay;
+    }
+
+    @Override
+    public int getMessagesFlushTimeOut()
+    {
+        return _messagesFlushTimeOut;
+    }
+
+    @Override
+    public int getMessageBufferCapacity()
+    {
+        return _messageBufferCapacity;
+    }
+
+    @Override
+    public boolean isRawMessageIncluded()
+    {
+        return _rawMessageIncluded;
+    }
+
+    @Override
+    public boolean isEventMarkerIncluded()
+    {
+        return _eventMarkerIncluded;
+    }
+
+    @Override
+    public boolean hasMdcPropertiesIncluded()
+    {
+        return _mdcPropertiesIncluded;
+    }
+
+    @Override
+    public boolean isCallerDataIncluded()
+    {
+        return _callerDataIncluded;
+    }
+
+    @Override
+    public boolean hasRootExceptionDataIncluded()
+    {
+        return _rootExceptionDataIncluded;
+    }
+
+    @Override
+    public boolean isLogLevelNameIncluded()
+    {
+        return _logLevelNameIncluded;
+    }
+
+    @Override
+    public Map<String, Object> getStaticFields()
+    {
+        return _staticFields;
+    }
+
+    @Override
+    public String getMessageOriginHost()
+    {
+        return _messageOriginHost;
+    }
+
+    @Override
+    public AsyncAppender appender()
+    {
+        return _appender;
+    }
+
+    @Override
+    protected Appender<ILoggingEvent> createAppenderInstance(Context context)
+    {
+        _appender = GraylogAppender.newInstance(context, this);
+        return _appender;
+    }
+
+    @Override
+    protected void validateOnCreate()
+    {
+        super.validateOnCreate();
+        GelfConfigurationValidator.validateConfiguration(this, this);
+
+    }
+
+    @Override
+    protected void onOpen()
+    {
+        super.onOpen();
+        GelfConfigurationValidator.validateConfiguration(this, this);
+    }
+
+    @Override
+    protected void postSetAttributes(Set<String> actualUpdatedAttributes)
+    {
+        super.postSetAttributes(actualUpdatedAttributes);
+        GelfConfigurationValidator.validateConfiguration(this, this, actualUpdatedAttributes);
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/event/LoggingEvent.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/event/LoggingEvent.java
new file mode 100644
index 0000000..5bbfaff
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/event/LoggingEvent.java
@@ -0,0 +1,149 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.event;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import org.apache.qpid.server.logging.CallerDataFilter;
+import org.apache.qpid.server.util.ArrayUtils;
+import org.slf4j.Marker;
+
+import java.util.Map;
+import java.util.Objects;
+
+public final class LoggingEvent implements ILoggingEvent
+{
+    private static final CallerDataFilter FILTER = new CallerDataFilter();
+
+    private final ILoggingEvent _event;
+
+    private StackTraceElement[] _callerData = null;
+
+    public static ILoggingEvent wrap(ILoggingEvent event)
+    {
+        return event != null ? new LoggingEvent(event) : null;
+    }
+
+    private LoggingEvent(ILoggingEvent event)
+    {
+        _event = Objects.requireNonNull(event);
+    }
+
+    @Override
+    public String getThreadName()
+    {
+        return _event.getThreadName();
+    }
+
+    @Override
+    public Level getLevel()
+    {
+        return _event.getLevel();
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return _event.getMessage();
+    }
+
+    @Override
+    public Object[] getArgumentArray()
+    {
+        return _event.getArgumentArray();
+    }
+
+    @Override
+    public String getFormattedMessage()
+    {
+        return _event.getFormattedMessage();
+    }
+
+    @Override
+    public String getLoggerName()
+    {
+        return _event.getLoggerName();
+    }
+
+    @Override
+    public LoggerContextVO getLoggerContextVO()
+    {
+        return _event.getLoggerContextVO();
+    }
+
+    @Override
+    public IThrowableProxy getThrowableProxy()
+    {
+        return _event.getThrowableProxy();
+    }
+
+    @Override
+    public StackTraceElement[] getCallerData()
+    {
+        if (_callerData == null)
+        {
+            _callerData = FILTER.filter(_event.getCallerData());
+        }
+        return ArrayUtils.clone(_callerData);
+    }
+
+    @Override
+    public boolean hasCallerData()
+    {
+        return !ArrayUtils.isEmpty(getCallerData());
+    }
+
+    @Override
+    public Marker getMarker()
+    {
+        return _event.getMarker();
+    }
+
+    @Override
+    public Map<String, String> getMDCPropertyMap()
+    {
+        return _event.getMDCPropertyMap();
+    }
+
+    /**
+     * @deprecated getMDCPropertyMap method should be used instead.
+     */
+    @Deprecated
+    @Override
+    public Map<String, String> getMdc()
+    {
+        return _event.getMdc();
+    }
+
+    @Override
+    public long getTimeStamp()
+    {
+        return _event.getTimeStamp();
+    }
+
+    @Override
+    public void prepareForDeferredProcessing()
+    {
+        _event.prepareForDeferredProcessing();
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeast.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeast.java
new file mode 100644
index 0000000..4db18bd
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeast.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+import java.util.function.Predicate;
+
+public class AtLeast implements Validator<Integer>, Predicate<Integer>
+{
+    private final int _min;
+
+    AtLeast(int min)
+    {
+        this._min = min;
+    }
+
+    @Override
+    public boolean test(Integer value)
+    {
+        return value != null && value >= _min;
+    }
+
+    @Override
+    public void validate(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        if (!test(value))
+        {
+            throw new IllegalConfigurationException(errorMessage(value, object, attribute));
+        }
+    }
+
+    private String errorMessage(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        return "Attribute '" + attribute
+                + "' instance of " + object.getClass().getName()
+                + " named '" + object.getName() + "'"
+                + " cannot have value '" + value + "'"
+                + " as it has to be at least " + minimum();
+    }
+
+    int minimum()
+    {
+        return _min;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastOne.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastOne.java
new file mode 100644
index 0000000..8f4da2d
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastOne.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public final class AtLeastOne extends AtLeast
+{
+    private static final AtLeastOne VALIDATOR = new AtLeastOne();
+
+    public static Validator<Integer> validator()
+    {
+        return VALIDATOR;
+    }
+
+    public static void validateValue(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        validator().validate(value, object, attribute);
+    }
+
+    private AtLeastOne()
+    {
+        super(1);
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastZero.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastZero.java
new file mode 100644
index 0000000..d4ee704
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/AtLeastZero.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public final class AtLeastZero extends AtLeast
+{
+    private static final AtLeastZero VALIDATOR = new AtLeastZero();
+
+    public static Validator<Integer> validator()
+    {
+        return VALIDATOR;
+    }
+
+    public static void validateValue(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        validator().validate(value, object, attribute);
+    }
+
+    private AtLeastZero()
+    {
+        super(0);
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidator.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidator.java
new file mode 100644
index 0000000..2eb0d37
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidator.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.logging.logback.GelfAppenderConfiguration;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+import java.util.Arrays;
+import java.util.Set;
+
+public enum GelfConfigurationValidator
+{
+    PORT("port")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    Port.validatePort(configuration.getPort(), object, attributeName());
+                }
+            },
+    RECONNECTION_INTERVAL("reconnectionInterval")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastZero.validateValue(configuration.getReconnectionInterval(), object, attributeName());
+                }
+            },
+    CONNECTION_TIMEOUT("connectionTimeout")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastZero.validateValue(configuration.getConnectionTimeout(), object, attributeName());
+                }
+            },
+    MAXIMUM_RECONNECTION_ATTEMPTS("maximumReconnectionAttempts")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastZero.validateValue(configuration.getMaximumReconnectionAttempts(), object, attributeName());
+                }
+            },
+    RETRY_DELAY("retryDelay")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastZero.validateValue(configuration.getRetryDelay(), object, attributeName());
+                }
+            },
+    BUFFER_CAPACITY("messageBufferCapacity")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastOne.validateValue(configuration.getMessageBufferCapacity(), object, attributeName());
+                }
+            },
+    FLUSH_TIME_OUT("messagesFlushTimeOut")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    AtLeastZero.validateValue(configuration.getMessagesFlushTimeOut(), object, attributeName());
+                }
+            },
+    STATIC_FIELDS("staticFields")
+            {
+                @Override
+                public void validate(GelfAppenderConfiguration configuration, ConfiguredObject<?> object)
+                {
+                    GelfMessageStaticFields.validateStaticFields(configuration.getStaticFields(), object, attributeName());
+                }
+            };
+
+    private final String _attributeName;
+
+    public abstract void validate(GelfAppenderConfiguration logger, ConfiguredObject<?> object);
+
+    public static void validateConfiguration(final GelfAppenderConfiguration logger, final ConfiguredObject<?> object)
+    {
+        Arrays.asList(values()).forEach(validator -> validator.validate(logger, object));
+    }
+
+    public static void validateConfiguration(final GelfAppenderConfiguration logger, final ConfiguredObject<?> object, Set<String> changedAttributes)
+    {
+        Arrays.stream(values()).filter(validator -> changedAttributes.contains(validator.attributeName())).forEach(validator -> validator.validate(logger, object));
+    }
+
+    GelfConfigurationValidator(String name)
+    {
+        _attributeName = name;
+    }
+
+    public String attributeName()
+    {
+        return _attributeName;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFields.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFields.java
new file mode 100644
index 0000000..1ba7c56
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFields.java
@@ -0,0 +1,143 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+public final class GelfMessageStaticFields implements Validator<Map<String, Object>>
+{
+    private static class Key implements Validator<String>, Predicate<String>
+    {
+        private static final Pattern PATTERN = Pattern.compile("[\\w\\.\\-]+");
+
+        Key()
+        {
+            super();
+        }
+
+        @Override
+        public boolean test(String value)
+        {
+            return value != null && PATTERN.matcher(value).matches();
+        }
+
+        @Override
+        public void validate(String value, ConfiguredObject<?> object, String attribute)
+        {
+            if (!test(value))
+            {
+                throw new IllegalConfigurationException(errorMessage(value, object, attribute));
+            }
+        }
+
+        private String errorMessage(String value, ConfiguredObject<?> object, String attribute)
+        {
+            return "Key of '" + attribute + " attribute"
+                    + " instance of " + object.getClass().getName()
+                    + " named '" + object.getName() + "'"
+                    + " cannot be '" + value + "'."
+                    + " Key pattern is: " + PATTERN.pattern();
+        }
+    }
+
+    private static final class Value implements Validator<Object>, Predicate<Object>
+    {
+        Value()
+        {
+            super();
+        }
+
+        @Override
+        public boolean test(Object value)
+        {
+            return value instanceof String || value instanceof Number;
+        }
+
+        @Override
+        public void validate(Object value, ConfiguredObject<?> object, String attribute)
+        {
+            if (!test(value))
+            {
+                throw new IllegalConfigurationException(errorMessage(value, object, attribute));
+            }
+        }
+
+        private String errorMessage(Object value, ConfiguredObject<?> object, String attribute)
+        {
+            return "Value of '" + attribute + " attribute"
+                    + " instance of " + object.getClass().getName()
+                    + " named '" + object.getName() + "'"
+                    + " cannot be '" + value + "',"
+                    + " as it has to be a string or number";
+        }
+    }
+
+    private static final Key KEY = new Key();
+
+    private static final Value VALUE = new Value();
+
+    private static final GelfMessageStaticFields VALIDATOR = new GelfMessageStaticFields();
+
+    public static Validator<Map<String, Object>> validator()
+    {
+        return VALIDATOR;
+    }
+
+    public static void validateStaticFields(Map<String, Object> value, ConfiguredObject<?> object, String attribute)
+    {
+        validator().validate(value, object, attribute);
+    }
+
+    private GelfMessageStaticFields()
+    {
+        super();
+    }
+
+    @Override
+    public void validate(Map<String, Object> map, final ConfiguredObject<?> object, final String attribute)
+    {
+        if (map == null) {
+            throw new IllegalConfigurationException(nullErrorMessage(object, attribute));
+        }
+        map.entrySet().forEach(entry -> validateMapEntry(entry, object, attribute));
+    }
+
+    private String nullErrorMessage(ConfiguredObject<?> object, String attribute)
+    {
+        return "Attribute '" + attribute
+                + " instance of " + object.getClass().getName()
+                + " named '" + object.getName() + "'"
+                + " cannot be 'null'";
+    }
+
+    private void validateMapEntry(Entry<String, Object> entry, ConfiguredObject<?> object, String attribute)
+    {
+        KEY.validate(entry.getKey(), object, attribute);
+        VALUE.validate(entry.getValue(), object, attribute);
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Port.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Port.java
new file mode 100644
index 0000000..4a30b7b
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Port.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+import java.util.function.Predicate;
+
+public final class Port implements Validator<Integer>, Predicate<Integer>
+{
+    private static final Port VALIDATOR = new Port();
+
+    public static Validator<Integer> validator()
+    {
+        return VALIDATOR;
+    }
+
+    public static void validatePort(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        validator().validate(value, object, attribute);
+    }
+
+    private Port()
+    {
+        super();
+    }
+
+    @Override
+    public boolean test(Integer value)
+    {
+        return value != null && value >= 1 && value <= 65535;
+    }
+
+    @Override
+    public void validate(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        if (!test(value))
+        {
+            throw new IllegalConfigurationException(errorMessage(value, object, attribute));
+        }
+    }
+
+    private String errorMessage(Integer value, ConfiguredObject<?> object, String attribute)
+    {
+        return "Attribute '" + attribute + "' instance of " + object.getClass().getName()
+                + " named '" + object.getName() + "'"
+                + " cannot have value '" + value + "'"
+                + " as it has to be in range [1, 65535]";
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Validator.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Validator.java
new file mode 100644
index 0000000..89e09ac
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/logging/logback/validator/Validator.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+@FunctionalInterface
+public interface Validator<T>
+{
+    void validate(T value, ConfiguredObject<?> object, String attribute);
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/util/ArrayUtils.java b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/util/ArrayUtils.java
new file mode 100644
index 0000000..b26e439
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/org/apache/qpid/server/util/ArrayUtils.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.util;
+
+public final class ArrayUtils
+{
+    private ArrayUtils()
+    {
+        super();
+    }
+
+    public static <T> T[] clone(T[] array) {
+        return array != null ? array.clone() : null;
+    }
+
+    public static boolean isEmpty(Object[] array) {
+        return array == null || array.length == 0;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/add.js b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/add.js
new file mode 100644
index 0000000..a9015f8
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/add.js
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ *
+ */
+define(["dojo/dom",
+        "dojo/query",
+        "dojo/_base/array",
+        "dijit/registry",
+        "qpid/common/util",
+        "dojo/parser",
+        "dojo/text!logger/graylog/add.html",
+        "dojo/text!logger/graylog/showStaticField.html",
+        "qpid/common/MapInputWidget",
+        "dojo/domReady!"],
+    function (dom, query, array, registry, util, parser, template)
+    {
+        return {
+            show: function (data)
+            {
+                data.containerNode.innerHTML = template;
+                return parser.parse(data.containerNode);
+            },
+            doNotScroll: function (containerNode)
+            {
+                const classNameToRemove = "mapList-scroll-y";
+                util.findNode(classNameToRemove, containerNode).forEach(function (node)
+                {
+                    if (node.classList) {
+                        node.classList.remove(classNameToRemove);
+                    }
+                });
+            }
+        };
+    });
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/show.js b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/show.js
new file mode 100644
index 0000000..fa42eef
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/brokerlogger/graylog/show.js
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ *
+ */
+define(["qpid/common/util",
+        "dojo/text!logger/graylog/show.html",
+        "dojo/text!logger/graylog/showStaticField.html",
+        "qpid/common/TypeTabExtension",
+        "dojo/domReady!"],
+    function (util, template, fieldTemplate, TypeTabExtension)
+    {
+        function Graylog(params)
+        {
+            const type = "Graylog";
+            const category = "BrokerLogger";
+
+            this.containerNode = params.containerNode;
+
+            TypeTabExtension.call(this,
+                params.containerNode,
+                template,
+                category,
+                type,
+                params.metadata,
+                params.data);
+
+            this.appenderBufferUsage = util.findNode("appenderBufferUsage", params.containerNode);
+        }
+
+        util.extend(Graylog, TypeTabExtension);
+
+        Graylog.prototype.update = function (restData)
+        {
+            util.updateAttributeNodes(this.attributeContainers, restData, util.updateBooleanAttributeNode,
+                (containerObject, data, utl) => util.updateMapAttributeNode(containerObject, data, utl, fieldTemplate));
+            const bufferUsage = String(restData["statistics"]["appenderBufferUsage"]);
+            this.appenderBufferUsage.forEach(node => node.innerHTML = bufferUsage);
+        }
+
+        return Graylog;
+    });
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/add.js b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/add.js
new file mode 100644
index 0000000..a9015f8
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/add.js
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ *
+ */
+define(["dojo/dom",
+        "dojo/query",
+        "dojo/_base/array",
+        "dijit/registry",
+        "qpid/common/util",
+        "dojo/parser",
+        "dojo/text!logger/graylog/add.html",
+        "dojo/text!logger/graylog/showStaticField.html",
+        "qpid/common/MapInputWidget",
+        "dojo/domReady!"],
+    function (dom, query, array, registry, util, parser, template)
+    {
+        return {
+            show: function (data)
+            {
+                data.containerNode.innerHTML = template;
+                return parser.parse(data.containerNode);
+            },
+            doNotScroll: function (containerNode)
+            {
+                const classNameToRemove = "mapList-scroll-y";
+                util.findNode(classNameToRemove, containerNode).forEach(function (node)
+                {
+                    if (node.classList) {
+                        node.classList.remove(classNameToRemove);
+                    }
+                });
+            }
+        };
+    });
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/show.js b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/show.js
new file mode 100644
index 0000000..cdfef05
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/js/qpid/management/logger/virtualhostlogger/graylog/show.js
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ *
+ */
+define(["qpid/common/util",
+        "dojo/text!logger/graylog/show.html",
+        "dojo/text!logger/graylog/showStaticField.html",
+        "qpid/common/TypeTabExtension",
+        "dojo/domReady!"],
+    function (util, template, fieldTemplate, TypeTabExtension)
+    {
+        function Graylog(params)
+        {
+            const type = "Graylog";
+            const category = "VirtualHostLogger";
+
+            this.containerNode = params.containerNode;
+
+            TypeTabExtension.call(this,
+                params.containerNode,
+                template,
+                category,
+                type,
+                params.metadata,
+                params.data);
+
+            this.appenderBufferUsage = util.findNode("appenderBufferUsage", params.containerNode);
+        }
+
+        util.extend(Graylog, TypeTabExtension);
+
+        Graylog.prototype.update = function (restData)
+        {
+            util.updateAttributeNodes(this.attributeContainers, restData, util.updateBooleanAttributeNode,
+                (containerObject, data, utl) => util.updateMapAttributeNode(containerObject, data, utl, fieldTemplate));
+            const bufferUsage = String(restData["statistics"]["appenderBufferUsage"]);
+            this.appenderBufferUsage.forEach(node => node.innerHTML = bufferUsage);
+        }
+
+        return Graylog;
+    });
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/add.html b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/add.html
new file mode 100644
index 0000000..b63a001
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/add.html
@@ -0,0 +1,305 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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.
+  ~
+  -->
+
+<div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Connection Options</legend>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Remote Host*:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.remoteHost"
+                           data-dojo-type="dijit/form/ValidationTextBox"
+                           data-dojo-props="
+                              name: 'remoteHost',
+                              required: true,
+                              placeHolder: '',
+                              promptMessage: 'Enter the host where the Graylog server is running',
+                              title: 'Enter the host where the Graylog server is running'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Port:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.port"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'port',
+                              placeHolder: '12201',
+                              promptMessage: 'Enter the port number on which the Graylog server runs',
+                              title: 'Port number on which the Graylog server runs',
+                              constraints:{min:1,max:65535,places:0},
+                              invalidMessage:'Please enter a port number in range [1,65535]',
+                              rangeMessage:'Insert a integer in the range [1,65535]'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Reconnection interval [ms]:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.reconnectionInterval"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'reconnectionInterval',
+                              placeHolder: '60000',
+                              promptMessage: 'Enter the time period of connection periodical reset',
+                              title: 'Time period of connection periodical reset',
+                              constraints:{min:0,max:2147483647,places:0},
+                              invalidMessage:'Please enter a reconnection interval',
+                              rangeMessage:'Insert zero or a positive integer'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Connection timeout [ms]:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.connectionTimeout"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'connectionTimeout',
+                              placeHolder: '15000',
+                              promptMessage: 'Enter the connection timeout',
+                              title: 'Connection timeout',
+                              constraints:{min:0,max:2147483647,places:0},
+                              invalidMessage:'Please enter a connection timeout',
+                              rangeMessage:'Insert zero (infinity) or a positive integer'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Maximum reconnection attempts:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.maximumReconnectionAttempts"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'maximumReconnectionAttempts',
+                              placeHolder: '2',
+                              promptMessage: 'Enter how many times can the client try to reconnect',
+                              title: 'Maximum reconnection attempts',
+                              constraints:{min:0,max:2147483647,places:0},
+                              invalidMessage:'Please enter a maximum reconnection attempts',
+                              rangeMessage:'Insert zero or a positive integer'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Retry delay [ms]:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.retryDelay"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'retryDelay',
+                              placeHolder: '3000',
+                              promptMessage: 'Enter delay between reconnection attempts',
+                              title: 'Retry delay',
+                              constraints:{min:0,max:2147483647,places:0},
+                              invalidMessage:'Please enter a retry delay',
+                              rangeMessage:'Insert zero or a positive integer'"/>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Message Buffer Options</legend>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Capacity:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.messageBufferCapacity"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'messageBufferCapacity',
+                              placeHolder: '256',
+                              promptMessage: 'Enter the capacity of the message buffer',
+                              title: 'Message buffer capacity',
+                              constraints:{min:1,max:2147483647,places:0},
+                              invalidMessage:'Please enter a message buffer capacity',
+                              rangeMessage:'Insert a positive integer'"/>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Logger Stopping</legend>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Messages flush timeout [ms]:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.messagesFlushTimeOut"
+                           data-dojo-type="dijit/form/NumberTextBox"
+                           data-dojo-props="
+                              name: 'messagesFlushTimeOut',
+                              placeHolder: '1000',
+                              promptMessage: 'Enter the timeout of the flushing of remaining messages in the buffer at the logger stop',
+                              title: 'Messages flush timeout',
+                              constraints:{min:0,max:2147483647,places:0},
+                              invalidMessage:'Please enter a messages flush timeout',
+                              rangeMessage:'Insert zero (infinity) or a positive integer'"/>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>GELF encoder options</legend>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Message origin host*:</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="text" id="addLogger.messageOriginHost"
+                           data-dojo-type="dijit/form/ValidationTextBox"
+                           data-dojo-props="
+                              name: 'messageOriginHost',
+                              required: true,
+                              placeHolder: 'hostname',
+                              promptMessage: 'Enter the origin host of the GELF log message',
+                              title: 'Enter the origin host of the GELF log message'"/>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include raw message</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.rawMessageIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'rawMessageIncluded',
+                                  required: false,
+                                  checked: false"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.rawMessageIncluded'],
+                                      label: 'If selected, the raw text of exception is included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include event marker</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.eventMarkerIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'eventMarkerIncluded',
+                                  required: false,
+                                  checked: true"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.eventMarkerIncluded'],
+                                      label: 'If selected, the event marker is included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include MDC properties</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.mdcPropertiesIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'mdcPropertiesIncluded',
+                                  required: false,
+                                  checked: true"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.mdcPropertiesIncluded'],
+                                      label: 'If selected, the MDC properties are included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include caller data</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.callerDataIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'callerDataIncluded',
+                                  required: false,
+                                  checked: false"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.callerDataIncluded'],
+                                      label: 'If selected, the caller data are included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include root exception data</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.rootExceptionDataIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'rootExceptionDataIncluded',
+                                  required: false,
+                                  checked: false"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.rootExceptionDataIncluded'],
+                                      label: 'If selected, the root exception data are included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+            <div class="clear">
+                <div class="formLabel-labelCell tableContainer-labelCell">Include log level name</div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <input type="checkbox" id="addLogger.logLevelNameIncluded"
+                           data-dojo-type="dijit/form/CheckBox"
+                           data-dojo-props="
+                                  name: 'logLevelNameIncluded',
+                                  required: false,
+                                  checked: false"/>
+                    <div data-dojo-type="dijit/Tooltip"
+                         data-dojo-props="connectId: ['addLogger.logLevelNameIncluded'],
+                                      label: 'If selected, the log level name is included in the GELF log message'">
+                    </div>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>GELF Log Message Static Fields</legend>
+            <div class="clear" id="addLogger.staticFields"
+                 data-dojo-type="qpid/common/MapInputWidget"
+                 data-dojo-props="
+                    name: 'staticFields',
+                    keyValueTemplate: 'logger/Graylog/showStaticField.html'">
+                <div class="formLabel-labelCell tableContainer-labelCell">
+                    <label for="addLogger.staticFields.key">Name (Key):</label><br>
+                    <input type="text" id="addLogger.staticFields.key"
+                           data-dojo-type="dijit/form/ValidationTextBox"
+                           data-dojo-props="
+                              name: 'staticFields.key',
+                              placeHolder: '',
+                              promptMessage: 'Enter the static field name',
+                              title: 'Name (Key)',
+                              regExp: '[\\w\\.\\-]+',
+                              invalidMessage: 'Name/key has to obey the schema: [\\w\\.\\-]+'"/>
+                </div>
+                <div class="formLabel-controlCell tableContainer-valueCell">
+                    <label for="addLogger.staticFields.value">Value:</label><br>
+                    <input type="text" id="addLogger.staticFields.value"
+                           data-dojo-type="dijit/form/ValidationTextBox"
+                           data-dojo-props="
+                              name: 'staticFields.value',
+                              placeHolder: '',
+                              promptMessage: 'Enter the static field value',
+                              title: 'Value'"/>
+                </div>
+                <div class="clear">
+                    <button data-dojo-type="dijit/form/Button" id="addLogger.staticFields.insertButton"
+                            data-dojo-props="label: 'Insert'" type="submit">Insert
+                    </button>
+                    <button data-dojo-type="dijit/form/Button" id="addLogger.staticFields.clearButton"
+                            data-dojo-props="label: 'Clear'" type="reset">Clear
+                    </button>
+                </div>
+                <div class="keyValueList clear mapList-scroll-y"></div>
+            </div>
+        </fieldset>
+    </div>
+</div>
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/show.html b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/show.html
new file mode 100644
index 0000000..6de2af0
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/show.html
@@ -0,0 +1,135 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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.
+  ~
+  -->
+<div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Connection Options</legend>
+            <div class="alignLeft">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Remote Host:</div>
+                    <div class="remoteHost formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Port:</div>
+                    <div class="port formValue-valueCell"></div>
+                </div>
+            </div>
+            <div class="alignRight">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Reconnection interval:</div>
+                    <div class="formValue-valueCell">
+                        <span class="reconnectionInterval"></span>
+                        <span>ms</span>
+                    </div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Connection Timeout:</div>
+                    <div class="formValue-valueCell">
+                        <span class="connectionTimeout"></span>
+                        <span>ms</span>
+                    </div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Maximum connection attempts:</div>
+                    <div class="maximumReconnectionAttempts formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Connection retry delay:</div>
+                    <div class="formValue-valueCell">
+                        <span class="retryDelay"></span>
+                        <span>ms</span>
+                    </div>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Message Buffer Options</legend>
+            <div class="alignLeft">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Capacity:</div>
+                    <div class="messageBufferCapacity formValue-valueCell"></div>
+                </div>
+            </div>
+            <div class="alignRight">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Current buffer usage:</div>
+                    <div class="appenderBufferUsage formValue-valueCell"></div>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>Logger Stopping</legend>
+            <div class="clear">
+                <div class="formLabel-labelCell">Messages flush time out:</div>
+                <div class="formValue-valueCell">
+                    <span class="messagesFlushTimeOut"></span>
+                    <span>ms</span>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>GELF Log Message Encoder Options</legend>
+            <div class="alignLeft">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Message origin host:</div>
+                    <div class="messageOriginHost formValue-valueCell"></div>
+                </div>
+            </div>
+            <div class="alignRight">
+                <div class="clear">
+                    <div class="formLabel-labelCell">Raw message included:</div>
+                    <div class="rawMessageIncluded formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Event marker included:</div>
+                    <div class="eventMarkerIncluded formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">MDC properties included:</div>
+                    <div class="mdcPropertiesIncluded formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Caller data included:</div>
+                    <div class="callerDataIncluded formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Root exception data included:</div>
+                    <div class="rootExceptionDataIncluded formValue-valueCell"></div>
+                </div>
+                <div class="clear">
+                    <div class="formLabel-labelCell">Log level name included:</div>
+                    <div class="logLevelNameIncluded formValue-valueCell"></div>
+                </div>
+            </div>
+        </fieldset>
+    </div>
+    <div class="formBox clear">
+        <fieldset>
+            <legend>GELF Log Message Static Fields</legend>
+            <div class="staticFields clear"></div>
+        </fieldset>
+    </div>
+</div>
diff --git a/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/showStaticField.html b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/showStaticField.html
new file mode 100644
index 0000000..1bbb8f7
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/main/java/resources/logger/graylog/showStaticField.html
@@ -0,0 +1,4 @@
+<div class="clear">
+    <div class="key formLabel-labelCell">:</div>
+    <div class="value formValue-valueCell"></div>
+</div>
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/CallerDataFilterTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/CallerDataFilterTest.java
new file mode 100644
index 0000000..b604b8c
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/CallerDataFilterTest.java
@@ -0,0 +1,167 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging;
+
+import org.apache.qpid.server.logging.LogMessage;
+import org.apache.qpid.server.logging.LogSubject;
+import org.apache.qpid.server.logging.MessageLogger;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class CallerDataFilterTest extends UnitTestBase
+{
+    public static class CallerDataTestLogger implements MessageLogger
+    {
+        private StackTraceElement[] _stackTraceElements;
+
+        @Override
+        public boolean isEnabled()
+        {
+            catchStackTrace();
+            return false;
+        }
+
+        @Override
+        public boolean isMessageEnabled(String logHierarchy)
+        {
+            catchStackTrace();
+            return false;
+        }
+
+        @Override
+        public void message(LogMessage message)
+        {
+            catchStackTrace();
+        }
+
+        @Override
+        public void message(LogSubject subject, LogMessage message)
+        {
+            catchStackTrace();
+        }
+
+        private void catchStackTrace()
+        {
+            _stackTraceElements = Thread.currentThread().getStackTrace();
+        }
+
+        public StackTraceElement[] getStackTrace()
+        {
+            return _stackTraceElements;
+        }
+    }
+
+    private CallerDataFilter _filter;
+    private CallerDataTestLogger _logger;
+
+    @Before
+    public void setUp()
+    {
+        _filter = new CallerDataFilter();
+        _logger = new CallerDataTestLogger();
+    }
+
+    @Test
+    public void testFilter_nullAsInput()
+    {
+        StackTraceElement[] result = _filter.filter(null);
+        assertNotNull(result);
+        assertEquals(0, result.length);
+    }
+
+    @Test
+    public void testFilter_emptyInput()
+    {
+        StackTraceElement[] result = _filter.filter(new StackTraceElement[0]);
+        assertNotNull(result);
+        assertEquals(0, result.length);
+    }
+
+    @Test
+    public void testFilter_withoutLogger()
+    {
+        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+        StackTraceElement[] result = _filter.filter(stackTrace);
+        assertNotNull(result);
+
+        assertTrue(Arrays.deepEquals(stackTrace, result));
+    }
+
+    @Test
+    public void testFilter_withLogger()
+    {
+        _logger.message(() -> "ClassName");
+        StackTraceElement[] result = _filter.filter(_logger.getStackTrace());
+        assertNotNull(result);
+
+        final String loggerName = _logger.getClass().getName();
+        assertFalse(Arrays.stream(result).anyMatch(e -> e.getClassName().contains(loggerName)));
+    }
+
+    @Test
+    public void testFilter_withLogger_InvalidMethod()
+    {
+        _logger.isEnabled();
+        StackTraceElement[] stackTrace = _logger.getStackTrace();
+        StackTraceElement[] result = _filter.filter(_logger.getStackTrace());
+        assertNotNull(result);
+
+        assertTrue(Arrays.deepEquals(stackTrace, result));
+    }
+
+    @Test
+    public void testFilter_withLoggerOnly()
+    {
+        _logger.message(() -> "ClassName");
+        final String loggerName = _logger.getClass().getName();
+        StackTraceElement[] stackTrace = Arrays.stream(_logger.getStackTrace())
+                .filter(e -> e.getClassName().contains(loggerName))
+                .toArray(StackTraceElement[]::new);
+
+        StackTraceElement[] result = _filter.filter(stackTrace);
+        assertNotNull(result);
+
+        assertTrue(Arrays.deepEquals(stackTrace, result));
+    }
+
+    @Test
+    public void testFilter_withUnknownClass()
+    {
+        StackTraceElement element1 = new StackTraceElement("unknown_class_xyz", "message", "file", 7);
+        StackTraceElement element2 = new StackTraceElement("unknown_class_xyz", "message", "file", 17);
+
+        final StackTraceElement[] stackTrace = {element1, element2};
+        StackTraceElement[] result = _filter.filter(stackTrace);
+        assertNotNull(result);
+
+        assertTrue(Arrays.deepEquals(stackTrace, result));
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/GraylogAppenderTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/GraylogAppenderTest.java
new file mode 100644
index 0000000..97b6e46
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/GraylogAppenderTest.java
@@ -0,0 +1,932 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.Context;
+import de.siegmar.logbackgelf.GelfEncoder;
+import de.siegmar.logbackgelf.GelfTcpAppender;
+import org.apache.qpid.server.logging.logback.event.TestLoggingEvent;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class GraylogAppenderTest extends UnitTestBase
+{
+    static class TestGelfAppenderConfiguration implements GelfAppenderConfiguration
+    {
+        private String _remoteHost = "localhost";
+
+        private int _port = 12201;
+
+        private int _reconnectionInterval = 10000;
+
+        private int _connectionTimeout = 300;
+
+        private int _maximumReconnectionAttempts = 1;
+
+        private int _retryDelay = 500;
+
+        private int _messagesFlushTimeOut = 10000;
+
+        private int _messageBufferCapacity = 10000;
+
+        private String _messageOriginHost = "BrokerJ";
+
+        private boolean _rawMessageIncluded = false;
+
+        private boolean _eventMarkerIncluded = false;
+
+        private boolean _mdcPropertiesIncluded = false;
+
+        private boolean _callerDataIncluded = false;
+
+        private boolean _rootExceptionDataIncluded = false;
+
+        private boolean _logLevelNameIncluded = false;
+
+        private final Map<String, Object> _staticFields = new LinkedHashMap<>();
+
+        public TestGelfAppenderConfiguration()
+        {
+            super();
+        }
+
+        @Override
+        public String getRemoteHost()
+        {
+            return _remoteHost;
+        }
+
+        public TestGelfAppenderConfiguration withRemoteHost(String remoteHost)
+        {
+            this._remoteHost = remoteHost;
+            return this;
+        }
+
+        @Override
+        public int getPort()
+        {
+            return _port;
+        }
+
+        public TestGelfAppenderConfiguration withPort(int port)
+        {
+            this._port = port;
+            return this;
+        }
+
+        @Override
+        public int getReconnectionInterval()
+        {
+            return _reconnectionInterval;
+        }
+
+        public TestGelfAppenderConfiguration withReconnectionInterval(int reconnectionInterval)
+        {
+            this._reconnectionInterval = reconnectionInterval;
+            return this;
+        }
+
+        @Override
+        public int getConnectionTimeout()
+        {
+            return _connectionTimeout;
+        }
+
+        public TestGelfAppenderConfiguration withConnectionTimeout(int connectionTimeout)
+        {
+            this._connectionTimeout = connectionTimeout;
+            return this;
+        }
+
+        @Override
+        public int getMaximumReconnectionAttempts()
+        {
+            return _maximumReconnectionAttempts;
+        }
+
+        public TestGelfAppenderConfiguration withMaximumReconnectionAttempts(int maximumReconnectionAttempts)
+        {
+            this._maximumReconnectionAttempts = maximumReconnectionAttempts;
+            return this;
+        }
+
+        @Override
+        public int getRetryDelay()
+        {
+            return _retryDelay;
+        }
+
+        public TestGelfAppenderConfiguration withRetryDelay(int retryDelay)
+        {
+            this._retryDelay = retryDelay;
+            return this;
+        }
+
+        @Override
+        public int getMessagesFlushTimeOut()
+        {
+            return _messagesFlushTimeOut;
+        }
+
+        public TestGelfAppenderConfiguration withMessagesFlushTimeOut(int messagesFlushTimeOut)
+        {
+            this._messagesFlushTimeOut = messagesFlushTimeOut;
+            return this;
+        }
+
+        @Override
+        public int getMessageBufferCapacity()
+        {
+            return _messageBufferCapacity;
+        }
+
+        public TestGelfAppenderConfiguration withMessageBufferCapacity(int messageBufferCapacity)
+        {
+            this._messageBufferCapacity = messageBufferCapacity;
+            return this;
+        }
+
+        @Override
+        public String getMessageOriginHost()
+        {
+            return _messageOriginHost;
+        }
+
+        public TestGelfAppenderConfiguration withMessageOriginHost(String messageOriginHost)
+        {
+            this._messageOriginHost = messageOriginHost;
+            return this;
+        }
+
+        @Override
+        public boolean isRawMessageIncluded()
+        {
+            return _rawMessageIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withRawMessageIncluded(boolean rawMessageIncluded)
+        {
+            this._rawMessageIncluded = rawMessageIncluded;
+            return this;
+        }
+
+        @Override
+        public boolean isEventMarkerIncluded()
+        {
+            return _eventMarkerIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withEventMarkerIncluded(boolean eventMarkerIncluded)
+        {
+            this._eventMarkerIncluded = eventMarkerIncluded;
+            return this;
+        }
+
+        @Override
+        public boolean hasMdcPropertiesIncluded()
+        {
+            return _mdcPropertiesIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withMdcPropertiesIncluded(boolean mdcPropertiesIncluded)
+        {
+            this._mdcPropertiesIncluded = mdcPropertiesIncluded;
+            return this;
+        }
+
+        @Override
+        public boolean isCallerDataIncluded()
+        {
+            return _callerDataIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withCallerDataIncluded(boolean callerDataIncluded)
+        {
+            this._callerDataIncluded = callerDataIncluded;
+            return this;
+        }
+
+        @Override
+        public boolean hasRootExceptionDataIncluded()
+        {
+            return _rootExceptionDataIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withRootExceptionDataIncluded(boolean rootExceptionDataIncluded)
+        {
+            this._rootExceptionDataIncluded = rootExceptionDataIncluded;
+            return this;
+        }
+
+        @Override
+        public boolean isLogLevelNameIncluded()
+        {
+            return _logLevelNameIncluded;
+        }
+
+        public TestGelfAppenderConfiguration withLogLevelNameIncluded(boolean logLevelNameIncluded)
+        {
+            this._logLevelNameIncluded = logLevelNameIncluded;
+            return this;
+        }
+
+        @Override
+        public Map<String, Object> getStaticFields()
+        {
+            return _staticFields;
+        }
+
+        public TestGelfAppenderConfiguration addStaticFields(Map<String, ?> map)
+        {
+            this._staticFields.putAll(map);
+            return this;
+        }
+
+        public GraylogAppender newAppender(Context context)
+        {
+            return GraylogAppender.newInstance(context, this);
+        }
+    }
+
+    static class DefaultGelfAppenderConfiguration implements GelfAppenderConfiguration
+    {
+        @Override
+        public String getRemoteHost()
+        {
+            return "localhost";
+        }
+
+        @Override
+        public String getMessageOriginHost()
+        {
+            return "BrokerJ";
+        }
+
+        public GraylogAppender newAppender(Context context)
+        {
+            return GraylogAppender.newInstance(context, this);
+        }
+    }
+
+    @Test
+    public void testNewInstance()
+    {
+        TestGelfAppenderConfiguration logger = new TestGelfAppenderConfiguration();
+        Context context = new LoggerContext();
+        GraylogAppender appender = GraylogAppender.newInstance(context, logger);
+        assertNotNull(appender);
+    }
+
+    @Test
+    public void testStart()
+    {
+        TestGelfAppenderConfiguration logger = new TestGelfAppenderConfiguration();
+        Context context = new LoggerContext();
+        GraylogAppender appender = logger.newAppender(context);
+        appender.setName("GelfAppender");
+        appender.start();
+
+        assertTrue(appender.isStarted());
+        assertEquals("GelfAppender", appender.getName());
+
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        Appender<ILoggingEvent> app = null;
+        while (iterator.hasNext())
+        {
+            app = iterator.next();
+            assertNotNull(app);
+            assertTrue(app.isStarted());
+            assertEquals("GelfAppender", app.getName());
+            assertEquals(context, app.getContext());
+            assertTrue(app instanceof GelfTcpAppender);
+        }
+        assertNotNull(app);
+    }
+
+    @Test
+    public void testStart_again()
+    {
+        TestGelfAppenderConfiguration logger = new TestGelfAppenderConfiguration();
+        Context context = new LoggerContext();
+        GraylogAppender appender = logger.newAppender(context);
+        appender.setName("GelfAppender");
+        appender.start();
+        assertTrue(appender.isStarted());
+
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        List<Appender<ILoggingEvent>> appenders = new ArrayList<>();
+        while (iterator.hasNext())
+        {
+            appenders.add(iterator.next());
+        }
+        assertFalse(appenders.isEmpty());
+
+        appender.start();
+        assertTrue(appender.isStarted());
+
+        iterator = appender.iteratorForAppenders();
+        List<Appender<ILoggingEvent>> newAppenders = new ArrayList<>();
+        while (iterator.hasNext())
+        {
+            newAppenders.add(iterator.next());
+        }
+        assertFalse(newAppenders.isEmpty());
+        assertTrue(newAppenders.containsAll(appenders));
+        assertTrue(appenders.containsAll(newAppenders));
+    }
+
+    @Test
+    public void testSetName()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration();
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.setName("GelfAppender");
+        appender.start();
+
+        appender.setName("NewGelfAppender");
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        while (iterator.hasNext())
+        {
+            Appender<ILoggingEvent> app = iterator.next();
+            assertNotNull(app);
+            assertEquals("NewGelfAppender", app.getName());
+        }
+    }
+
+    @Test
+    public void testSetContext()
+    {
+        TestGelfAppenderConfiguration logger = new TestGelfAppenderConfiguration();
+        Context context = new LoggerContext();
+        GraylogAppender appender = logger.newAppender(context);
+        appender.setName("GelfAppender");
+        appender.start();
+
+        appender.setContext(context);
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        while (iterator.hasNext())
+        {
+            Appender<ILoggingEvent> app = iterator.next();
+            assertNotNull(app);
+            assertEquals(context, app.getContext());
+        }
+    }
+
+    @Test
+    public void testRemoteHost()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRemoteHost("Remote");
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals("Remote", gelfAppender.getGraylogHost());
+    }
+
+    @Test
+    public void testRemoteHost_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals("localhost", gelfAppender.getGraylogHost());
+    }
+
+    @Test
+    public void testPort()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withPort(42456);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(42456, gelfAppender.getGraylogPort());
+    }
+
+    @Test
+    public void testPort_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(12201, gelfAppender.getGraylogPort());
+    }
+
+    @Test
+    public void testReconnectInterval_withRounding()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withReconnectionInterval(11456);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(12, gelfAppender.getReconnectInterval());
+    }
+
+    @Test
+    public void testReconnectInterval_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(60, gelfAppender.getReconnectInterval());
+    }
+
+    @Test
+    public void testReconnectInterval()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withReconnectionInterval(11000);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(11, gelfAppender.getReconnectInterval());
+    }
+
+    @Test
+    public void testConnectTimeout()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withConnectionTimeout(1123);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(1123, gelfAppender.getConnectTimeout());
+    }
+
+    @Test
+    public void testConnectTimeout_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(15000, gelfAppender.getConnectTimeout());
+    }
+
+    @Test
+    public void testMaximumReconnectionAttempts()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMaximumReconnectionAttempts(17);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(17, gelfAppender.getMaxRetries());
+    }
+
+    @Test
+    public void testMaximumReconnectionAttempts_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(2, gelfAppender.getMaxRetries());
+    }
+
+    @Test
+    public void testRetryDelay()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRetryDelay(178);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(178, gelfAppender.getRetryDelay());
+    }
+
+    @Test
+    public void testRetryDelay_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfTcpAppender gelfAppender = extractGelfAppender(appender);
+        assertNotNull(gelfAppender);
+        assertEquals(3000, gelfAppender.getRetryDelay());
+    }
+
+    @Test
+    public void testMessageOriginHost()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMessageOriginHost("Broker");
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertEquals("Broker", gelfEncoder.getOriginHost());
+    }
+
+    @Test
+    public void testRawMessageIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRawMessageIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeRawMessage());
+    }
+
+    @Test
+    public void testRawMessageIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRawMessageIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeRawMessage());
+    }
+
+    @Test
+    public void testRawMessageIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeRawMessage());
+    }
+
+    @Test
+    public void testEventMarkerIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withEventMarkerIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeMarker());
+    }
+
+    @Test
+    public void testEventMarkerIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withEventMarkerIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeMarker());
+    }
+
+    @Test
+    public void testEventMarkerIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeMarker());
+    }
+
+    @Test
+    public void testMdcPropertiesIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMdcPropertiesIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeMdcData());
+    }
+
+    @Test
+    public void testMdcPropertiesIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMdcPropertiesIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeMdcData());
+    }
+
+    @Test
+    public void testMdcPropertiesIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeMdcData());
+    }
+
+    @Test
+    public void testCallerDataIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withCallerDataIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeCallerData());
+    }
+
+    @Test
+    public void testCallerDataIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withCallerDataIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeCallerData());
+    }
+
+    @Test
+    public void testCallerDataIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeCallerData());
+    }
+
+    @Test
+    public void testRootExceptionDataIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRootExceptionDataIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeRootCauseData());
+    }
+
+    @Test
+    public void testRootExceptionDataIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withRootExceptionDataIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeRootCauseData());
+    }
+
+    @Test
+    public void testRootExceptionDataIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeRootCauseData());
+    }
+
+    @Test
+    public void testLogLevelNameIncluded_True()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withLogLevelNameIncluded(true);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertTrue(gelfEncoder.isIncludeLevelName());
+    }
+
+    @Test
+    public void testLogLevelNameIncluded_False()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withLogLevelNameIncluded(false);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeLevelName());
+    }
+
+    @Test
+    public void testLogLevelNameIncluded_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        assertFalse(gelfEncoder.isIncludeLevelName());
+    }
+
+    @Test
+    public void testStaticFields()
+    {
+        Map<String, Object> staticFields = new HashMap<>(2);
+        staticFields.put("A", "A.A");
+        staticFields.put("B", 234);
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().addStaticFields(staticFields);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        Map<String, Object> fields = gelfEncoder.getStaticFields();
+        assertNotNull(fields);
+        assertEquals(2, fields.size());
+        assertEquals("A.A", fields.get("A"));
+        assertEquals(234, fields.get("B"));
+    }
+
+    @Test
+    public void testStaticFields_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        GelfEncoder gelfEncoder = extractGelfEncoder(appender);
+        assertNotNull(gelfEncoder);
+        Map<String, Object> fields = gelfEncoder.getStaticFields();
+        assertNotNull(fields);
+        assertTrue(fields.isEmpty());
+    }
+
+    @Test
+    public void testMessageBufferCapacity()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMessageBufferCapacity(789);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        assertEquals(789, appender.getQueueSize());
+    }
+
+    @Test
+    public void testMessageBufferCapacity_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        assertEquals(256, appender.getQueueSize());
+    }
+
+    @Test
+    public void testMessagesFlushTimeOut()
+    {
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration().withMessagesFlushTimeOut(567);
+        Context context = new LoggerContext();
+        GraylogAppender appender = configuration.newAppender(context);
+        appender.start();
+
+        assertEquals(567, appender.getMaxFlushTime());
+    }
+
+    @Test
+    public void testMessagesFlushTimeOut_Default()
+    {
+        Context context = new LoggerContext();
+        GraylogAppender appender = new DefaultGelfAppenderConfiguration().newAppender(context);
+        appender.start();
+
+        assertEquals(1000, appender.getMaxFlushTime());
+    }
+
+    @Test
+    public void testDoAppend()
+    {
+        Context context = new LoggerContext();
+        TestGelfAppenderConfiguration configuration = new TestGelfAppenderConfiguration();
+        GraylogAppender appender = configuration.newAppender(context);
+        try
+        {
+            appender.doAppend(new TestLoggingEvent());
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    private GelfTcpAppender extractGelfAppender(GraylogAppender appender)
+    {
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        while ((iterator.hasNext()))
+        {
+            Appender<ILoggingEvent> app = iterator.next();
+            if (app instanceof GelfTcpAppender)
+            {
+                return (GelfTcpAppender) app;
+            }
+        }
+        return null;
+    }
+
+    private GelfEncoder extractGelfEncoder(GraylogAppender appender)
+    {
+        Iterator<Appender<ILoggingEvent>> iterator = appender.iteratorForAppenders();
+        while (iterator.hasNext())
+        {
+            Appender<ILoggingEvent> app = iterator.next();
+            if (app instanceof GelfTcpAppender)
+            {
+                return ((GelfTcpAppender) app).getEncoder();
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/LoggingEventTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/LoggingEventTest.java
new file mode 100644
index 0000000..c25bf5e
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/LoggingEventTest.java
@@ -0,0 +1,298 @@
+package org.apache.qpid.server.logging.logback.event;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import org.apache.qpid.server.logging.LogMessage;
+import org.apache.qpid.server.logging.LogSubject;
+import org.apache.qpid.server.logging.MessageLogger;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class LoggingEventTest extends UnitTestBase
+{
+    public static class LocalTestLogger implements MessageLogger
+    {
+        private StackTraceElement[] _stackTrace = null;
+
+        @Override
+        public boolean isEnabled()
+        {
+            fillStackTrace();
+            return false;
+        }
+
+        @Override
+        public boolean isMessageEnabled(String logHierarchy)
+        {
+            fillStackTrace();
+            return false;
+        }
+
+        @Override
+        public void message(LogMessage message)
+        {
+            fillStackTrace();
+        }
+
+        @Override
+        public void message(LogSubject subject, LogMessage message)
+        {
+            fillStackTrace();
+        }
+
+        private void fillStackTrace()
+        {
+            _stackTrace = Thread.currentThread().getStackTrace();
+        }
+
+        public TestLoggingEvent event()
+        {
+            return new TestLoggingEvent().withCallerData(_stackTrace);
+        }
+    }
+
+    @Test
+    public void testWrap_NullAsInput()
+    {
+        assertNull(LoggingEvent.wrap(null));
+    }
+
+    @Test
+    public void testWrap()
+    {
+        assertNotNull(LoggingEvent.wrap(new TestLoggingEvent()));
+    }
+
+    @Test
+    public void testGetThreadName()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getThreadName(), wrapper.getThreadName());
+    }
+
+    @Test
+    public void testGetLevel()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getLevel(), wrapper.getLevel());
+    }
+
+    @Test
+    public void testGetMessage()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getMessage(), wrapper.getMessage());
+    }
+
+    @Test
+    public void testGetArgumentArray()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertTrue(Arrays.deepEquals(event.getArgumentArray(), wrapper.getArgumentArray()));
+    }
+
+    @Test
+    public void testGetFormattedMessage()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getFormattedMessage(), wrapper.getFormattedMessage());
+    }
+
+    @Test
+    public void testGetLoggerName()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getLoggerName(), wrapper.getLoggerName());
+    }
+
+    @Test
+    public void testGetLoggerContextVO()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getLoggerContextVO(), wrapper.getLoggerContextVO());
+    }
+
+    @Test
+    public void testGetThrowableProxy()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getThrowableProxy(), wrapper.getThrowableProxy());
+    }
+
+    @Test
+    public void testGetCallerData_NullAsInput()
+    {
+        TestLoggingEvent event = new TestLoggingEvent().withCallerData(null);
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        StackTraceElement[] data = wrapper.getCallerData();
+        assertNotNull(data);
+        assertEquals(0, data.length);
+
+        data = wrapper.getCallerData();
+        assertNotNull(data);
+        assertEquals(0, data.length);
+    }
+
+    @Test
+    public void testGetCallerData_EmptyInput()
+    {
+        TestLoggingEvent event = new TestLoggingEvent().withCallerData(new StackTraceElement[0]);
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        StackTraceElement[] callerData = wrapper.getCallerData();
+        assertNotNull(callerData);
+        assertEquals(0, callerData.length);
+
+        callerData = wrapper.getCallerData();
+        assertNotNull(callerData);
+        assertEquals(0, callerData.length);
+    }
+
+    @Test
+    public void testGetCallerData_AllData()
+    {
+        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+        TestLoggingEvent event = new TestLoggingEvent().withCallerData(stackTrace);
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        StackTraceElement[] callerData = wrapper.getCallerData();
+        assertNotNull(callerData);
+        assertTrue(Arrays.deepEquals(stackTrace, callerData));
+
+        callerData = wrapper.getCallerData();
+        assertNotNull(callerData);
+        assertTrue(Arrays.deepEquals(stackTrace, callerData));
+    }
+
+    @Test
+    public void testGetCallerData_FilteredData()
+    {
+        LocalTestLogger logger = new LocalTestLogger();
+        logger.message(() -> "Class");
+
+        ILoggingEvent wrapper = LoggingEvent.wrap(logger.event());
+        assertNotNull(wrapper);
+
+        StackTraceElement[] callerData = wrapper.getCallerData();
+        assertNotNull(callerData);
+
+        final String name = LocalTestLogger.class.getName();
+        assertFalse(Arrays.stream(callerData).anyMatch(e -> e.getClassName().contains(name)));
+    }
+
+    @Test
+    public void testHasCallerData_NegativeResult()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        event.withCallerData(null);
+        assertFalse(wrapper.hasCallerData());
+
+        event.withCallerData(new StackTraceElement[0]);
+        assertFalse(wrapper.hasCallerData());
+    }
+
+    @Test
+    public void testHasCallerData_PositiveResult()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        event.withCallerData(Thread.currentThread().getStackTrace());
+        assertTrue(wrapper.hasCallerData());
+    }
+
+    @Test
+    public void testGetMarker()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getMarker(), wrapper.getMarker());
+    }
+
+    @Test
+    public void tesGetMDCPropertyMap()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        Map<String, String> originalMap = event.getMDCPropertyMap();
+        Map<String, String> map = wrapper.getMDCPropertyMap();
+
+        assertEquals(originalMap.keySet(), map.keySet());
+        for (Map.Entry<String, String> entry : originalMap.entrySet())
+        {
+            assertEquals(entry.getValue(), map.get(entry.getKey()));
+        }
+    }
+
+    @Test
+    public void testGetMdc()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+
+        Map<String, String> originalMap = event.getMdc();
+        Map<String, String> map = wrapper.getMdc();
+
+        assertEquals(originalMap.keySet(), map.keySet());
+        for (Map.Entry<String, String> entry : originalMap.entrySet())
+        {
+            assertEquals(entry.getValue(), map.get(entry.getKey()));
+        }
+    }
+
+    @Test
+    public void testGetTimeStamp()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertEquals(event.getTimeStamp(), wrapper.getTimeStamp());
+    }
+
+    @Test
+    public void testPrepareForDeferredProcessing()
+    {
+        TestLoggingEvent event = new TestLoggingEvent();
+        ILoggingEvent wrapper = LoggingEvent.wrap(event);
+        assertNotNull(wrapper);
+        assertFalse(event.isPreparedForDeferredProcessing());
+        wrapper.prepareForDeferredProcessing();
+        assertTrue(event.isPreparedForDeferredProcessing());
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/TestLoggingEvent.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/TestLoggingEvent.java
new file mode 100644
index 0000000..f85a149
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/event/TestLoggingEvent.java
@@ -0,0 +1,224 @@
+package org.apache.qpid.server.logging.logback.event;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
+import org.slf4j.Marker;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+public class TestLoggingEvent implements ILoggingEvent
+{
+    private final String _threadName = Thread.currentThread().getName();
+
+    private final Object[] _argumentArray = {"A", "B"};
+
+    private final LoggerContextVO _loggerContext = new LoggerContextVO(new LoggerContext());
+
+    private final long _timeStamp = System.currentTimeMillis();
+
+    private final Marker _marker = new Marker()
+    {
+        @Override
+        public String getName()
+        {
+            return Marker.ANY_MARKER;
+        }
+
+        @Override
+        public void add(Marker marker)
+        {
+        }
+
+        @Override
+        public boolean remove(Marker marker)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean hasChildren()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean hasReferences()
+        {
+            return false;
+        }
+
+        @Override
+        public Iterator<Marker> iterator()
+        {
+            return Collections.emptyIterator();
+        }
+
+        @Override
+        public boolean contains(Marker marker)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean contains(String s)
+        {
+            return false;
+        }
+    };
+
+    private final Map<String, String> _mdcMap = Collections.singletonMap("A", "B");
+
+    private boolean preparedForDeferredProcessing = false;
+
+    private final IThrowableProxy _throwableProxy = new IThrowableProxy()
+    {
+        @Override
+        public String getMessage()
+        {
+            return "message";
+        }
+
+        @Override
+        public String getClassName()
+        {
+            return "className";
+        }
+
+        @Override
+        public StackTraceElementProxy[] getStackTraceElementProxyArray()
+        {
+            return new StackTraceElementProxy[0];
+        }
+
+        @Override
+        public int getCommonFrames()
+        {
+            return 0;
+        }
+
+        @Override
+        public IThrowableProxy getCause()
+        {
+            return null;
+        }
+
+        @Override
+        public IThrowableProxy[] getSuppressed()
+        {
+            return new IThrowableProxy[0];
+        }
+    };
+
+    private StackTraceElement[] _callerData;
+
+    @Override
+    public String getThreadName()
+    {
+        return _threadName;
+    }
+
+    @Override
+    public Level getLevel()
+    {
+        return Level.INFO;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return "Error message";
+    }
+
+    @Override
+    public Object[] getArgumentArray()
+    {
+        return _argumentArray;
+    }
+
+    @Override
+    public String getFormattedMessage()
+    {
+        return "Formatted message";
+    }
+
+    @Override
+    public String getLoggerName()
+    {
+        return "Logger";
+    }
+
+    @Override
+    public LoggerContextVO getLoggerContextVO()
+    {
+        return _loggerContext;
+    }
+
+    @Override
+    public IThrowableProxy getThrowableProxy()
+    {
+        return _throwableProxy;
+    }
+
+    @Override
+    public StackTraceElement[] getCallerData()
+    {
+        return _callerData;
+    }
+
+    @Override
+    public boolean hasCallerData()
+    {
+        return _callerData != null;
+    }
+
+    @Override
+    public Marker getMarker()
+    {
+        return _marker;
+    }
+
+    @Override
+    public Map<String, String> getMDCPropertyMap()
+    {
+        return _mdcMap;
+    }
+
+    /**
+     * @deprecated getMDCPropertyMap method should be used instead.
+     */
+    @Deprecated
+    @Override
+    public Map<String, String> getMdc()
+    {
+        return _mdcMap;
+    }
+
+    @Override
+    public long getTimeStamp()
+    {
+        return _timeStamp;
+    }
+
+    @Override
+    public void prepareForDeferredProcessing()
+    {
+        preparedForDeferredProcessing = true;
+    }
+
+    public boolean isPreparedForDeferredProcessing()
+    {
+        return preparedForDeferredProcessing;
+    }
+
+    public TestLoggingEvent withCallerData(StackTraceElement[] data)
+    {
+        _callerData = data;
+        return this;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastOneTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastOneTest.java
new file mode 100644
index 0000000..839a067
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastOneTest.java
@@ -0,0 +1,93 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class AtLeastOneTest extends UnitTestBase
+{
+    @Test
+    public void validator()
+    {
+        assertNotNull("Factory method has to produce a instance", AtLeastOne.validator());
+    }
+
+    @Test
+    public void testValidate_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastOne.validateValue(null, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value 'null' as it has to be at least 1", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastOne.validateValue(2, object, "attr");
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastOne.validateValue(0, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '0' as it has to be at least 1", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastTest.java
new file mode 100644
index 0000000..7cea738
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastTest.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class AtLeastTest extends UnitTestBase
+{
+    @Test
+    public void testValidate_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        Validator<Integer> validator = new AtLeast(42);
+        try
+        {
+            validator.validate(null, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value 'null' as it has to be at least 42", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        Validator<Integer> validator = new AtLeast(42);
+        try
+        {
+            validator.validate(42, object, "attr");
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        Validator<Integer> validator = new AtLeast(42);
+        try
+        {
+            validator.validate(41, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '41' as it has to be at least 42", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastZeroTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastZeroTest.java
new file mode 100644
index 0000000..80c115c
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/AtLeastZeroTest.java
@@ -0,0 +1,93 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class AtLeastZeroTest extends UnitTestBase
+{
+    @Test
+    public void validator()
+    {
+        assertNotNull("Factory method has to produce a instance", AtLeastZero.validator());
+    }
+
+    @Test
+    public void testValidate_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastZero.validateValue(null, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value 'null' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastZero.validateValue(2, object, "attr");
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            AtLeastZero.validateValue(-1, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-1' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidatorTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidatorTest.java
new file mode 100644
index 0000000..4d62581
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfConfigurationValidatorTest.java
@@ -0,0 +1,788 @@
+package org.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.logging.logback.GelfAppenderConfiguration;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class GelfConfigurationValidatorTest extends UnitTestBase
+{
+    private static class TestLogger implements GelfAppenderConfiguration
+    {
+        private int _port = 12201;
+
+        private int _reconnectionInterval = 10000;
+
+        private int _connectionTimeout = 300;
+
+        private int _maximumReconnectionAttempts = 1;
+
+        private int _retryDelay = 500;
+
+        private int _messagesFlushTimeOut = 10000;
+
+        private int _messageBufferCapacity = 10000;
+
+        private final Map<String, Object> _staticFields = new LinkedHashMap<>();
+
+        @Override
+        public String getRemoteHost()
+        {
+            return "localhost";
+        }
+
+        @Override
+        public int getPort()
+        {
+            return _port;
+        }
+
+        public TestLogger withPort(int port)
+        {
+            this._port = port;
+            return this;
+        }
+
+        @Override
+        public int getReconnectionInterval()
+        {
+            return _reconnectionInterval;
+        }
+
+        public TestLogger withReconnectionInterval(int reconnectionInterval)
+        {
+            this._reconnectionInterval = reconnectionInterval;
+            return this;
+        }
+
+        @Override
+        public int getConnectionTimeout()
+        {
+            return _connectionTimeout;
+        }
+
+        public TestLogger withConnectionTimeout(int connectionTimeout)
+        {
+            this._connectionTimeout = connectionTimeout;
+            return this;
+        }
+
+        @Override
+        public int getMaximumReconnectionAttempts()
+        {
+            return _maximumReconnectionAttempts;
+        }
+
+        public TestLogger withMaximumReconnectionAttempts(int maximumReconnectionAttempts)
+        {
+            this._maximumReconnectionAttempts = maximumReconnectionAttempts;
+            return this;
+        }
+
+        @Override
+        public int getRetryDelay()
+        {
+            return _retryDelay;
+        }
+
+        public TestLogger withRetryDelay(int retryDelay)
+        {
+            this._retryDelay = retryDelay;
+            return this;
+        }
+
+        @Override
+        public int getMessagesFlushTimeOut()
+        {
+            return _messagesFlushTimeOut;
+        }
+
+        public TestLogger withMessagesFlushTimeOut(int messagesFlushTimeOut)
+        {
+            this._messagesFlushTimeOut = messagesFlushTimeOut;
+            return this;
+        }
+
+        @Override
+        public int getMessageBufferCapacity()
+        {
+            return _messageBufferCapacity;
+        }
+
+        public TestLogger withMessageBufferCapacity(int messageBufferCapacity)
+        {
+            this._messageBufferCapacity = messageBufferCapacity;
+            return this;
+        }
+
+        @Override
+        public String getMessageOriginHost()
+        {
+            return "BrokerJ";
+        }
+
+        @Override
+        public boolean isRawMessageIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean isEventMarkerIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean hasMdcPropertiesIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean isCallerDataIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean hasRootExceptionDataIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean isLogLevelNameIncluded()
+        {
+            return true;
+        }
+
+        @Override
+        public Map<String, Object> getStaticFields()
+        {
+            return _staticFields;
+        }
+
+        public TestLogger addStaticFields(Map<String, ?> map)
+        {
+            this._staticFields.putAll(map);
+            return this;
+        }
+    }
+
+    @Test
+    public void testValidate_Port_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withPort(4567), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withPort(4567), object, Collections.singleton(GelfConfigurationValidator.PORT.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.PORT.validate(logger.withPort(4567), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_Port_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withPort(456789), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'port' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '456789' as it has to be in range [1, 65535]", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.PORT.validate(logger.withPort(456789), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'port' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '456789' as it has to be in range [1, 65535]", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withPort(456789), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ReconnectionInterval_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withReconnectionInterval(500), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withReconnectionInterval(500), object, Collections.singleton(GelfConfigurationValidator.RECONNECTION_INTERVAL.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.RECONNECTION_INTERVAL.validate(logger.withReconnectionInterval(500), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ReconnectionInterval_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withReconnectionInterval(-1), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'reconnectionInterval' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-1' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.RECONNECTION_INTERVAL.validate(logger.withReconnectionInterval(-1), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'reconnectionInterval' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-1' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withReconnectionInterval(-1), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ConnectionTimeout_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withConnectionTimeout(500), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withConnectionTimeout(500), object, Collections.singleton(GelfConfigurationValidator.CONNECTION_TIMEOUT.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.CONNECTION_TIMEOUT.validate(logger.withConnectionTimeout(500), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ConnectionTimeout_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withConnectionTimeout(-1), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'connectionTimeout' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-1' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.CONNECTION_TIMEOUT.validate(logger.withConnectionTimeout(-1), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'connectionTimeout' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-1' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withConnectionTimeout(-1), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_MaximumReconnectionAttempts_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMaximumReconnectionAttempts(5), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMaximumReconnectionAttempts(5), object, Collections.singleton(GelfConfigurationValidator.MAXIMUM_RECONNECTION_ATTEMPTS.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.MAXIMUM_RECONNECTION_ATTEMPTS.validate(logger.withMaximumReconnectionAttempts(5), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+    }
+
+    @Test
+    public void testValidate_MaximumReconnectionAttempts_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMaximumReconnectionAttempts(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'maximumReconnectionAttempts' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.MAXIMUM_RECONNECTION_ATTEMPTS.validate(logger.withMaximumReconnectionAttempts(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'maximumReconnectionAttempts' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMaximumReconnectionAttempts(-1), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_RetryDelay_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withRetryDelay(50), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withRetryDelay(50), object, Collections.singleton(GelfConfigurationValidator.RETRY_DELAY.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.RETRY_DELAY.validate(logger.withRetryDelay(50), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_RetryDelay_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withRetryDelay(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'retryDelay' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.RETRY_DELAY.validate(logger.withRetryDelay(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'retryDelay' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withRetryDelay(-5), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_MessagesFlushTimeOut_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessagesFlushTimeOut(50), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessagesFlushTimeOut(50), object, Collections.singleton(GelfConfigurationValidator.FLUSH_TIME_OUT.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.FLUSH_TIME_OUT.validate(logger.withMessagesFlushTimeOut(50), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_MessagesFlushTimeOut_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessagesFlushTimeOut(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'messagesFlushTimeOut' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.FLUSH_TIME_OUT.validate(logger.withMessagesFlushTimeOut(-5), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'messagesFlushTimeOut' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '-5' as it has to be at least 0", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessagesFlushTimeOut(-5), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_MessageBufferCapacity_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessageBufferCapacity(250), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessageBufferCapacity(250), object, Collections.singleton(GelfConfigurationValidator.BUFFER_CAPACITY.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.BUFFER_CAPACITY.validate(logger.withMessageBufferCapacity(250), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_MessageBufferCapacity_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessageBufferCapacity(0), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'messageBufferCapacity' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '0' as it has to be at least 1", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.BUFFER_CAPACITY.validate(logger.withMessageBufferCapacity(0), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'messageBufferCapacity' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '0' as it has to be at least 1", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.withMessageBufferCapacity(0), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_StaticFields_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.addStaticFields(Collections.singletonMap("A", 345)), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.addStaticFields(Collections.singletonMap("A", 345)), object, Collections.singleton(GelfConfigurationValidator.STATIC_FIELDS.attributeName()));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.STATIC_FIELDS.validate(logger.addStaticFields(Collections.singletonMap("A", 345)), object);
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_StaticFields_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        TestLogger logger = new TestLogger();
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.addStaticFields(Collections.singletonMap("A", true)), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Value of 'staticFields attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'true', as it has to be a string or number", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.STATIC_FIELDS.validate(logger.addStaticFields(Collections.singletonMap("A", true)), object);
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Value of 'staticFields attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'true', as it has to be a string or number", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+
+        try
+        {
+            GelfConfigurationValidator.validateConfiguration(logger.addStaticFields(Collections.singletonMap("A", true)), object, Collections.singleton("A"));
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFieldsTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFieldsTest.java
new file mode 100644
index 0000000..37e7038
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/GelfMessageStaticFieldsTest.java
@@ -0,0 +1,142 @@
+package org.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class GelfMessageStaticFieldsTest extends UnitTestBase
+{
+    @Test
+    public void testValidator()
+    {
+        assertNotNull("Factory method has to produce a instance", GelfMessageStaticFields.validator());
+    }
+
+    @Test
+    public void testValidate_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            GelfMessageStaticFields.validateStaticFields(null, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'null'", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidateKey_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Map<String, Object> map = new HashMap<>();
+            map.put("B", "B");
+            map.put(null, "A");
+            GelfMessageStaticFields.validateStaticFields(map, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Key of 'attr attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'null'. Key pattern is: [\\w\\.\\-]+", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidateValue_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Map<String, Object> map = new HashMap<>();
+            map.put("B", "B");
+            map.put("A", null);
+            GelfMessageStaticFields.validateStaticFields(map, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Value of 'attr attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'null', as it has to be a string or number", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidateKey_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Map<String, Object> map = new HashMap<>();
+            map.put("B", "AB");
+            map.put("A", 234);
+            GelfMessageStaticFields.validateStaticFields(map, object, "attr");
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidateKey_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+
+        try
+        {
+            GelfMessageStaticFields.validateStaticFields(Collections.singletonMap("{abc}", "true"), object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Key of 'attr attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be '{abc}'. Key pattern is: [\\w\\.\\-]+", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidateValue_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+
+        try
+        {
+            GelfMessageStaticFields.validateStaticFields(Collections.singletonMap("A", true), object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Value of 'attr attribute instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot be 'true', as it has to be a string or number", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/PortTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/PortTest.java
new file mode 100644
index 0000000..3d2b8df
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/PortTest.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.test.utils.UnitTestBase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class PortTest extends UnitTestBase
+{
+    @Test
+    public void testValidator()
+    {
+        assertNotNull("Factory method has to produce a instance", Port.validator());
+    }
+
+    @Test
+    public void testValidate_NullAsInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Port.validatePort(null, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value 'null' as it has to be in range [1, 65535]", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_ValidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Port.validatePort(14420, object, "attr");
+        }
+        catch (RuntimeException e)
+        {
+            fail("Any exception is not expected");
+        }
+    }
+
+    @Test
+    public void testValidate_InvalidInput()
+    {
+        TestConfiguredObject object = new TestConfiguredObject();
+        try
+        {
+            Port.validatePort(170641, object, "attr");
+            fail("An exception is expected");
+        }
+        catch (IllegalConfigurationException e)
+        {
+            assertEquals("Attribute 'attr' instance of org.apache.qpid.server.logging.logback.validator.TestConfiguredObject named 'TestConfiguredObject' cannot have value '170641' as it has to be in range [1, 65535]", e.getMessage());
+        }
+        catch (RuntimeException e)
+        {
+            fail("A generic exception is not expected");
+        }
+    }
+}
\ No newline at end of file
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObject.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObject.java
new file mode 100644
index 0000000..ffb9620
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObject.java
@@ -0,0 +1,486 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.qpid.server.configuration.updater.CurrentThreadTaskExecutor;
+import org.apache.qpid.server.configuration.updater.TaskExecutor;
+import org.apache.qpid.server.model.ConfigurationChangeListener;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFactory;
+import org.apache.qpid.server.model.LifetimePolicy;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.model.State;
+import org.apache.qpid.server.model.preferences.UserPreferences;
+import org.apache.qpid.server.security.SecurityToken;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.store.ConfiguredObjectRecord;
+
+import javax.security.auth.Subject;
+import java.lang.reflect.Type;
+import java.security.AccessControlException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class TestConfiguredObject implements ConfiguredObject<TestConfiguredObject>
+{
+    public static final String CONFIGURED_OBJECT = "ConfiguredObject";
+
+    public static final String _NAME = "TestConfiguredObject";
+
+    private final Date _now = new Date();
+
+    private final Map<String, String> _context = new LinkedHashMap<>();
+
+    private final Map<String, Object> _attributes = new LinkedHashMap<>();
+
+    private final TaskExecutor _taskExecutor = CurrentThreadTaskExecutor.newStartedInstance();
+
+    private final ConfiguredObject<?> _parent;
+
+    private UUID _uuid = UUID.randomUUID();
+
+    private UserPreferences _userPreferences;
+
+    public TestConfiguredObject()
+    {
+        super();
+        _parent = null;
+    }
+
+    public TestConfiguredObject(ConfiguredObject<?> parent, Map<String, Object> attributes)
+    {
+        super();
+        _parent = parent;
+        _attributes.putAll(attributes);
+    }
+
+    @Override
+    public UUID getId()
+    {
+        return _uuid;
+    }
+
+    public TestConfiguredObject withId(UUID id)
+    {
+        this._uuid = id;
+        return this;
+    }
+
+    @Override
+    public String getName()
+    {
+        return _NAME;
+    }
+
+    @Override
+    public String getDescription()
+    {
+        return getName();
+    }
+
+    @Override
+    public String getType()
+    {
+        return CONFIGURED_OBJECT;
+    }
+
+    @Override
+    public Map<String, String> getContext()
+    {
+        return _context;
+    }
+
+    @Override
+    public String getLastUpdatedBy()
+    {
+        return "user";
+    }
+
+    @Override
+    public Date getLastUpdatedTime()
+    {
+        return _now;
+    }
+
+    @Override
+    public String getCreatedBy()
+    {
+        return "user";
+    }
+
+    @Override
+    public Date getCreatedTime()
+    {
+        return _now;
+    }
+
+    @Override
+    public State getDesiredState()
+    {
+        return State.ACTIVE;
+    }
+
+    @Override
+    public State getState()
+    {
+        return State.ACTIVE;
+    }
+
+    @Override
+    public Date getLastOpenedTime()
+    {
+        return _now;
+    }
+
+    @Override
+    public void addChangeListener(ConfigurationChangeListener listener)
+    {
+    }
+
+    @Override
+    public boolean removeChangeListener(ConfigurationChangeListener listener)
+    {
+        return false;
+    }
+
+    @Override
+    public ConfiguredObject<?> getParent()
+    {
+        return _parent;
+    }
+
+    @Override
+    public boolean isDurable()
+    {
+        return false;
+    }
+
+    @Override
+    public LifetimePolicy getLifetimePolicy()
+    {
+        return LifetimePolicy.IN_USE;
+    }
+
+    @Override
+    public Map<String, Object> getStatistics(List<String> statistics)
+    {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public String setContextVariable(String name, String value)
+    {
+        return _context.put(name, value);
+    }
+
+    @Override
+    public String removeContextVariable(String name)
+    {
+        return _context.remove(name);
+    }
+
+    @Override
+    public Collection<String> getAttributeNames()
+    {
+        return _attributes.keySet();
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        return _attributes.get(name);
+    }
+
+    @Override
+    public Map<String, Object> getActualAttributes()
+    {
+        return _attributes;
+    }
+
+    @Override
+    public Map<String, Object> getStatistics()
+    {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz)
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public <C extends ConfiguredObject> C getChildById(Class<C> clazz, UUID id)
+    {
+        return null;
+    }
+
+    @Override
+    public <C extends ConfiguredObject> C getChildByName(Class<C> clazz, String name)
+    {
+        return null;
+    }
+
+    @Override
+    public <C extends ConfiguredObject> C createChild(Class<C> childClass, Map<String, Object> attributes)
+    {
+        return null;
+    }
+
+    @Override
+    public <C extends ConfiguredObject> ListenableFuture<C> getAttainedChildById(Class<C> childClass, UUID id)
+    {
+        final SettableFuture<C> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public <C extends ConfiguredObject> ListenableFuture<C> getAttainedChildByName(Class<C> childClass, String name)
+    {
+        final SettableFuture<C> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public <C extends ConfiguredObject> ListenableFuture<C> createChildAsync(Class<C> childClass, Map<String, Object> attributes)
+    {
+        final SettableFuture<C> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public void setAttributes(Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException
+    {
+        _attributes.clear();
+        _attributes.putAll(attributes);
+    }
+
+    @Override
+    public ListenableFuture<Void> setAttributesAsync(Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException
+    {
+        setAttributes(attributes);
+
+        final SettableFuture<Void> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public Class<? extends ConfiguredObject> getCategoryClass()
+    {
+        return TestConfiguredObject.class;
+    }
+
+    @Override
+    public Class<? extends ConfiguredObject> getTypeClass()
+    {
+        return TestConfiguredObject.class;
+    }
+
+    @Override
+    public boolean managesChildStorage()
+    {
+        return false;
+    }
+
+    @Override
+    public <C extends ConfiguredObject<C>> C findConfiguredObject(Class<C> clazz, String name)
+    {
+        if (getClass().equals(clazz) && Objects.equals(getName(), name))
+        {
+            return (C) this;
+        }
+        return null;
+    }
+
+    @Override
+    public ConfiguredObjectRecord asObjectRecord()
+    {
+        final TestConfiguredObject me = this;
+        return new ConfiguredObjectRecord()
+        {
+            @Override
+            public UUID getId()
+            {
+                return me.getId();
+            }
+
+            @Override
+            public String getType()
+            {
+                return me.getType();
+            }
+
+            @Override
+            public Map<String, Object> getAttributes()
+            {
+                return me.getActualAttributes();
+            }
+
+            @Override
+            public Map<String, UUID> getParents()
+            {
+                return Collections.emptyMap();
+            }
+        };
+    }
+
+    @Override
+    public void open()
+    {
+    }
+
+    @Override
+    public ListenableFuture<Void> openAsync()
+    {
+        final SettableFuture<Void> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public ListenableFuture<Void> closeAsync()
+    {
+        final SettableFuture<Void> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public ListenableFuture<Void> deleteAsync()
+    {
+        final SettableFuture<Void> returnVal = SettableFuture.create();
+        returnVal.set(null);
+        return returnVal;
+    }
+
+    @Override
+    public TaskExecutor getChildExecutor()
+    {
+        return null;
+    }
+
+    @Override
+    public ConfiguredObjectFactory getObjectFactory()
+    {
+        return TestModel.MODEL.getObjectFactory();
+    }
+
+    @Override
+    public Model getModel()
+    {
+        return TestModel.MODEL;
+    }
+
+    @Override
+    public void delete()
+    {
+    }
+
+    @Override
+    public boolean hasEncrypter()
+    {
+        return false;
+    }
+
+    @Override
+    public void decryptSecrets()
+    {
+    }
+
+    @Override
+    public UserPreferences getUserPreferences()
+    {
+        return _userPreferences;
+    }
+
+    @Override
+    public void setUserPreferences(UserPreferences userPreferences)
+    {
+        _userPreferences = userPreferences;
+    }
+
+    @Override
+    public void authorise(Operation operation) throws AccessControlException
+    {
+    }
+
+    @Override
+    public void authorise(Operation operation, Map<String, Object> arguments) throws AccessControlException
+    {
+    }
+
+    @Override
+    public void authorise(SecurityToken token, Operation operation, Map<String, Object> arguments) throws AccessControlException
+    {
+    }
+
+    @Override
+    public SecurityToken newToken(Subject subject)
+    {
+        return null;
+    }
+
+    @Override
+    public <T> T getContextValue(Class<T> clazz, String propertyName)
+    {
+        if (String.class.equals(clazz))
+        {
+            return (T) _context.get(propertyName);
+        }
+        return null;
+    }
+
+    @Override
+    public <T> T getContextValue(Class<T> clazz, Type t, String propertyName)
+    {
+        return getContextValue(clazz, propertyName);
+    }
+
+    @Override
+    public Set<String> getContextKeys(boolean excludeSystem)
+    {
+        return _context.keySet();
+    }
+
+    @Override
+    public TaskExecutor getTaskExecutor()
+    {
+        return _taskExecutor;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectFactory.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectFactory.java
new file mode 100644
index 0000000..67a2d54
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectFactory.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFactory;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.plugin.ConfiguredObjectTypeFactory;
+import org.apache.qpid.server.store.ConfiguredObjectRecord;
+import org.apache.qpid.server.store.UnresolvedConfiguredObject;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+public class TestConfiguredObjectFactory implements ConfiguredObjectFactory
+{
+    private static final TestConfiguredObjectTypeFactory _FACTORY = new TestConfiguredObjectTypeFactory();
+
+    private final Model _model;
+
+    public TestConfiguredObjectFactory(Model model)
+    {
+        _model = model;
+    }
+
+    @Override
+    public <X extends ConfiguredObject<X>> UnresolvedConfiguredObject<X> recover(ConfiguredObjectRecord record, ConfiguredObject<?> parent)
+    {
+        if (!TestConfiguredObject.TYPE.equals(record.getType()))
+        {
+            return null;
+        }
+        return (UnresolvedConfiguredObject<X>) _FACTORY.recover(this, record, parent);
+    }
+
+    @Override
+    public <X extends ConfiguredObject<X>> X create(Class<X> clazz, Map<String, Object> attributes, ConfiguredObject<?> parent)
+    {
+        if (TestConfiguredObject.class.equals(clazz))
+        {
+            return (X) _FACTORY.create(this, attributes, parent);
+        }
+        return null;
+    }
+
+    @Override
+    public <X extends ConfiguredObject<X>> ListenableFuture<X> createAsync(Class<X> clazz, Map<String, Object> attributes, ConfiguredObject<?> parent)
+    {
+        final SettableFuture<X> returnVal = SettableFuture.create();
+        returnVal.set(create(clazz, attributes, parent));
+        return returnVal;
+    }
+
+    @Override
+    public <X extends ConfiguredObject<X>> ConfiguredObjectTypeFactory<X> getConfiguredObjectTypeFactory(String category, String type)
+    {
+        if (TestConfiguredObject.class.getSimpleName().equals(category) && TestConfiguredObject.TYPE.equals(type))
+        {
+            return (ConfiguredObjectTypeFactory<X>) new TestConfiguredObjectTypeFactory();
+        }
+        return null;
+    }
+
+    @Override
+    public Collection<String> getSupportedTypes(Class<? extends ConfiguredObject> category)
+    {
+        if (TestConfiguredObject.class.equals(category))
+        {
+            return Collections.singleton(TestConfiguredObject.TYPE);
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Model getModel()
+    {
+        return _model;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectTypeFactory.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectTypeFactory.java
new file mode 100644
index 0000000..acec505
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestConfiguredObjectTypeFactory.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFactory;
+import org.apache.qpid.server.plugin.ConfiguredObjectTypeFactory;
+import org.apache.qpid.server.store.ConfiguredObjectDependency;
+import org.apache.qpid.server.store.ConfiguredObjectRecord;
+import org.apache.qpid.server.store.UnresolvedConfiguredObject;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+public class TestConfiguredObjectTypeFactory implements ConfiguredObjectTypeFactory<TestConfiguredObject>
+{
+    @Override
+    public Class<? super TestConfiguredObject> getCategoryClass()
+    {
+        return TestConfiguredObject.class;
+    }
+
+    @Override
+    public TestConfiguredObject create(ConfiguredObjectFactory factory, Map<String, Object> attributes, ConfiguredObject<?> parent)
+    {
+        return new TestConfiguredObject(parent, attributes);
+    }
+
+    @Override
+    public ListenableFuture<TestConfiguredObject> createAsync(ConfiguredObjectFactory factory, Map<String, Object> attributes, ConfiguredObject<?> parent)
+    {
+        final SettableFuture<TestConfiguredObject> returnVal = SettableFuture.create();
+        returnVal.set(create(factory, attributes, parent));
+        return returnVal;
+    }
+
+    @Override
+    public UnresolvedConfiguredObject<TestConfiguredObject> recover(final ConfiguredObjectFactory factory, final ConfiguredObjectRecord record, final ConfiguredObject<?> parent)
+    {
+        return new UnresolvedConfiguredObject<TestConfiguredObject>()
+        {
+            @Override
+            public ConfiguredObject<?> getParent()
+            {
+                return parent;
+            }
+
+            @Override
+            public Collection<ConfiguredObjectDependency<?>> getUnresolvedDependencies()
+            {
+                return Collections.emptyList();
+            }
+
+            @Override
+            public TestConfiguredObject resolve()
+            {
+                return create(factory, record.getAttributes(), parent).withId(record.getId());
+            }
+        };
+    }
+
+    @Override
+    public String getType()
+    {
+        return null;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestModel.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestModel.java
new file mode 100644
index 0000000..8bd09a0
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/logging/logback/validator/TestModel.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.logging.logback.validator;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFactory;
+import org.apache.qpid.server.model.ConfiguredObjectTypeRegistry;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.plugin.ConfiguredObjectRegistration;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class TestModel extends Model
+{
+    public static final TestModel MODEL = new TestModel().init();
+
+    private static final ConfiguredObjectRegistration REGISTRATION = new ConfiguredObjectRegistration()
+    {
+        @Override
+        public Collection<Class<? extends ConfiguredObject>> getConfiguredObjectClasses()
+        {
+            return Collections.singleton(TestConfiguredObject.class);
+        }
+
+        @Override
+        public String getType()
+        {
+            return null;
+        }
+    };
+
+    private TestConfiguredObjectFactory _factory;
+
+    private ConfiguredObjectTypeRegistry _registry;
+
+    private TestModel()
+    {
+        super();
+    }
+
+    private TestModel init()
+    {
+        _factory = new TestConfiguredObjectFactory(this);
+        _registry = new ConfiguredObjectTypeRegistry(
+                Collections.singleton(REGISTRATION),
+                Collections.emptySet(),
+                Collections.singleton(TestConfiguredObject.class),
+                _factory);
+        return this;
+    }
+
+    @Override
+    public Collection<Class<? extends ConfiguredObject>> getSupportedCategories()
+    {
+        return Collections.singletonList(TestConfiguredObject.class);
+    }
+
+    @Override
+    public Collection<Class<? extends ConfiguredObject>> getChildTypes(Class<? extends ConfiguredObject> parent)
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Class<? extends ConfiguredObject> getRootCategory()
+    {
+        return TestConfiguredObject.class;
+    }
+
+    @Override
+    public Class<? extends ConfiguredObject> getParentType(Class<? extends ConfiguredObject> child)
+    {
+        return null;
+    }
+
+    @Override
+    public int getMajorVersion()
+    {
+        return 1;
+    }
+
+    @Override
+    public int getMinorVersion()
+    {
+        return 1;
+    }
+
+    @Override
+    public ConfiguredObjectFactory getObjectFactory()
+    {
+        return _factory;
+    }
+
+    @Override
+    public ConfiguredObjectTypeRegistry getTypeRegistry()
+    {
+        return _registry;
+    }
+}
diff --git a/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/util/ArrayUtilsTest.java b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/util/ArrayUtilsTest.java
new file mode 100644
index 0000000..f4fac79
--- /dev/null
+++ b/broker-plugins/graylog-logging-logback/src/test/java/org/apache/qpid/server/util/ArrayUtilsTest.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.server.util;
+
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class ArrayUtilsTest extends TestCase
+{
+    @Test
+    public void testClone_NullInput()
+    {
+        assertNull(ArrayUtils.clone(null));
+    }
+
+    @Test
+    public void testClone_EmptyInput()
+    {
+        String[] data = new String[0];
+        String[] result = ArrayUtils.clone(data);
+        assertArrayEquals(data, result);
+        assertNotSame(data, result);
+    }
+
+    @Test
+    public void testClone()
+    {
+        String[] data = new String[]{"A", null, "B", "CC"};
+        String[] result = ArrayUtils.clone(data);
+        assertArrayEquals(data, result);
+        assertNotSame(data, result);
+    }
+
+    @Test
+    public void testIsEmpty_NullInput()
+    {
+        assertTrue(ArrayUtils.isEmpty(null));
+    }
+
+    @Test
+    public void testIsEmpty_EmptyInput()
+    {
+        assertTrue(ArrayUtils.isEmpty(new String[0]));
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        assertFalse(ArrayUtils.isEmpty(new String[]{"A"}));
+        assertFalse(ArrayUtils.isEmpty(new String[]{"A", "B"}));
+        assertFalse(ArrayUtils.isEmpty(new String[]{null}));
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/resources/addLogger.html b/broker-plugins/management-http/src/main/java/resources/addLogger.html
index c0aa6b5..2b9cbe4 100644
--- a/broker-plugins/management-http/src/main/java/resources/addLogger.html
+++ b/broker-plugins/management-http/src/main/java/resources/addLogger.html
@@ -20,8 +20,8 @@
 <div class="dijitHidden">
     <div data-dojo-type="dijit/Dialog" data-dojo-props="title:'Add Logger'" id="addLogger">
         <form id="addLogger.form" method="post" data-dojo-type="dijit/form/Form">
-            <div class="hidden infoPane" id="brokerLoggerEditWarning">Changes will only take effect after Broker restart.</div>
-            <div class="hidden infoPane" id="virtualHostlLoggerEditWarning">Changes will only take effect after VirtualHost restart.</div>
+            <div class="hidden loggerInfoPane" id="brokerLoggerEditWarning">Changes will only take effect after Broker restart.</div>
+            <div class="hidden loggerInfoPane" id="virtualHostlLoggerEditWarning">Changes will only take effect after VirtualHost restart.</div>
             <div id="addLogger.contentPane">
                 <div class="clear">
                     <div class="formLabel-labelCell tableContainer-labelCell">Name*:</div>
diff --git a/broker-plugins/management-http/src/main/java/resources/css/common.css b/broker-plugins/management-http/src/main/java/resources/css/common.css
index 4646275..8001d37 100644
--- a/broker-plugins/management-http/src/main/java/resources/css/common.css
+++ b/broker-plugins/management-http/src/main/java/resources/css/common.css
@@ -384,6 +384,18 @@ h1 {
     height:100%;
 }
 
+.loggerInfoPane
+{
+    margin-left: 5px;
+    margin-bottom: 5px;
+    padding: 5px 5px 5px 1.2em;
+    font-style: italic;
+    background:url("../images/notification.svg") no-repeat left center;
+    background-size: 1em;
+    width:100%;
+    height:1em;
+}
+
 .dgrid-column-selected
 {
     width: 2em;
@@ -839,3 +851,10 @@ td.advancedSearchField, col.autoWidth {
 .queueMessages .field-size { width: 20%;}
 .queueMessages .field-state { width: 20%; }
 .queueMessages .field-arrivalTime { width: auto }
+
+.mapList-scroll-y
+{
+    height: 5em;
+    overflow-y: auto;
+    overflow-x: visible;
+}
\ No newline at end of file
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/common/MapInputWidget.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/MapInputWidget.js
new file mode 100644
index 0000000..94109bc
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/MapInputWidget.js
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+define("qpid/common/MapInputWidget", [
+    "dojo/_base/declare",
+    "dojo/dom-construct",
+    "dojo/query",
+    "dojo/_base/event",
+    "dojox/html/entities",
+    "dijit",
+    "dijit/registry",
+    "dijit/form/Form",
+], function (declare, domConstruct, query, event, entities, dijit, registry, Form)
+{
+    return declare("qpid.common.MapInputWidget", [Form],
+        {
+            value: {},
+
+            _setValueAttr: function (obj)
+            {
+                if (typeof obj == "object")
+                {
+                    this.value = obj;
+                    this._widgetValue = this._initWidgetValue();
+                    this._listContainers().forEach(container => this._initListContainer(container));
+                }
+            },
+
+            _getValueAttr()
+            {
+                return this._widgetValue();
+            },
+
+            _initWidgetValue()
+            {
+                const widgetValue = {};
+
+                for (let [key, value] of Object.entries(this.value || {}))
+                {
+                    const trimmedKey = key.trim();
+                    if (trimmedKey.length)
+                    {
+                        widgetValue[trimmedKey] = value;
+                    }
+                }
+                this.value = widgetValue;
+
+                return () => this.value;
+            },
+
+            _widgetValue()
+            {
+                return (this._widgetValue = this._initWidgetValue())();
+            },
+
+            _getWidgetAttribute(widget, name)
+            {
+                const attribute = widget.get(name);
+                return attribute != "undefined" ? attribute : undefined;
+            },
+
+            _getWidgetAttributeAsString(widget, name)
+            {
+                const attribute = widget.get(name);
+                if (attribute !== undefined && attribute !== null)
+                {
+                    const str = String(attribute).trim();
+                    return str !== "undefined" ? str : "";
+                }
+                return "";
+            },
+
+            _getWidgetName(widget)
+            {
+                return this._getWidgetAttributeAsString(widget, "name");
+            },
+
+            _findNodes(className, domNode)
+            {
+                if (domNode)
+                {
+                    return query("." + className, domNode) || [];
+                }
+                return [];
+            },
+
+            _initWidgetName()
+            {
+                const name = this._getWidgetName(this);
+                return () => name;
+            },
+
+            _widgetName()
+            {
+                return (this._widgetName = this._initWidgetName())();
+            },
+
+            _initKeyClass()
+            {
+                const className = this._getWidgetAttributeAsString(this, "keyClass");
+                if (!className.length)
+                {
+                    return () => "key";
+                }
+                return () => className;
+
+            },
+
+            _keyClass()
+            {
+                return (this._keyClass = this._initKeyClass())();
+            },
+
+            _initValueClass()
+            {
+                const className = this._getWidgetAttributeAsString(this, "valueClass");
+                if (!className.length)
+                {
+                    return () => "value";
+                }
+                return () => className;
+            },
+
+            _valueClass()
+            {
+                return (this._valueClass = this._initValueClass())();
+            },
+
+            _initListClass()
+            {
+                const className = this._getWidgetAttributeAsString(this, "listClass");
+                if (!className.length)
+                {
+                    return () => "keyValueList";
+                }
+                return () => className;
+            },
+
+            _listClass()
+            {
+                return (this._listClass = this._initListClass())();
+            },
+
+            _getWidgetById(attributeName)
+            {
+                const id = this._getWidgetAttribute(this, attributeName);
+                if (id)
+                {
+                    return registry.byId(id, this.domNode);
+                }
+                return undefined;
+            },
+
+            _getWidgetByName(name)
+            {
+                const childName = this._widgetName() + "." + name;
+                for (let childWidget of this.getChildren() || [])
+                {
+                    if (this._getWidgetName(childWidget) === childName)
+                    {
+                        return childWidget;
+                    }
+                }
+                return undefined;
+            },
+
+            _newSupplier(widget)
+            {
+                if (!widget)
+                {
+                    return () => null;
+                }
+                if (widget instanceof dijit.form.RadioButton)
+                {
+                    return () => this._getWidgetAttribute(widget, "checked") ? this._getWidgetAttribute(widget, "value") : null;
+                }
+                if (widget instanceof dijit.form.CheckBox)
+                {
+                    return () => this._getWidgetAttribute(widget, "checked");
+                }
+                return () => this._getWidgetAttribute(widget, "value");
+            },
+
+            _widget: Symbol("widget"),
+
+            _reset: Symbol("reset"),
+
+            _decorateWithReset(obj)
+            {
+                if (["function", "object"].includes(typeof obj))
+                {
+                    const widget = obj[this._widget];
+                    if (widget)
+                    {
+                        if (typeof widget.reset === "function")
+                        {
+                            obj[this._reset] = () => widget.reset();
+                            return obj;
+                        }
+                    }
+                    obj[this._reset] = () =>
+                    {
+                    }
+                }
+                return obj;
+            },
+
+            _initKeySupplier()
+            {
+                let widget = this._getWidgetById("keySupplierId");
+                if (!widget)
+                {
+                    widget = this._getWidgetByName(this._keyClass());
+                }
+                if (widget)
+                {
+                    const rawSupplier = this._newSupplier(widget);
+                    const supplier = function ()
+                    {
+                        let result = rawSupplier();
+                        if (result)
+                        {
+                            result = String(result).trim();
+                            return result.length ? result : null;
+                        }
+                        return null;
+                    }
+                    supplier[this._widget] = widget;
+                    return this._decorateWithReset(supplier);
+                }
+                return this._decorateWithReset(() => null);
+            },
+
+            _keySupplier()
+            {
+                return (this._keySupplier = this._initKeySupplier())();
+            },
+
+            _initValueSupplier()
+            {
+                let widget = this._getWidgetById("valueSupplierId");
+                if (!widget)
+                {
+                    widget = this._getWidgetByName(this._valueClass());
+                }
+                if (widget)
+                {
+                    const rawSupplier = this._newSupplier(widget);
+                    const supplier = function ()
+                    {
+                        const value = rawSupplier();
+                        if (typeof value == "string" && !value.length)
+                        {
+                            return null;
+                        }
+                        return value;
+                    }
+                    supplier[this._widget] = widget;
+                    return this._decorateWithReset(supplier);
+                }
+                return this._decorateWithReset(() => null);
+            },
+
+            _valueSupplier()
+            {
+                return (this._valueSupplier = this._initValueSupplier())();
+            },
+
+            _initKeyValueTemplate()
+            {
+                let template = '<div class="clear">' +
+                    '<div class="key formLabel-labelCell">:</div>' +
+                    '<div class="value formValue-valueCell"></div>' +
+                    '</div>';
+                const file = this._getWidgetAttribute(this, "keyValueTemplate");
+                if (file)
+                {
+                    try
+                    {
+                        require(["dojo/text!" + file], t =>
+                        {
+                            if (t)
+                            {
+                                template = t;
+                            }
+                        });
+                    }
+                    catch (e)
+                    {
+                        console.warn(e);
+                    }
+                }
+                return () => template;
+            },
+
+            _keyValueTemplate()
+            {
+                return (this._keyValueTemplate = this._initKeyValueTemplate())();
+            },
+
+            _insertNodeToContainer: Symbol("insertNode"),
+
+            _deleteNodeFromContainer: Symbol("deleteNode"),
+
+            _initListContainer(container)
+            {
+                domConstruct.empty(container);
+
+                const containerDomNodes = {};
+                const mapWidget = this;
+                const insertNode = function (key, value)
+                {
+                    let newDomNode;
+                    if (containerDomNodes[key])
+                    {
+                        newDomNode = domConstruct.place(mapWidget._keyValueTemplate(), containerDomNodes[key], "replace");
+                    }
+                    else
+                    {
+                        newDomNode = domConstruct.place(mapWidget._keyValueTemplate(), container, "last");
+                    }
+                    containerDomNodes[key] = newDomNode;
+                    mapWidget._findNodes(mapWidget._keyClass(), newDomNode).forEach(function (node)
+                    {
+                        const innerHTML = node.innerHTML;
+                        if (typeof innerHTML === "string")
+                        {
+                            node.innerHTML = entities.encode(key) + innerHTML.trim();
+                        }
+                        else
+                        {
+                            node.innerHTML = entities.encode(key);
+                        }
+                    });
+                    mapWidget._findNodes(mapWidget._valueClass(), newDomNode).forEach(node => node.innerHTML = entities.encode(String(value)));
+                }
+                container[this._insertNodeToContainer] = insertNode;
+
+                container[this._deleteNodeFromContainer] = function (key)
+                {
+                    if (key && containerDomNodes[key])
+                    {
+                        domConstruct.destroy(containerDomNodes[key]);
+                        delete containerDomNodes[key];
+                    }
+                }
+
+                for (let [key, value] of Object.entries(this._widgetValue()))
+                {
+                    insertNode(key, value);
+                }
+            },
+
+            _initListContainers()
+            {
+                const containers = this._findNodes(this._listClass(), this.domNode);
+                containers.forEach(container => this._initListContainer(container));
+                return () => containers;
+            },
+
+            _listContainers()
+            {
+                return (this._listContainers = this._initListContainers())();
+            },
+
+            _addKeyValueItem()
+            {
+                const key = this._keySupplier();
+                const value = this._valueSupplier();
+                if (key && value)
+                {
+                    this._listContainers().forEach(container => container[this._insertNodeToContainer](key, value));
+                    this._widgetValue()[key] = value;
+                    this._keySupplier[this._reset]();
+                    this._valueSupplier[this._reset]();
+                }
+            },
+
+            _deleteKeyValueItem(key)
+            {
+                if (key && this.value[key])
+                {
+                    delete this.value[key];
+                }
+                this._listContainers().forEach(container => container[this._deleteNodeFromContainer](key));
+            },
+
+            _initWidget()
+            {
+                this._keyClass = this._initKeyClass();
+                this._valueClass = this._initValueClass();
+                this._keySupplier = this._initKeySupplier();
+                this._keyValueTemplate = this._initKeyValueTemplate();
+                this._valueSupplier = this._initValueSupplier();
+                this._listContainers = this._initListContainers();
+                this._initWidget = () =>
+                {
+                };
+            },
+
+            onSubmit()
+            {
+                this.inherited(arguments);
+                if (this.isValid())
+                {
+                    this._addKeyValueItem();
+                }
+                return false;
+            },
+
+            onReset()
+            {
+                this.inherited(arguments);
+                const key = this._keySupplier();
+                if (key)
+                {
+                    this._deleteKeyValueItem(key);
+                }
+                else
+                {
+                    this.value = {};
+                    this._listContainers().forEach(container => this._initListContainer(container));
+                }
+                return true;
+            },
+
+            startup()
+            {
+                this.inherited(arguments);
+                this._initWidget();
+            }
+        });
+});
\ No newline at end of file
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
index 3cef2b7..8b88d2b 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
@@ -1112,24 +1112,64 @@ define(["dojo/_base/xhr",
             return containerObject;
         };
 
-        util.updateAttributeNodes = function (containerObject, restData) {
+        util.updateBooleanAttributeNode = function (containerObject, restData, util) {
+            containerObject.containerNode.innerHTML = util.buildCheckboxMarkup(restData);
+        }
+
+        util.updateMapAttributeNode = function (containerObject, restData, util, template, key = "key", value = "value") {
+            if (!template)
+            {
+                return;
+            }
+            dom.empty(containerObject.containerNode);
+            for (let [restRecordKey, restRecordValue] of Object.entries(restData))
+            {
+                let newNode = dom.place(template, containerObject.containerNode, "last");
+
+                util.findNode(key, newNode).forEach(node => {
+                    let innerHTML = node.innerHTML;
+                    if (typeof innerHTML === "string")
+                    {
+                        node.innerHTML = entities.encode(String(restRecordKey)) + innerHTML.trim();
+                    } else {
+                        node.innerHTML = entities.encode(String(restRecordKey));
+                    }
+                });
+
+                util.findNode(value, newNode).forEach(node => node.innerHTML = entities.encode(String(restRecordValue)));
+            }
+        }
+
+        util.findNode = function (key, containerNode)
+        {
+            if (containerNode)
+            {
+                return query("." + key, containerNode) || [];
+            }
+            return [];
+        }
+
+        util.updateAttributeNodes = function (containerObject, restData, booleanUpdater = util.updateBooleanAttributeNode, mapUpdater = null)
+        {
             for (var attrName in containerObject)
             {
                 if (containerObject.hasOwnProperty(attrName) && attrName in restData)
                 {
-                    var content = "";
-                    if (containerObject[attrName].attributeType === "Boolean")
+                    if (containerObject[attrName].attributeType === "Boolean" && typeof booleanUpdater === "function")
+                    {
+                        booleanUpdater(containerObject[attrName], restData[attrName], util);
+                    }
+                    else if (containerObject[attrName].attributeType === "Map" && typeof mapUpdater === "function")
                     {
-                        content = util.buildCheckboxMarkup(restData[attrName]);
+                        mapUpdater(containerObject[attrName], restData[attrName], util)
                     }
                     else
                     {
-                        content = entities.encode(String(restData[attrName]));
+                        containerObject[attrName].containerNode.innerHTML = entities.encode(String(restData[attrName]));
                     }
-                    containerObject[attrName].containerNode.innerHTML = content;
                 }
             }
-        };
+        }
 
         return util;
     });
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/common/widgetconfigurer.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/widgetconfigurer.js
index 4a8f22d..f5a452b 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/common/widgetconfigurer.js
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/common/widgetconfigurer.js
@@ -190,7 +190,7 @@ define(["dojo/_base/xhr",
                     widget.defaultValue = defaultValue;
                     if ( widget instanceof dijit.form.CheckBox)
                     {
-                        widget.set("checked", defaultValue === true);
+                        widget.set("checked", defaultValue === true || defaultValue === "true");
                     }
                 }
             }
diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addLogger.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addLogger.js
index 9c368ae..1739d0e 100644
--- a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addLogger.js
+++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addLogger.js
@@ -22,6 +22,7 @@ define(["dojo/_base/lang",
         "dojo/dom",
         "dojo/dom-construct",
         "dojo/dom-style",
+        "dojo/dom-geometry",
         "dijit/registry",
         "dojo/parser",
         "dojo/store/Memory",
@@ -45,12 +46,13 @@ define(["dojo/_base/lang",
         "dijit/layout/ContentPane",
         "dojox/layout/TableContainer",
         "dojo/domReady!"],
-    function (lang, dom, construct, domStyle, registry, parser, memory, array, event, json, util, template)
+    function (lang, dom, construct, domStyle, domGeometry, registry, parser, memory, array, event, json, util, template)
     {
-        var addLogger = {
+        const addLogger = {
             init: function ()
             {
-                var that = this;
+                const that = this;
+                this.initDocument = document.documentElement;
                 this.containerNode = construct.create("div", {innerHTML: template});
                 parser.parse(this.containerNode)
                     .then(function (instances)
@@ -60,7 +62,7 @@ define(["dojo/_base/lang",
             },
             _postParse: function ()
             {
-                var that = this;
+                const that = this;
                 this.name = registry.byId("addLogger.name");
                 this.name.set("regExpGen", util.nameOrContextVarRegexp);
 
@@ -117,8 +119,8 @@ define(["dojo/_base/lang",
                     this._configure(actualData.type);
                 }
 
-                var brokerLoggerEditWarningNode = dom.byId("brokerLoggerEditWarning");
-                var virtualHostlLoggerEditWarningNode = dom.byId("virtualHostlLoggerEditWarning");
+                const brokerLoggerEditWarningNode = dom.byId("brokerLoggerEditWarning");
+                const virtualHostlLoggerEditWarningNode = dom.byId("virtualHostlLoggerEditWarning");
                 domStyle.set(brokerLoggerEditWarningNode,
                     "display",
                     !this.isNew && this.category === "BrokerLogger" ? "block" : "none");
@@ -126,13 +128,15 @@ define(["dojo/_base/lang",
                     "display",
                     !this.isNew && this.category === "VirtualHostLogger" ? "block" : "none");
 
-                util.loadEffectiveAndInheritedActualData(this.management, this.modelObj, lang.hitch(this, function(data)
+                util.loadEffectiveAndInheritedActualData(this.management, this.modelObj, lang.hitch(this, function (data)
                 {
-                    this.context.setData(this.isNew ? {} : this.initialData.context ,
+                    this.context.setData(this.isNew ? {} : this.initialData.context,
                         data.effective.context,
                         data.inheritedActual.context);
                     this._loadCategoryUserInterfacesAndShowDialog(actualData);
                 }));
+                this._resetSize();
+                this.dialog.show();
             },
             hide: function ()
             {
@@ -154,12 +158,12 @@ define(["dojo/_base/lang",
             {
                 if (this.form.validate())
                 {
-                    var excludedData = this.initialData
-                                       || this.management.metadata.getDefaultValueForType(this.category,
+                    const excludedData = this.initialData
+                        || this.management.metadata.getDefaultValueForType(this.category,
                             this.loggerType.get("value"));
-                    var formData = util.getFormWidgetValues(this.form, excludedData);
-                    var context = this.context.get("value");
-                    var oldContext = null;
+                    const formData = util.getFormWidgetValues(this.form, excludedData);
+                    const context = this.context.get("value");
+                    let oldContext = null;
                     if (this.initialData !== null && this.initialData !== undefined)
                     {
                         oldContext = this.initialData.context;
@@ -168,7 +172,7 @@ define(["dojo/_base/lang",
                     {
                         formData["context"] = context;
                     }
-                    var that = this;
+                    const that = this;
                     if (this.isNew)
                     {
                         this.management.create(this.category, this.modelObj, formData)
@@ -193,7 +197,7 @@ define(["dojo/_base/lang",
             },
             _destroyTypeFields: function (typeFieldsContainer)
             {
-                var widgets = registry.findWidgets(typeFieldsContainer);
+                const widgets = registry.findWidgets(typeFieldsContainer);
                 array.forEach(widgets, function (item)
                 {
                     item.destroyRecursive();
@@ -206,14 +210,15 @@ define(["dojo/_base/lang",
 
                 if (type)
                 {
+                    const [maxHeight, maxWidth] = this._calculateMaxHeight(this.dialog, this.form);
                     this._configure(type);
-                    var that = this;
+                    const that = this;
                     require(["qpid/management/logger/" + this.category.toLowerCase() + "/" + type.toLowerCase()
-                             + "/add"], function (typeUI)
+                    + "/add"], function (typeUI)
                     {
                         try
                         {
-                            var promise = typeUI.show({
+                            const promise = typeUI.show({
                                 containerNode: that.typeFieldsContainer,
                                 data: that.initialData,
                                 metadata: that.management.metadata,
@@ -230,6 +235,8 @@ define(["dojo/_base/lang",
                                         type,
                                         that.initialData,
                                         that.management.metadata);
+                                    that._setFormOverflow(maxHeight, maxWidth, typeUI);
+                                    that._setPosition();
                                 });
                             }
                         }
@@ -239,25 +246,30 @@ define(["dojo/_base/lang",
                         }
                     });
                 }
+                else
+                {
+                    this._resetFormOverflow();
+                    this._setPosition();
+                }
             },
             _configure: function (type)
             {
                 if (!this.configured)
                 {
-                    var metadata = this.management.metadata;
+                    const metadata = this.management.metadata;
                     util.applyToWidgets(this.allFieldsContainer, this.category, type, this.initialData, metadata);
                     this.configured = true;
                 }
             },
             _loadCategoryUserInterfacesAndShowDialog: function (actualData)
             {
-                var that = this;
-                var node = construct.create("div", {}, this.categoryFieldsContainer);
+                const that = this;
+                const node = construct.create("div", {}, this.categoryFieldsContainer);
                 require(["qpid/management/logger/" + this.category.toLowerCase() + "/add"], function (categoryUI)
                 {
                     try
                     {
-                        var promise = categoryUI.show({
+                        const promise = categoryUI.show({
                             containerNode: node,
                             data: actualData
                         });
@@ -283,6 +295,53 @@ define(["dojo/_base/lang",
                         console.error(e);
                     }
                 });
+            },
+            _calculateMaxHeight: function ()
+            {
+                const loggerGeometry = domGeometry.getMarginBox(this.dialog.domNode);
+                const formGeometry = domGeometry.getContentBox(this.form.domNode);
+                const documentGeometry = domGeometry.getContentBox(this.initDocument);
+                const maxHeight = documentGeometry.h - (loggerGeometry.h - formGeometry.h) - 7;
+                const maxWidth = documentGeometry.w - (loggerGeometry.w - formGeometry.w) - 7;
+                return [maxHeight, maxWidth];
+            },
+            _setFormOverflow: function (maxHeight, maxWidth, typeUI)
+            {
+                const formNode = this.form.domNode;
+                if (formNode.clientHeight > maxHeight || formNode.clientWidth > maxWidth)
+                {
+                    if (typeUI && typeof typeUI.doNotScroll === "function")
+                    {
+                        typeUI.doNotScroll(this.typeFieldsContainer);
+                    }
+                    domStyle.set(formNode, {
+                        maxWidth: maxWidth + "px",
+                        maxHeight: maxHeight + "px",
+                        overflow: "scroll"
+                    });
+                    domStyle.set(this.dialog.domNode, {height: "initial", weight: "initial"});
+                }
+                else
+                {
+                    this._resetFormOverflow();
+                }
+            },
+            _resetFormOverflow: function ()
+            {
+                domStyle.set(this.form.domNode, {maxWidth: "", maxHeight: "", overflow: "initial"});
+                this._resetSize();
+            },
+            _resetSize: function ()
+            {
+                domStyle.set(this.form.domNode, {height: "initial", weight: "initial"});
+                domStyle.set(this.dialog.domNode, {height: "initial", weight: "initial"});
+            },
+            _setPosition: function ()
+            {
+                const rectangle = this.dialog.domNode.getBoundingClientRect();
+                const top = Math.round((this.initDocument.offsetHeight - rectangle.height) / 2.0);
+                const left = Math.round((this.initDocument.offsetWidth - rectangle.width) / 2.0);
+                domStyle.set(this.dialog.domNode, {position: "fixed", top: top + "px", left: left + "px"});
             }
         };
 
diff --git a/broker/pom.xml b/broker/pom.xml
index 61ca655..4f2ae15 100644
--- a/broker/pom.xml
+++ b/broker/pom.xml
@@ -28,6 +28,25 @@
   <name>Apache Qpid Broker-J</name>
   <description>Broker configuration and executable</description>
 
+  <profiles>
+    <profile>
+      <id>graylog</id>
+      <activation>
+        <property>
+          <name>graylog</name>
+        </property>
+      </activation>
+
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.qpid</groupId>
+          <artifactId>qpid-broker-plugins-graylog-logging-logback</artifactId>
+          <scope>runtime</scope>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
+
   <dependencies>
     <dependency>
       <groupId>org.apache.qpid</groupId>
diff --git a/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Log-Files.xml b/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Log-Files.xml
index d1f93da..a4ba2b8 100644
--- a/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Log-Files.xml
+++ b/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Log-Files.xml
@@ -168,6 +168,15 @@
         default the circular buffer holds the last 4096 log events. The contents of the buffer can
         be viewed via Management. See <xref linkend="Java-Broker-Runtime-Logging-Management-MemoryLogger"/></para>
     </section>
+    <section xml:id="Java-Broker-Runtime-Logging-Loggers-GraylogLogger">
+      <title>GraylogLogger</title>
+      <para><emphasis>GraylogLogger</emphasis> - sends log messages to a Graylog server in
+        <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://docs.graylog.org/en/3.2/pages/gelf.html">GELF format</link> via TCP.
+        The hostname and port number of the Graylog server has to be configured. The content of the log messages is also configurable.</para>
+      <para>The Broker has to be built using option <code>-Dgraylog</code> to support Graylog logger, please visit
+        <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://github.com/apache/qpid-broker-j/blob/master/doc/developer-guide/src/main/markdown/build-instructions.md#maven-commands">build instructions</link>.
+      </para>
+    </section>
   </section>
   <section xml:id="Java-Broker-Runtime-Logging-InclusionRules">
     <title>Inclusion Rules</title>
diff --git a/pom.xml b/pom.xml
index bafdcdc..2362bb0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -359,6 +359,12 @@
 
       <dependency>
         <groupId>org.apache.qpid</groupId>
+        <artifactId>qpid-broker-plugins-graylog-logging-logback</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.qpid</groupId>
         <artifactId>qpid-broker-plugins-memory-store</artifactId>
         <version>${project.version}</version>
       </dependency>
@@ -643,6 +649,7 @@
         <artifactId>dgrid</artifactId>
         <version>${dgrid-version}</version>
       </dependency>
+
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
@@ -1623,6 +1630,27 @@
         </plugins>
       </build>
     </profile>
+
+    <profile>
+      <id>graylog</id>
+      <activation>
+        <property>
+          <name>graylog</name>
+        </property>
+      </activation>
+
+      <modules>
+        <module>broker-plugins/graylog-logging-logback</module>
+      </modules>
+      <properties>
+        <graylog/>
+        <profile>java-mms.1-0</profile>
+        <profile.broker.version>1.0</profile.broker.version>
+        <profile.virtualhostnode.type>Memory</profile.virtualhostnode.type>
+        <profile.virtualhostnode.context.blueprint>{"type":"ProvidedStore","globalAddressDomains":"${dollar.sign}{qpid.globalAddressDomains}"}</profile.virtualhostnode.context.blueprint>
+        <profile.qpid.tests.mms.messagestore.persistence>true</profile.qpid.tests.mms.messagestore.persistence>
+      </properties>
+    </profile>
   </profiles>
 </project>
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org