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.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     /**
61      * Creates a new instance whose logger name is the fully qualified class
62      * name of the instance.
63      *
64      * @param level the log level
65      */

66     public LoggingHandler(LogLevel level) {
67         this(level, ByteBufFormat.HEX_DUMP);
68     }
69
70     /**
71      * Creates a new instance whose logger name is the fully qualified class
72      * name of the instance.
73      *
74      * @param level the log level
75      * @param byteBufFormat the ByteBuf format
76      */

77     public LoggingHandler(LogLevel level, ByteBufFormat byteBufFormat) {
78         this.level = ObjectUtil.checkNotNull(level, "level");
79         this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
80         logger = InternalLoggerFactory.getInstance(getClass());
81         internalLevel = level.toInternalLevel();
82     }
83
84     /**
85      * Creates a new instance with the specified logger name and with hex dump
86      * enabled.
87      *
88      * @param clazz the class type to generate the logger for
89      */

90     public LoggingHandler(Class<?> clazz) {
91         this(clazz, DEFAULT_LEVEL);
92     }
93
94     /**
95      * Creates a new instance with the specified logger name.
96      *
97      * @param clazz the class type to generate the logger for
98      * @param level the log level
99      */

100     public LoggingHandler(Class<?> clazz, LogLevel level) {
101         this(clazz, level, ByteBufFormat.HEX_DUMP);
102     }
103
104     /**
105      * Creates a new instance with the specified logger name.
106      *
107      * @param clazz the class type to generate the logger for
108      * @param level the log level
109      * @param byteBufFormat the ByteBuf format
110      */

111     public LoggingHandler(Class<?> clazz, LogLevel level, ByteBufFormat byteBufFormat) {
112         ObjectUtil.checkNotNull(clazz, "clazz");
113         this.level = ObjectUtil.checkNotNull(level, "level");
114         this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
115         logger = InternalLoggerFactory.getInstance(clazz);
116         internalLevel = level.toInternalLevel();
117     }
118
119     /**
120      * Creates a new instance with the specified logger name using the default log level.
121      *
122      * @param name the name of the class to use for the logger
123      */

124     public LoggingHandler(String name) {
125         this(name, DEFAULT_LEVEL);
126     }
127
128     /**
129      * Creates a new instance with the specified logger name.
130      *
131      * @param name the name of the class to use for the logger
132      * @param level the log level
133      */

134     public LoggingHandler(String name, LogLevel level) {
135         this(name, level, ByteBufFormat.HEX_DUMP);
136     }
137
138     /**
139      * Creates a new instance with the specified logger name.
140      *
141      * @param name the name of the class to use for the logger
142      * @param level the log level
143      * @param byteBufFormat the ByteBuf format
144      */

145     public LoggingHandler(String name, LogLevel level, ByteBufFormat byteBufFormat) {
146         ObjectUtil.checkNotNull(name, "name");
147
148         this.level = ObjectUtil.checkNotNull(level, "level");
149         this.byteBufFormat = ObjectUtil.checkNotNull(byteBufFormat, "byteBufFormat");
150         logger = InternalLoggerFactory.getInstance(name);
151         internalLevel = level.toInternalLevel();
152     }
153
154     /**
155      * Returns the {@link LogLevel} that this handler uses to log
156      */

157     public LogLevel level() {
158         return level;
159     }
160
161     /**
162      * Returns the {@link ByteBufFormat} that this handler uses to log
163      */

