diff --git a/handler/src/main/java/io/netty/handler/logging/ByteBufFormat.java b/handler/src/main/java/io/netty/handler/logging/ByteBufFormat.java
new file mode 100644
index 0000000000..c643afdde0
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/logging/ByteBufFormat.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Netty Project
+ *
+ * The Netty Project 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 io.netty.handler.logging;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufHolder;
+import io.netty.buffer.ByteBufUtil;
+
+/**
+ * Used to control the format and verbosity of logging for {@link ByteBuf}s and {@link ByteBufHolder}s.
+ *
+ * @see LoggingHandler
+ */
+public enum ByteBufFormat {
+ /**
+ * {@link ByteBuf}s will be logged in a simple format, with no hex dump included.
+ */
+ SIMPLE,
+ /**
+ * {@link ByteBuf}s will be logged using {@link ByteBufUtil#appendPrettyHexDump(StringBuilder, ByteBuf)}.
+ */
+ HEX_DUMP
+}
diff --git a/handler/src/main/java/io/netty/handler/logging/LoggingHandler.java b/handler/src/main/java/io/netty/handler/logging/LoggingHandler.java
index 5b353ea62f..81fc0aefba 100644
--- a/handler/src/main/java/io/netty/handler/logging/LoggingHandler.java
+++ b/handler/src/main/java/io/netty/handler/logging/LoggingHandler.java
@@ -33,7 +33,7 @@ import static java.util.Objects.requireNonNull;
/**
* A {@link ChannelHandler} that logs all events using a logging framework.
- * By default, all events are logged at DEBUG level.
+ * By default, all events are logged at DEBUG level and full hex dumps are recorded for ByteBufs.
*/
@Sharable
@SuppressWarnings({ "StringConcatenationInsideStringBufferAppend", "StringBufferReplaceableByString" })
@@ -45,6 +45,7 @@ public class LoggingHandler implements ChannelHandler {
protected final InternalLogLevel internalLevel;
private final LogLevel level;
+ private final ByteBufFormat byteBufFormat;
/**
* Creates a new instance whose logger name is the fully qualified class
@@ -61,10 +62,20 @@ public class LoggingHandler implements ChannelHandler {
* @param level the log level
*/
public LoggingHandler(LogLevel level) {
- requireNonNull(level, "level");
+ this(level, ByteBufFormat.HEX_DUMP);
+ }
+ /**
+ * Creates a new instance whose logger name is the fully qualified class
+ * name of the instance.
+ *
+ * @param level the log level
+ * @param byteBufFormat the ByteBuf format
+ */
+ public LoggingHandler(LogLevel level, ByteBufFormat byteBufFormat) {
+ this.level = requireNonNull(level, "level");
+ this.byteBufFormat = requireNonNull(byteBufFormat, "byteBufFormat");
logger = InternalLoggerFactory.getInstance(getClass());
- this.level = level;
internalLevel = level.toInternalLevel();
}
@@ -85,11 +96,21 @@ public class LoggingHandler implements ChannelHandler {
* @param level the log level
*/
public LoggingHandler(Class> clazz, LogLevel level) {
- requireNonNull(clazz, "clazz");
- requireNonNull(level, "level");
+ this(clazz, level, ByteBufFormat.HEX_DUMP);
+ }
+ /**
+ * Creates a new instance with the specified logger name.
+ *
+ * @param clazz the class type to generate the logger for
+ * @param level the log level
+ * @param byteBufFormat the ByteBuf format
+ */
+ public LoggingHandler(Class> clazz, LogLevel level, ByteBufFormat byteBufFormat) {
+ requireNonNull(clazz, "clazz");
+ this.level = requireNonNull(level, "level");
+ this.byteBufFormat = requireNonNull(byteBufFormat, "byteBufFormat");
logger = InternalLoggerFactory.getInstance(clazz);
- this.level = level;
internalLevel = level.toInternalLevel();
}
@@ -109,11 +130,22 @@ public class LoggingHandler implements ChannelHandler {
* @param level the log level
*/
public LoggingHandler(String name, LogLevel level) {
- requireNonNull(name, "name");
- requireNonNull(level, "level");
+ this(name, level, ByteBufFormat.HEX_DUMP);
+ }
+ /**
+ * Creates a new instance with the specified logger name.
+ *
+ * @param name the name of the class to use for the logger
+ * @param level the log level
+ * @param byteBufFormat the ByteBuf format
+ */
+ public LoggingHandler(String name, LogLevel level, ByteBufFormat byteBufFormat) {
+ requireNonNull(name, "name");
+
+ this.level = requireNonNull(level, "level");
+ this.byteBufFormat = requireNonNull(byteBufFormat, "byteBufFormat");
logger = InternalLoggerFactory.getInstance(name);
- this.level = level;
internalLevel = level.toInternalLevel();
}
@@ -124,6 +156,13 @@ public class LoggingHandler implements ChannelHandler {
return level;
}
+ /**
+ * Returns the {@link ByteBufFormat} that this handler uses to log
+ */
+ public ByteBufFormat byteBufFormat() {
+ return byteBufFormat;
+ }
+
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (logger.isEnabled(internalLevel)) {
@@ -309,7 +348,7 @@ public class LoggingHandler implements ChannelHandler {
/**
* Generates the default log message of the specified event whose argument is a {@link ByteBuf}.
*/
- private static String formatByteBuf(ChannelHandlerContext ctx, String eventName, ByteBuf msg) {
+ private String formatByteBuf(ChannelHandlerContext ctx, String eventName, ByteBuf msg) {
String chStr = ctx.channel().toString();
int length = msg.readableBytes();
if (length == 0) {
@@ -317,11 +356,18 @@ public class LoggingHandler implements ChannelHandler {
buf.append(chStr).append(' ').append(eventName).append(": 0B");
return buf.toString();
} else {
- int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
- StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + 10 + 1 + 2 + rows * 80);
-
- buf.append(chStr).append(' ').append(eventName).append(": ").append(length).append('B').append(NEWLINE);
- appendPrettyHexDump(buf, msg);
+ int outputLength = chStr.length() + 1 + eventName.length() + 2 + 10 + 1;
+ if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
+ int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
+ int hexDumpLength = 2 + rows * 80;
+ outputLength += hexDumpLength;
+ }
+ StringBuilder buf = new StringBuilder(outputLength);
+ buf.append(chStr).append(' ').append(eventName).append(": ").append(length).append('B');
+ if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
+ buf.append(NEWLINE);
+ appendPrettyHexDump(buf, msg);
+ }
return buf.toString();
}
@@ -330,7 +376,7 @@ public class LoggingHandler implements ChannelHandler {
/**
* Generates the default log message of the specified event whose argument is a {@link ByteBufHolder}.
*/
- private static String formatByteBufHolder(ChannelHandlerContext ctx, String eventName, ByteBufHolder msg) {
+ private String formatByteBufHolder(ChannelHandlerContext ctx, String eventName, ByteBufHolder msg) {
String chStr = ctx.channel().toString();
String msgStr = msg.toString();
ByteBuf content = msg.content();
@@ -340,13 +386,19 @@ public class LoggingHandler implements ChannelHandler {
buf.append(chStr).append(' ').append(eventName).append(", ").append(msgStr).append(", 0B");
return buf.toString();
} else {
- int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
- StringBuilder buf = new StringBuilder(
- chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 2 + 10 + 1 + 2 + rows * 80);
-
+ int outputLength = chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 2 + 10 + 1;
+ if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
+ int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
+ int hexDumpLength = 2 + rows * 80;
+ outputLength += hexDumpLength;
+ }
+ StringBuilder buf = new StringBuilder(outputLength);
buf.append(chStr).append(' ').append(eventName).append(": ")
- .append(msgStr).append(", ").append(length).append('B').append(NEWLINE);
- appendPrettyHexDump(buf, content);
+ .append(msgStr).append(", ").append(length).append('B');
+ if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
+ buf.append(NEWLINE);
+ appendPrettyHexDump(buf, content);
+ }
return buf.toString();
}
diff --git a/handler/src/test/java/io/netty/handler/logging/LoggingHandlerTest.java b/handler/src/test/java/io/netty/handler/logging/LoggingHandlerTest.java
index 751902146f..31d1fe0c35 100644
--- a/handler/src/test/java/io/netty/handler/logging/LoggingHandlerTest.java
+++ b/handler/src/test/java/io/netty/handler/logging/LoggingHandlerTest.java
@@ -39,6 +39,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import static io.netty.util.internal.StringUtil.NEWLINE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
@@ -221,7 +222,20 @@ public class LoggingHandlerTest {
ByteBuf msg = Unpooled.copiedBuffer("hello", CharsetUtil.UTF_8);
EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler());
channel.writeInbound(msg);
- verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: " + msg.readableBytes() + "B$")));
+ verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: " + msg.readableBytes() + "B$", true)));
+
+ ByteBuf handledMsg = channel.readInbound();
+ assertThat(msg, is(sameInstance(handledMsg)));
+ handledMsg.release();
+ assertThat(channel.readInbound(), is(nullValue()));
+ }
+
+ @Test
+ public void shouldLogByteBufDataReadWithSimpleFormat() throws Exception {
+ ByteBuf msg = Unpooled.copiedBuffer("hello", CharsetUtil.UTF_8);
+ EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler(LogLevel.DEBUG, ByteBufFormat.SIMPLE));
+ channel.writeInbound(msg);
+ verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: " + msg.readableBytes() + "B$", false)));
ByteBuf handledMsg = channel.readInbound();
assertThat(msg, is(sameInstance(handledMsg)));
@@ -234,7 +248,7 @@ public class LoggingHandlerTest {
ByteBuf msg = Unpooled.EMPTY_BUFFER;
EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler());
channel.writeInbound(msg);
- verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: 0B$")));
+ verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: 0B$", false)));
ByteBuf handledMsg = channel.readInbound();
assertThat(msg, is(sameInstance(handledMsg)));
@@ -252,7 +266,7 @@ public class LoggingHandlerTest {
EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler());
channel.writeInbound(msg);
- verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: foobar, 5B$")));
+ verify(appender).doAppend(argThat(new RegexLogMatcher(".+READ: foobar, 5B$", true)));
ByteBufHolder handledMsg = channel.readInbound();
assertThat(msg, is(sameInstance(handledMsg)));
@@ -274,10 +288,16 @@ public class LoggingHandlerTest {
private static final class RegexLogMatcher implements ArgumentMatcher {
private final String expected;
+ private final boolean shouldContainNewline;
private String actualMsg;
RegexLogMatcher(String expected) {
+ this(expected, false);
+ }
+
+ RegexLogMatcher(String expected, boolean shouldContainNewline) {
this.expected = expected;
+ this.shouldContainNewline = shouldContainNewline;
}
@Override
@@ -285,7 +305,11 @@ public class LoggingHandlerTest {
public boolean matches(ILoggingEvent actual) {
// Match only the first line to skip the validation of hex-dump format.
actualMsg = actual.getMessage().split("(?s)[\\r\\n]+")[0];
- return actualMsg.matches(expected);
+ if (actualMsg.matches(expected)) {
+ // The presence of a newline implies a hex-dump was logged
+ return actual.getMessage().contains(NEWLINE) == shouldContainNewline;
+ }
+ return false;
}
}