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.netty5.handler.logging;
17  
18  import io.netty5.buffer.BufferUtil;
19  import io.netty5.buffer.api.Buffer;
20  import io.netty5.channel.ChannelHandler;
21  import io.netty5.channel.ChannelHandlerContext;
22  import io.netty5.util.concurrent.Future;
23  import io.netty5.util.internal.logging.InternalLogLevel;
24  import io.netty5.util.internal.logging.InternalLogger;
25  import io.netty5.util.internal.logging.InternalLoggerFactory;
26  
27  import java.net.SocketAddress;
28  
29  import static io.netty5.util.internal.StringUtil.NEWLINE;
30  import static java.util.Objects.requireNonNull;
31  
32  /**
33   * A {@link ChannelHandler} that logs all events using a logging framework.
34   * By default, all events are logged at <tt>DEBUG</tt> level and full hex dumps are recorded for ByteBufs.
35   */
36  @SuppressWarnings("StringBufferReplaceableByString")
37  public class LoggingHandler implements ChannelHandler {
38  
39      private static final LogLevel DEFAULT_LEVEL = LogLevel.DEBUG;
40  
41      protected final InternalLogger logger;
42      protected final InternalLogLevel internalLevel;
43  
44      private final LogLevel level;
45      private final BufferFormat bufferFormat;
46  
47      /**
48       * Creates a new instance whose logger name is the fully qualified class
49       * name of the instance with hex dump enabled.
50       */
51      public LoggingHandler() {
52          this(DEFAULT_LEVEL);
53      }
54      /**
55       * Creates a new instance whose logger name is the fully qualified class
56       * name of the instance.
57       *
58       * @param format Format of ByteBuf dumping
59       */
60      public LoggingHandler(BufferFormat format) {
61          this(DEFAULT_LEVEL, format);
62      }
63  
64      /**
65       * Creates a new instance whose logger name is the fully qualified class
66       * name of the instance.
67       *
68       * @param level the log level
69       */
70      public LoggingHandler(LogLevel level) {
71          this(level, BufferFormat.HEX_DUMP);
72      }
73  
74      /**
75       * Creates a new instance whose logger name is the fully qualified class
76       * name of the instance.
77       *
78       * @param level the log level
79       * @param bufferFormat the ByteBuf format
80       */
81      public LoggingHandler(LogLevel level, BufferFormat bufferFormat) {
82          this.level = requireNonNull(level, "level");
83          this.bufferFormat = requireNonNull(bufferFormat, "bufferFormat");
84          logger = InternalLoggerFactory.getInstance(getClass());
85          internalLevel = level.toInternalLevel();
86      }
87  
88      /**
89       * Creates a new instance with the specified logger name and with hex dump
90       * enabled.
91       *
92       * @param clazz the class type to generate the logger for
93       */
94      public LoggingHandler(Class<?> clazz) {
95          this(clazz, DEFAULT_LEVEL);
96      }
97  
98      /**
99       * Creates a new instance with the specified logger name.
100      *
101      * @param clazz the class type to generate the logger for
102      * @param level the log level
103      */
104     public LoggingHandler(Class<?> clazz, LogLevel level) {
105         this(clazz, level, BufferFormat.HEX_DUMP);
106     }
107 
108     /**
109      * Creates a new instance with the specified logger name.
110      *
111      * @param clazz the class type to generate the logger for
112      * @param level the log level
113      * @param bufferFormat the ByteBuf format
114      */
115     public LoggingHandler(Class<?> clazz, LogLevel level, BufferFormat bufferFormat) {
116         requireNonNull(clazz, "clazz");
117         this.level = requireNonNull(level, "level");
118         this.bufferFormat = requireNonNull(bufferFormat, "bufferFormat");
119         logger = InternalLoggerFactory.getInstance(clazz);
120         internalLevel = level.toInternalLevel();
121     }
122 
123     /**
124      * Creates a new instance with the specified logger name using the default log level.
125      *
126      * @param name the name of the class to use for the logger
127      */
128     public LoggingHandler(String name) {
129         this(name, DEFAULT_LEVEL);
130     }
131 
132     /**
133      * Creates a new instance with the specified logger name.
134      *
135      * @param name the name of the class to use for the logger
136      * @param level the log level
137      */
138     public LoggingHandler(String name, LogLevel level) {
139         this(name, level, BufferFormat.HEX_DUMP);
140     }
141 
142     /**
143      * Creates a new instance with the specified logger name.
144      *
145      * @param name the name of the class to use for the logger
146      * @param level the log level
147      * @param bufferFormat the ByteBuf format
148      */
149     public LoggingHandler(String name, LogLevel level, BufferFormat bufferFormat) {
150         requireNonNull(name, "name");
151 
152         this.level = requireNonNull(level, "level");
153         this.bufferFormat = requireNonNull(bufferFormat, "bufferFormat");
154         logger = InternalLoggerFactory.getInstance(name);
155         internalLevel = level.toInternalLevel();
156     }
157 
158     @Override
159     public boolean isSharable() {
160         return true;
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 BufferFormat} that this handler uses to log
172      */
173     public BufferFormat bufferFormat() {
174         return bufferFormat;
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 channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
211         if (logger.isEnabled(internalLevel)) {
212             logger.log(internalLevel, format(ctx, "EXCEPTION", cause), cause);
213         }
214         ctx.fireChannelExceptionCaught(cause);
215     }
216 
217     @Override
218     public void channelInboundEvent(ChannelHandlerContext ctx, Object evt) throws Exception {
219         if (logger.isEnabled(internalLevel)) {
220             logger.log(internalLevel, format(ctx, "USER_EVENT", evt));
221         }
222         ctx.fireChannelInboundEvent(evt);
223     }
224 
225     @Override
226     public Future<Void> bind(ChannelHandlerContext ctx, SocketAddress localAddress) {
227         if (logger.isEnabled(internalLevel)) {
228             logger.log(internalLevel, format(ctx, "BIND", localAddress));
229         }
230         return ctx.bind(localAddress);
231     }
232 
233     @Override
234     public Future<Void> connect(
235             ChannelHandlerContext ctx,
236             SocketAddress remoteAddress, SocketAddress localAddress) {
237         if (logger.isEnabled(internalLevel)) {
238             logger.log(internalLevel, format(ctx, "CONNECT", remoteAddress, localAddress));
239         }
240         return ctx.connect(remoteAddress, localAddress);
241     }
242 
243     @Override
244     public Future<Void> disconnect(ChannelHandlerContext ctx) {
245         if (logger.isEnabled(internalLevel)) {
246             logger.log(internalLevel, format(ctx, "DISCONNECT"));
247         }
248         return ctx.disconnect();
249     }
250 
251     @Override
252     public Future<Void> close(ChannelHandlerContext ctx) {
253         if (logger.isEnabled(internalLevel)) {
254             logger.log(internalLevel, format(ctx, "CLOSE"));
255         }
256         return ctx.close();
257     }
258 
259     @Override
260     public Future<Void> deregister(ChannelHandlerContext ctx) {
261         if (logger.isEnabled(internalLevel)) {
262             logger.log(internalLevel, format(ctx, "DEREGISTER"));
263         }
264         return ctx.deregister();
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 Future<Void> write(ChannelHandlerContext ctx, Object msg) {
285         if (logger.isEnabled(internalLevel)) {
286             logger.log(internalLevel, format(ctx, "WRITE", msg));
287         }
288         return ctx.write(msg);
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) {
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 Buffer) {
329             return formatBuffer(ctx, eventName, (Buffer) arg);
330         } else {
331             return formatSimple(ctx, eventName, arg);
332         }
333     }
334 
335     /**
336      * Formats an event and returns the formatted message.  This method is currently only used for formatting
337      * {@link ChannelHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress)}.
338      *
339      * @param eventName the name of the event
340      * @param firstArg  the first argument of the event
341      * @param secondArg the second argument of the event
342      */
343     protected String format(ChannelHandlerContext ctx, String eventName, Object firstArg, Object secondArg) {
344         if (secondArg == null) {
345             return formatSimple(ctx, eventName, firstArg);
346         }
347 
348         String chStr = ctx.channel().toString();
349         String arg1Str = String.valueOf(firstArg);
350         String arg2Str = secondArg.toString();
351         StringBuilder buf = new StringBuilder(
352                 chStr.length() + 1 + eventName.length() + 2 + arg1Str.length() + 2 + arg2Str.length());
353         buf.append(chStr).append(' ').append(eventName).append(": ").append(arg1Str).append(", ").append(arg2Str);
354         return buf.toString();
355     }
356 
357     /**
358      * Generates the default log message of the specified event whose argument is a {@link Buffer}.
359      */
360     private String formatBuffer(ChannelHandlerContext ctx, String eventName, Buffer msg) {
361         String chStr = ctx.channel().toString();
362         int length = msg.readableBytes();
363         if (length == 0) {
364             StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 4);
365             buf.append(chStr).append(' ').append(eventName).append(": 0B");
366             return buf.toString();
367         } else {
368             int outputLength = chStr.length() + 1 + eventName.length() + 2 + 10 + 1;
369             if (bufferFormat == BufferFormat.HEX_DUMP) {
370                 int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
371                 int hexDumpLength = 2 + rows * 80;
372                 outputLength += hexDumpLength;
373             }
374             StringBuilder buf = new StringBuilder(outputLength);
375             buf.append(chStr).append(' ').append(eventName).append(": ").append(length).append('B');
376             if (bufferFormat == BufferFormat.HEX_DUMP) {
377                 buf.append(NEWLINE);
378                 BufferUtil.appendPrettyHexDump(buf, msg);
379             }
380 
381             return buf.toString();
382         }
383     }
384 
385     /**
386      * Generates the default log message of the specified event whose argument is an arbitrary object.
387      */
388     private static String formatSimple(ChannelHandlerContext ctx, String eventName, Object msg) {
389         String chStr = ctx.channel().toString();
390         String msgStr = String.valueOf(msg);
391         StringBuilder buf = new StringBuilder(chStr.length() + 1 + eventName.length() + 2 + msgStr.length());
392         return buf.append(chStr).append(' ').append(eventName).append(": ").append(msgStr).toString();
393     }
394 }