View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.logging;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufHolder;
20  import io.netty.channel.ChannelDuplexHandler;
21  import io.netty.channel.ChannelHandler;
22  import io.netty.channel.ChannelHandler.Sharable;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelOutboundHandler;
25  import io.netty.channel.ChannelPromise;
26  import io.netty.util.internal.ObjectUtil;
27  import io.netty.util.internal.logging.InternalLogLevel;
28  import io.netty.util.internal.logging.InternalLogger;
29  import io.netty.util.internal.logging.InternalLoggerFactory;
30  
31  import java.net.SocketAddress;
32  
33  import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
34  import static io.netty.util.internal.StringUtil.NEWLINE;
35  
36  /**
37   * A {@link ChannelHandler} that logs all events using a logging framework.
38   * By default, all events are logged at <tt>DEBUG</tt> level and full hex dumps are recorded for ByteBufs.
39   */
40  @Sharable
41  @SuppressWarnings({ "StringConcatenationInsideStringBufferAppend", "StringBufferReplaceableByString" })
42  public class LoggingHandler extends ChannelDuplexHandler {
43  
44      private static final LogLevel DEFAULT_LEVEL = LogLevel.DEBUG;
45  
46      protected final InternalLogger logger;
47      protected final InternalLogLevel internalLevel;
48  
49      private final LogLevel level;
50      private final ByteBufFormat byteBufFormat;
51  
52      /**
53       * Creates a new instance whose logger name is the fully qualified class
54       * name of the instance with hex dump enabled.
55       */
56      public LoggingHandler() {
57          this(DEFAULT_LEVEL);
58      }
59      /**
60       * Creates a new instance whose logger name is the fully qualified class
61       * name of the instance.
62       *
63       * @param format Format of ByteBuf dumping
64       */
65      public LoggingHandler(ByteBufFormat format) {
66          this(DEFAULT_LEVEL, format);
67      }
68  
69      /**
70       * Creates a new instance whose logger name is the fully qualified class
71       * name of the instance.
72       *
73       * @param level the log level
74       */
75      public LoggingHandler(LogLevel level) {
76          this(level, ByteBufFormat.HEX_DUMP);
77      }
78  
79      /**
80       * Creates a new instance whose logger name is the fully qualified class
81       * name of the instance.
82       *
83       * @param level the log level
84       * @param byteBufFormat the ByteBuf format
85       */
86      public LoggingHandler(LogLevel level, ByteBufFormat byteBufFormat) {
87          this.level = ObjectUtil.checkNotNull(level, "level");
88          this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
89          logger = InternalLoggerFactory.getInstance(getClass());
90          internalLevel = level.toInternalLevel();
91      }
92  
93      /**
94       * Creates a new instance with the specified logger name and with hex dump
95       * enabled.
96       *
97       * @param clazz the class type to generate the logger for
98       */
99      public LoggingHandler(Class<?> clazz) {
100         this(clazz, DEFAULT_LEVEL);
101     }
102 
103     /**
104      * Creates a new instance with the specified logger name.
105      *
106      * @param clazz the class type to generate the logger for
107      * @param level the log level
108      */
109     public LoggingHandler(Class<?> clazz, LogLevel level) {
110         this(clazz, level, ByteBufFormat.HEX_DUMP);
111     }
112 
113     /**
114      * Creates a new instance with the specified logger name.
115      *
116      * @param clazz the class type to generate the logger for
117      * @param level the log level
118      * @param byteBufFormat the ByteBuf format
119      */
120     public LoggingHandler(Class<?> clazz, LogLevel level, ByteBufFormat byteBufFormat) {
121         ObjectUtil.checkNotNull(clazz, "clazz");
122         this.level = ObjectUtil.checkNotNull(level, "level");
123         this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
124         logger = InternalLoggerFactory.getInstance(clazz);
125         internalLevel = level.toInternalLevel();
126     }
127 
128     /**
129      * Creates a new instance with the specified logger name using the default log level.
130      *
131      * @param name the name of the class to use for the logger
132      */
133     public LoggingHandler(String name) {
134         this(name, DEFAULT_LEVEL);
135     }
136 
137     /**
138      * Creates a new instance with the specified logger name.
139      *
140      * @param name the name of the class to use for the logger
141      * @param level the log level
142      */
143     public LoggingHandler(String name, LogLevel level) {
144         this(name, level, ByteBufFormat.HEX_DUMP);
145     }
146 
147     /**
148      * Creates a new instance with the specified logger name.
149      *
150      * @param name the name of the class to use for the logger
151      * @param level the log level
152      * @param byteBufFormat the ByteBuf format
153      */
154     public LoggingHandler(String name, LogLevel level, ByteBufFormat byteBufFormat) {
155         ObjectUtil.checkNotNull(name, "name");
156 
157         this.level = ObjectUtil.checkNotNull(level, "level");
158         this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
159         logger = InternalLoggerFactory.getInstance(name);
160         internalLevel = level.toInternalLevel();
161     }
162 
163     /**
164      * Returns the {@link LogLevel} that this handler uses to log
165      */
166     public LogLevel level() {
167         return level;
168     }
169 
170     /**
171      * Returns the {@link ByteBufFormat} that this handler uses to log
172      */
173     public ByteBufFormat byteBufFormat() {
174         return byteBufFormat;
175     }
176 
177     @Override
178     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
179         if (logger.isEnabled(internalLevel)) {
180             logger.log(internalLevel, format(ctx, "REGISTERED"));
181         }
182         ctx.fireChannelRegistered();
183     }
184 
185     @Override
186     public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
187         if (logger.isEnabled(internalLevel)) {
188             logger.log(internalLevel, format(ctx, "UNREGISTERED"));
189         }
190         ctx.fireChannelUnregistered();
191     }
192 
193     @Override
194     public void channelActive(ChannelHandlerContext ctx) throws Exception {
195         if (logger.isEnabled(internalLevel)) {
196             logger.log(internalLevel, format(ctx, "ACTIVE"));
197         }
198         ctx.fireChannelActive();
199     }
200 
201     @Override
202     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
203         if (logger.isEnabled(internalLevel)) {
204             logger.log(internalLevel, format(ctx, "INACTIVE"));
205         }
206         ctx.fireChannelInactive();
207     }
208 
209     @Override
210     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
211         if (logger.isEnabled(internalLevel)) {
212             logger.log(internalLevel, format(ctx, "EXCEPTION", cause), cause);
213         }
214         ctx.fireExceptionCaught(cause);
215     }
216 
217     @Override
218     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
219         if (logger.isEnabled(internalLevel)) {
220             logger.log(internalLevel, format(ctx, "USER_EVENT", evt));
221         }
222         ctx.fireUserEventTriggered(evt);
223     }
224 
225     @Override
226     public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
227         if (logger.isEnabled(internalLevel)) {
228             logger.log(internalLevel, format(ctx, "BIND", localAddress));
229         }
230         ctx.bind(localAddress, promise);
231     }
232 
233     @Override
234     public void connect(
235             ChannelHandlerContext ctx,
236             SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
237         if (logger.isEnabled(internalLevel)) {
238             logger.log(internalLevel, format(ctx, "CONNECT", remoteAddress, localAddress));
239         }
240         ctx.connect(remoteAddress, localAddress, promise);
241     }
242 
243     @Override
244     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
245         if (logger.isEnabled(internalLevel)) {
246             logger.log(internalLevel, format(ctx, "DISCONNECT"));
247         }
248         ctx.disconnect(promise);
249     }
250 
251     @Override
252     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
253         if (logger.isEnabled(internalLevel)) {
254             logger.log(internalLevel, format(ctx, "CLOSE"));
255         }
256         ctx.close(promise);
257     }
258 
259     @Override
260     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
261         if (logger.isEnabled(internalLevel)) {
262             logger.log(internalLevel, format(ctx, "DEREGISTER"));
263         }
264         ctx.deregister(promise);
265     }
266 
267     @Override
268     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
269         if (logger.isEnabled(internalLevel)) {
270             logger.log(internalLevel, format(ctx, "READ COMPLETE"));
271         }
272         ctx.fireChannelReadComplete();
273     }
274 
275     @Override
276     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
277         if (logger.isEnabled(internalLevel)) {
278             logger.log(internalLevel, format(ctx, "READ", msg));
279         }
280         ctx.fireChannelRead(msg);
281     }
282 
283     @Override
284     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
285         if (logger.isEnabled(internalLevel)) {
286             logger.log(internalLevel, format(ctx, "WRITE", msg));
287         }
288         ctx.write(msg, promise);
289     }
290 
291     @Override
292     public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
293         if (logger.isEnabled(internalLevel)) {
294             logger.log(internalLevel, format(ctx, "WRITABILITY CHANGED"));
295         }
296         ctx.fireChannelWritabilityChanged();
297     }
298 
299     @Override
300     public void flush(ChannelHandlerContext ctx) throws Exception {
301         if (logger.isEnabled(internalLevel)) {
302             logger.log(internalLevel, format(ctx, "FLUSH"));
303         }
304         ctx.flush();
305     }
306 
307     /**
308      * Formats an event and returns the formatted message.
309      *
310      * @param eventName the name of the event
311      */
312     protected String format(ChannelHandlerContext ctx, String eventName) {
313         String chStr = ctx.channel().toString();
314         return new StringBuilder(chStr.length() + 1 + eventName.length())
315             .append(chStr)
316             .append(' ')
317             .append(eventName)
318             .toString();
319     }
320 
321     /**
322      * Formats an event and returns the formatted message.
323      *
324      * @param eventName the name of the event
325      * @param arg       the argument of the event
326      */
327     protected String format(ChannelHandlerContext ctx, String eventName, Object arg) {
328         if (arg instanceof ByteBuf) {
329             return formatByteBuf(ctx, eventName, (ByteBuf) arg);
330         } else if (arg instanceof ByteBufHolder) {
331             return formatByteBufHolder(ctx, eventName, (ByteBufHolder) arg);
332         } else {
333             return formatSimple(ctx, eventName, arg);
334         }
335     }
336 
337     /**
338      * Formats an event and returns the formatted message.  This method is currently only used for formatting
339      * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}.
340      *
341      * @param eventName the name of the event
342      * @param firstArg  the first argument of the event
343      * @param secondArg the second argument of the event
344      */
345     protected String format(ChannelHandlerContext ctx, String eventName, Object firstArg, Object secondArg) {
346         if (secondArg == null) {
347             return formatSimple(ctx, eventName, firstArg);
348         }
349 
350         String chStr = ctx.channel().toString();
351         String arg1Str = String.valueOf(firstArg);
352         String arg2Str = secondArg.toString();
353         StringBuilder buf = new StringBuilder(
354                 chStr.length() + 1 + eventName.length() + 2 + arg1Str.length() + 2 + arg2Str.length());
355         buf.append(chStr).append(' ').append(eventName).append(": ").append(arg1Str).append(", ").append(arg2Str);
356         return buf.toString();
357     }
358 
359     /**
360      * Generates the default log message of the specified event whose argument is a {@link ByteBuf}.
361      */
362     private String formatByteBuf(ChannelHandlerContext ctx, String eventName, ByteBuf msg) {
363         String chStr = ctx.channel().toString();
364         int length = msg.readableBytes();
365         if (length == 0) {
366             StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 4);
367             buf.append(chStr).append(' ').append(eventName).append(": 0B");
368             return buf.toString();
369         } else {
370             int outputLength = chStr.length() + 1 + eventName.length() + 2 + 10 + 1;
371             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
372                 int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
373                 int hexDumpLength = 2 + rows * 80;
374                 outputLength += hexDumpLength;
375             }
376             StringBuilder buf = new StringBuilder(outputLength);
377             buf.append(chStr).append(' ').append(eventName).append(": ").append(length).append('B');
378             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
379                 buf.append(NEWLINE);
380                 appendPrettyHexDump(buf, msg);
381             }
382 
383             return buf.toString();
384         }
385     }
386 
387     /**
388      * Generates the default log message of the specified event whose argument is a {@link ByteBufHolder}.
389      */
390     private String formatByteBufHolder(ChannelHandlerContext ctx, String eventName, ByteBufHolder msg) {
391         String chStr = ctx.channel().toString();
392         String msgStr = msg.toString();
393         ByteBuf content = msg.content();
394         int length = content.readableBytes();
395         if (length == 0) {
396             StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 4);
397             buf.append(chStr).append(' ').append(eventName).append(", ").append(msgStr).append(", 0B");
398             return buf.toString();
399         } else {
400             int outputLength = chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 2 + 10 + 1;
401             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
402                 int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
403                 int hexDumpLength = 2 + rows * 80;
404                 outputLength += hexDumpLength;
405             }
406             StringBuilder buf = new StringBuilder(outputLength);
407             buf.append(chStr).append(' ').append(eventName).append(": ")
408                .append(msgStr).append(", ").append(length).append('B');
409             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
410                 buf.append(NEWLINE);
411                 appendPrettyHexDump(buf, content);
412             }
413 
414             return buf.toString();
415         }
416     }
417 
418     /**
419      * Generates the default log message of the specified event whose argument is an arbitrary object.
420      */
421     private static String formatSimple(ChannelHandlerContext ctx, String eventName, Object msg) {
422         String chStr = ctx.channel().toString();
423         String msgStr = String.valueOf(msg);
424         StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + msgStr.length());
425         return buf.append(chStr).append(' ').append(eventName).append(": ").append(msgStr).toString();
426     }
427 }