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