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    *   http://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.ChannelPromise;
25  import io.netty.util.internal.logging.InternalLogLevel;
26  import io.netty.util.internal.logging.InternalLogger;
27  import io.netty.util.internal.logging.InternalLoggerFactory;
28  
29  import java.net.SocketAddress;
30  
31  import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
32  import static io.netty.util.internal.StringUtil.NEWLINE;
33  
34  /**
35   * A {@link ChannelHandler} that logs all events using a logging framework.
36   * By default, all events are logged at <tt>DEBUG</tt> level.
37   */
38  @Sharable
39  public class LoggingHandler extends ChannelDuplexHandler {
40  
41      private static final LogLevel DEFAULT_LEVEL = LogLevel.DEBUG;
42  
43      protected final InternalLogger logger;
44      protected final InternalLogLevel internalLevel;
45  
46      private final LogLevel level;
47  
48      /**
49       * Creates a new instance whose logger name is the fully qualified class
50       * name of the instance with hex dump enabled.
51       */
52      public LoggingHandler() {
53          this(DEFAULT_LEVEL);
54      }
55  
56      /**
57       * Creates a new instance whose logger name is the fully qualified class
58       * name of the instance.
59       *
60       * @param level   the log level
61       */
62      public LoggingHandler(LogLevel level) {
63          if (level == null) {
64              throw new NullPointerException("level");
65          }
66  
67          logger = InternalLoggerFactory.getInstance(getClass());
68          this.level = level;
69          internalLevel = level.toInternalLevel();
70      }
71  
72      /**
73       * Creates a new instance with the specified logger name and with hex dump
74       * enabled.
75       */
76      public LoggingHandler(Class<?> clazz) {
77          this(clazz, DEFAULT_LEVEL);
78      }
79  
80      /**
81       * Creates a new instance with the specified logger name.
82       *
83       * @param level   the log level
84       */
85      public LoggingHandler(Class<?> clazz, LogLevel level) {
86          if (clazz == null) {
87              throw new NullPointerException("clazz");
88          }
89          if (level == null) {
90              throw new NullPointerException("level");
91          }
92          logger = InternalLoggerFactory.getInstance(clazz);
93          this.level = level;
94          internalLevel = level.toInternalLevel();
95      }
96  
97      /**
98       * Creates a new instance with the specified logger name.
99       */
100     public LoggingHandler(String name) {
101         this(name, DEFAULT_LEVEL);
102     }
103 
104     /**
105      * Creates a new instance with the specified logger name.
106      *
107      * @param level   the log level
108      */
109     public LoggingHandler(String name, LogLevel level) {
110         if (name == null) {
111             throw new NullPointerException("name");
112         }
113         if (level == null) {
114             throw new NullPointerException("level");
115         }
116         logger = InternalLoggerFactory.getInstance(name);
117         this.level = level;
118         internalLevel = level.toInternalLevel();
119     }
120 
121     /**
122      * Returns the {@link LogLevel} that this handler uses to log
123      */
124     public LogLevel level() {
125         return level;
126     }
127 
128     protected String format(ChannelHandlerContext ctx, String message) {
129         String chStr = ctx.channel().toString();
130         return new StringBuilder(chStr.length() + message.length() + 1)
131         .append(chStr)
132         .append(' ')
133         .append(message)
134         .toString();
135     }
136 
137     @Override
138     public void channelRegistered(ChannelHandlerContext ctx)
139             throws Exception {
140         if (logger.isEnabled(internalLevel)) {
141             logger.log(internalLevel, format(ctx, "REGISTERED"));
142         }
143         super.channelRegistered(ctx);
144     }
145 
146     @Override
147     public void channelUnregistered(ChannelHandlerContext ctx)
148             throws Exception {
149         if (logger.isEnabled(internalLevel)) {
150             logger.log(internalLevel, format(ctx, "UNREGISTERED"));
151         }
152         super.channelUnregistered(ctx);
153     }
154 
155     @Override
156     public void channelActive(ChannelHandlerContext ctx)
157             throws Exception {
158         if (logger.isEnabled(internalLevel)) {
159             logger.log(internalLevel, format(ctx, "ACTIVE"));
160         }
161         super.channelActive(ctx);
162     }
163 
164     @Override
165     public void channelInactive(ChannelHandlerContext ctx)
166             throws Exception {
167         if (logger.isEnabled(internalLevel)) {
168             logger.log(internalLevel, format(ctx, "INACTIVE"));
169         }
170         super.channelInactive(ctx);
171     }
172 
173     @Override
174     public void exceptionCaught(ChannelHandlerContext ctx,
175             Throwable cause) throws Exception {
176         if (logger.isEnabled(internalLevel)) {
177             logger.log(internalLevel, format(ctx, "EXCEPTION: " + cause), cause);
178         }
179         super.exceptionCaught(ctx, cause);
180     }
181 
182     @Override
183     public void userEventTriggered(ChannelHandlerContext ctx,
184             Object evt) throws Exception {
185         if (logger.isEnabled(internalLevel)) {
186             logger.log(internalLevel, format(ctx, "USER_EVENT: " + evt));
187         }
188         super.userEventTriggered(ctx, evt);
189     }
190 
191     @Override
192     public void bind(ChannelHandlerContext ctx,
193             SocketAddress localAddress, ChannelPromise promise) throws Exception {
194         if (logger.isEnabled(internalLevel)) {
195             logger.log(internalLevel, format(ctx, "BIND(" + localAddress + ')'));
196         }
197         super.bind(ctx, localAddress, promise);
198     }
199 
200     @Override
201     public void connect(ChannelHandlerContext ctx,
202             SocketAddress remoteAddress, SocketAddress localAddress,
203             ChannelPromise promise) throws Exception {
204         if (logger.isEnabled(internalLevel)) {
205             logger.log(internalLevel, format(ctx, "CONNECT(" + remoteAddress + ", " + localAddress + ')'));
206         }
207         super.connect(ctx, remoteAddress, localAddress, promise);
208     }
209 
210     @Override
211     public void disconnect(ChannelHandlerContext ctx,
212             ChannelPromise promise) throws Exception {
213         if (logger.isEnabled(internalLevel)) {
214             logger.log(internalLevel, format(ctx, "DISCONNECT()"));
215         }
216         super.disconnect(ctx, promise);
217     }
218 
219     @Override
220     public void close(ChannelHandlerContext ctx,
221             ChannelPromise promise) throws Exception {
222         if (logger.isEnabled(internalLevel)) {
223             logger.log(internalLevel, format(ctx, "CLOSE()"));
224         }
225         super.close(ctx, promise);
226     }
227 
228     @Override
229     public void deregister(ChannelHandlerContext ctx,
230              ChannelPromise promise) throws Exception {
231         if (logger.isEnabled(internalLevel)) {
232             logger.log(internalLevel, format(ctx, "DEREGISTER()"));
233         }
234         super.deregister(ctx, promise);
235     }
236 
237     @Override
238     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
239         if (logger.isEnabled(internalLevel)) {
240             logger.log(internalLevel, format(ctx, "READ COMPLETE"));
241         }
242         ctx.fireChannelReadComplete();
243     }
244 
245     @Override
246     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
247         logMessage(ctx, "READ", msg);
248         ctx.fireChannelRead(msg);
249     }
250 
251     @Override
252     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
253         logMessage(ctx, "WRITE", msg);
254         ctx.write(msg, promise);
255     }
256 
257     @Override
258     public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
259         if (logger.isEnabled(internalLevel)) {
260             logger.log(internalLevel, format(ctx, "WRITABILITY CHANGED"));
261         }
262         ctx.fireChannelWritabilityChanged();
263     }
264 
265     @Override
266     public void flush(ChannelHandlerContext ctx) throws Exception {
267         if (logger.isEnabled(internalLevel)) {
268             logger.log(internalLevel, format(ctx, "FLUSH"));
269         }
270         ctx.flush();
271     }
272 
273     private void logMessage(ChannelHandlerContext ctx, String eventName, Object msg) {
274         if (logger.isEnabled(internalLevel)) {
275             logger.log(internalLevel, format(ctx, formatMessage(eventName, msg)));
276         }
277     }
278 
279     protected String formatMessage(String eventName, Object msg) {
280         if (msg instanceof ByteBuf) {
281             return formatByteBuf(eventName, (ByteBuf) msg);
282         } else if (msg instanceof ByteBufHolder) {
283             return formatByteBufHolder(eventName, (ByteBufHolder) msg);
284         } else {
285             return formatNonByteBuf(eventName, msg);
286         }
287     }
288 
289     /**
290      * Returns a String which contains all details to log the {@link ByteBuf}
291      */
292     protected String formatByteBuf(String eventName, ByteBuf msg) {
293         int length = msg.readableBytes();
294         if (length == 0) {
295             StringBuilder buf = new StringBuilder(eventName.length() + 4);
296             buf.append(eventName).append(": 0B");
297             return buf.toString();
298         } else {
299             int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
300             StringBuilder buf = new StringBuilder(eventName.length() + 2 + 10 + 1 + 2 + rows * 80);
301 
302             buf.append(eventName).append(": ").append(length).append('B').append(NEWLINE);
303             appendPrettyHexDump(buf, msg);
304 
305             return buf.toString();
306         }
307     }
308 
309     /**
310      * Returns a String which contains all details to log the {@link Object}
311      */
312     protected String formatNonByteBuf(String eventName, Object msg) {
313         return eventName + ": " + msg;
314     }
315 
316     /**
317      * Returns a String which contains all details to log the {@link ByteBufHolder}.
318      *
319      * By default this method just delegates to {@link #formatByteBuf(String, ByteBuf)},
320      * using the content of the {@link ByteBufHolder}. Sub-classes may override this.
321      */
322     protected String formatByteBufHolder(String eventName, ByteBufHolder msg) {
323         String msgStr = msg.toString();
324         ByteBuf content = msg.content();
325         int length = content.readableBytes();
326         if (length == 0) {
327             StringBuilder buf = new StringBuilder(eventName.length() + 2 + msgStr.length() + 4);
328             buf.append(eventName).append(", ").append(msgStr).append(", 0B");
329             return buf.toString();
330         } else {
331             int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
332             StringBuilder buf = new StringBuilder(
333                     eventName.length() + 2 + msgStr.length() + 2 + 10 + 1 + 2 + rows * 80);
334 
335             buf.append(eventName).append(": ")
336                .append(msgStr).append(", ").append(length).append('B').append(NEWLINE);
337             appendPrettyHexDump(buf, content);
338 
339             return buf.toString();
340         }
341     }
342 }
343