164     public ByteBufFormat byteBufFormat() {
165         return byteBufFormat;
166     }
167
168     @Override
169     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
170         if (logger.isEnabled(internalLevel)) {
171             logger.log(internalLevel, format(ctx, "REGISTERED"));
172         }
173         ctx.fireChannelRegistered();
174     }
175
176     @Override
177     public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
178         if (logger.isEnabled(internalLevel)) {
179             logger.log(internalLevel, format(ctx, "UNREGISTERED"));
180         }
181         ctx.fireChannelUnregistered();
182     }
183
184     @Override
185     public void channelActive(ChannelHandlerContext ctx) throws Exception {
186         if (logger.isEnabled(internalLevel)) {
187             logger.log(internalLevel, format(ctx, "ACTIVE"));
188         }
189         ctx.fireChannelActive();
190     }
191
192     @Override
193     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
194         if (logger.isEnabled(internalLevel)) {
195             logger.log(internalLevel, format(ctx, "INACTIVE"));
196         }
197         ctx.fireChannelInactive();
198     }
199
200     @Override
201     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
202         if (logger.isEnabled(internalLevel)) {
203             logger.log(internalLevel, format(ctx, "EXCEPTION", cause), cause);
204         }
205         ctx.fireExceptionCaught(cause);
206     }
207
208     @Override
209     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
210         if (logger.isEnabled(internalLevel)) {
211             logger.log(internalLevel, format(ctx, "USER_EVENT", evt));
212         }
213         ctx.fireUserEventTriggered(evt);
214     }
215
216     @Override
217     public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
218         if (logger.isEnabled(internalLevel)) {
219             logger.log(internalLevel, format(ctx, "BIND", localAddress));
220         }
221         ctx.bind(localAddress, promise);
222     }
223
224     @Override
225     public void connect(
226             ChannelHandlerContext ctx,
227             SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
228         if (logger.isEnabled(internalLevel)) {
229             logger.log(internalLevel, format(ctx, "CONNECT", remoteAddress, localAddress));
230         }
231         ctx.connect(remoteAddress, localAddress, promise);
232     }
233
234     @Override
235     public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
236         if (logger.isEnabled(internalLevel)) {
237             logger.log(internalLevel, format(ctx, "DISCONNECT"));
238         }
239         ctx.disconnect(promise);
240     }
241
242     @Override
243     public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
244         if (logger.isEnabled(internalLevel)) {
245             logger.log(internalLevel, format(ctx, "CLOSE"));
246         }
247         ctx.close(promise);
248     }
249
250     @Override
251     public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
252         if (logger.isEnabled(internalLevel)) {
253             logger.log(internalLevel, format(ctx, "DEREGISTER"));
254         }
255         ctx.deregister(promise);
256     }
257
258     @Override
259     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
260         if (logger.isEnabled(internalLevel)) {
261             logger.log(internalLevel, format(ctx, "READ COMPLETE"));
262         }
263         ctx.fireChannelReadComplete();
264     }
265
266     @Override
267     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
268         if (logger.isEnabled(internalLevel)) {
269             logger.log(internalLevel, format(ctx, "READ", msg));
270         }
271         ctx.fireChannelRead(msg);
272     }
273
274     @Override
275     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
276         if (logger.isEnabled(internalLevel)) {
277             logger.log(internalLevel, format(ctx, "WRITE", msg));
278         }
279         ctx.write(msg, promise);
280     }
281
282     @Override
283     public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
284         if (logger.isEnabled(internalLevel)) {
285             logger.log(internalLevel, format(ctx, "WRITABILITY CHANGED"));
286         }
287         ctx.fireChannelWritabilityChanged();
288     }
289
290     @Override
291     public void flush(ChannelHandlerContext ctx) throws Exception {
292         if (logger.isEnabled(internalLevel)) {
293             logger.log(internalLevel, format(ctx, "FLUSH"));
294         }
295         ctx.flush();
296     }
297
298     /**
299      * Formats an event and returns the formatted message.
300      *
301      * @param eventName the name of the event
302      */

303     protected String format(ChannelHandlerContext ctx, String eventName) {
304         String chStr = ctx.channel().toString();
305         return new StringBuilder(chStr.length() + 1 + eventName.length())
306             .append(chStr)
307             .append(' ')
308             .append(eventName)
309             .toString();
310     }
311
312     /**
313      * Formats an event and returns the formatted message.
314      *
315      * @param eventName the name of the event
316      * @param arg       the argument of the event
317      */

318     protected String format(ChannelHandlerContext ctx, String eventName, Object arg) {
319         if (arg instanceof ByteBuf) {
320             return formatByteBuf(ctx, eventName, (ByteBuf) arg);
321         } else if (arg instanceof ByteBufHolder) {
322             return formatByteBufHolder(ctx, eventName, (ByteBufHolder) arg);
323         } else {
324             return formatSimple(ctx, eventName, arg);
325         }
326     }
327
328     /**
329      * Formats an event and returns the formatted message.  This method is currently only used for formatting
330      * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}.
331      *
332      * @param eventName the name of the event
333      * @param firstArg  the first argument of the event
334      * @param secondArg the second argument of the event
335      */

336     protected String format(ChannelHandlerContext ctx, String eventName, Object firstArg, Object secondArg) {
337         if (secondArg == null) {
338             return formatSimple(ctx, eventName, firstArg);
339         }
340
341         String chStr = ctx.channel().toString();
342         String arg1Str = String.valueOf(firstArg);
343         String arg2Str = secondArg.toString();
344         StringBuilder buf = new StringBuilder(
345                 chStr.length() + 1 + eventName.length() + 2 + arg1Str.length() + 2 + arg2Str.length());
346         buf.append(chStr).append(' ').append(eventName).append(": ").append(arg1Str).append(", ").append(arg2Str);
347         return buf.toString();
348     }
349
350     /**
351      * Generates the default log message of the specified event whose argument is a {@link ByteBuf}.
352      */

353     private String formatByteBuf(ChannelHandlerContext ctx, String eventName, ByteBuf msg) {
354         String chStr = ctx.channel().toString();
355         int length = msg.readableBytes();
356         if (length == 0) {
357             StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 4);
358             buf.append(chStr).append(' ').append(eventName).append(": 0B");
359             return buf.toString();
360         } else {
361             int outputLength = chStr.length() + 1 + eventName.length() + 2 + 10 + 1;
362             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
363                 int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
364                 int hexDumpLength = 2 + rows * 80;
365                 outputLength += hexDumpLength;
366             }
367             StringBuilder buf = new StringBuilder(outputLength);
368             buf.append(chStr).append(' ').append(eventName).append(": ").append(length).append('B');
369             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
370                 buf.append(NEWLINE);
371                 appendPrettyHexDump(buf, msg);
372             }
373
374             return buf.toString();
375         }
376     }
377
378     /**
379      * Generates the default log message of the specified event whose argument is a {@link ByteBufHolder}.
380      */

381     private String formatByteBufHolder(ChannelHandlerContext ctx, String eventName, ByteBufHolder msg) {
382         String chStr = ctx.channel().toString();
383         String msgStr = msg.toString();
384         ByteBuf content = msg.content();
385         int length = content.readableBytes();
386         if (length == 0) {
387             StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 4);
388             buf.append(chStr).append(' ').append(eventName).append(", ").append(msgStr).append(", 0B");
389             return buf.toString();
390         } else {
391             int outputLength = chStr.length() + 1 + eventName.length() + 2 + msgStr.length() + 2 + 10 + 1;
392             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
393                 int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
394                 int hexDumpLength = 2 + rows * 80;
395                 outputLength += hexDumpLength;
396             }
397             StringBuilder buf = new StringBuilder(outputLength);
398             buf.append(chStr).append(' ').append(eventName).append(": ")
399                .append(msgStr).append(", ").append(length).append('B');
400             if (byteBufFormat == ByteBufFormat.HEX_DUMP) {
401                 buf.append(NEWLINE);
402                 appendPrettyHexDump(buf, content);
403             }
404
405             return buf.toString();
406         }
407     }
408
409     /**
410      * Generates the default log message of the specified event whose argument is an arbitrary object.
411      */

412     private static String formatSimple(ChannelHandlerContext ctx, String eventName, Object msg) {
413         String chStr = ctx.channel().toString();
414         String msgStr = String.valueOf(msg);
415         StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + msgStr.length());
416         return buf.append(chStr).append(' ').append(eventName).append(": ").append(msgStr).toString();
417     }
418 }
419