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 org.jboss.netty.handler.logging;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.channel.ChannelDownstreamHandler;
20  import org.jboss.netty.channel.ChannelEvent;
21  import org.jboss.netty.channel.ChannelHandler;
22  import org.jboss.netty.channel.ChannelHandler.Sharable;
23  import org.jboss.netty.channel.ChannelHandlerContext;
24  import org.jboss.netty.channel.ChannelUpstreamHandler;
25  import org.jboss.netty.channel.ExceptionEvent;
26  import org.jboss.netty.channel.MessageEvent;
27  import org.jboss.netty.logging.InternalLogLevel;
28  import org.jboss.netty.logging.InternalLogger;
29  import org.jboss.netty.logging.InternalLoggerFactory;
30  
31  /**
32   * A {@link ChannelHandler} that logs all events via {@link InternalLogger}.
33   * By default, all events are logged at <tt>DEBUG</tt> level.  You can extend
34   * this class and override {@link #log(ChannelEvent)} to change the default
35   * behavior.
36   * @apiviz.landmark
37   */
38  @Sharable
39  public class LoggingHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler {
40  
41      private static final InternalLogLevel DEFAULT_LEVEL = InternalLogLevel.DEBUG;
42      private static final String NEWLINE = String.format("%n");
43  
44      private static final String[] BYTE2HEX = new String[256];
45      private static final String[] HEXPADDING = new String[16];
46      private static final String[] BYTEPADDING = new String[16];
47      private static final char[] BYTE2CHAR = new char[256];
48  
49      static {
50          int i;
51  
52          // Generate the lookup table for byte-to-hex-dump conversion
53          for (i = 0; i < 10; i ++) {
54              StringBuilder buf = new StringBuilder(3);
55              buf.append(" 0");
56              buf.append(i);
57              BYTE2HEX[i] = buf.toString();
58          }
59          for (; i < 16; i ++) {
60              StringBuilder buf = new StringBuilder(3);
61              buf.append(" 0");
62              buf.append((char) ('a' + i - 10));
63              BYTE2HEX[i] = buf.toString();
64          }
65          for (; i < BYTE2HEX.length; i ++) {
66              StringBuilder buf = new StringBuilder(3);
67              buf.append(' ');
68              buf.append(Integer.toHexString(i));
69              BYTE2HEX[i] = buf.toString();
70          }
71  
72          // Generate the lookup table for hex dump paddings
73          for (i = 0; i < HEXPADDING.length; i ++) {
74              int padding = HEXPADDING.length - i;
75              StringBuilder buf = new StringBuilder(padding * 3);
76              for (int j = 0; j < padding; j ++) {
77                  buf.append("   ");
78              }
79              HEXPADDING[i] = buf.toString();
80          }
81  
82          // Generate the lookup table for byte dump paddings
83          for (i = 0; i < BYTEPADDING.length; i ++) {
84              int padding = BYTEPADDING.length - i;
85              StringBuilder buf = new StringBuilder(padding);
86              for (int j = 0; j < padding; j ++) {
87                  buf.append(' ');
88              }
89              BYTEPADDING[i] = buf.toString();
90          }
91  
92          // Generate the lookup table for byte-to-char conversion
93          for (i = 0; i < BYTE2CHAR.length; i ++) {
94              if (i <= 0x1f || i >= 0x7f) {
95                  BYTE2CHAR[i] = '.';
96              } else {
97                  BYTE2CHAR[i] = (char) i;
98              }
99          }
100     }
101 
102     private final InternalLogger logger;
103     private final InternalLogLevel level;
104     private final boolean hexDump;
105 
106     /**
107      * Creates a new instance whose logger name is the fully qualified class
108      * name of the instance with hex dump enabled.
109      */
110     public LoggingHandler() {
111         this(true);
112     }
113 
114     /**
115      * Creates a new instance whose logger name is the fully qualified class
116      * name of the instance.
117      *
118      * @param level   the log level
119      */
120     public LoggingHandler(InternalLogLevel level) {
121         this(level, true);
122     }
123 
124     /**
125      * Creates a new instance whose logger name is the fully qualified class
126      * name of the instance.
127      *
128      * @param hexDump {@code true} if and only if the hex dump of the received
129      *                message is logged
130      */
131     public LoggingHandler(boolean hexDump) {
132         this(DEFAULT_LEVEL, hexDump);
133     }
134 
135     /**
136      * Creates a new instance whose logger name is the fully qualified class
137      * name of the instance.
138      *
139      * @param level   the log level
140      * @param hexDump {@code true} if and only if the hex dump of the received
141      *                message is logged
142      */
143     public LoggingHandler(InternalLogLevel level, boolean hexDump) {
144         if (level == null) {
145             throw new NullPointerException("level");
146         }
147 
148         logger = InternalLoggerFactory.getInstance(getClass());
149         this.level = level;
150         this.hexDump = hexDump;
151     }
152 
153     /**
154      * Creates a new instance with the specified logger name and with hex dump
155      * enabled.
156      */
157     public LoggingHandler(Class<?> clazz) {
158         this(clazz, true);
159     }
160 
161     /**
162      * Creates a new instance with the specified logger name.
163      *
164      * @param hexDump {@code true} if and only if the hex dump of the received
165      *                message is logged
166      */
167     public LoggingHandler(Class<?> clazz, boolean hexDump) {
168         this(clazz, DEFAULT_LEVEL, hexDump);
169     }
170 
171     /**
172      * Creates a new instance with the specified logger name.
173      *
174      * @param level   the log level
175      */
176     public LoggingHandler(Class<?> clazz, InternalLogLevel level) {
177         this(clazz, level, true);
178     }
179 
180     /**
181      * Creates a new instance with the specified logger name.
182      *
183      * @param level   the log level
184      * @param hexDump {@code true} if and only if the hex dump of the received
185      *                message is logged
186      */
187     public LoggingHandler(Class<?> clazz, InternalLogLevel level, boolean hexDump) {
188         if (clazz == null) {
189             throw new NullPointerException("clazz");
190         }
191         if (level == null) {
192             throw new NullPointerException("level");
193         }
194         logger = InternalLoggerFactory.getInstance(clazz);
195         this.level = level;
196         this.hexDump = hexDump;
197     }
198 
199     /**
200      * Creates a new instance with the specified logger name and with hex dump
201      * enabled.
202      */
203     public LoggingHandler(String name) {
204         this(name, true);
205     }
206 
207     /**
208      * Creates a new instance with the specified logger name.
209      *
210      * @param hexDump {@code true} if and only if the hex dump of the received
211      *                message is logged
212      */
213     public LoggingHandler(String name, boolean hexDump) {
214         this(name, DEFAULT_LEVEL, hexDump);
215     }
216 
217     /**
218      * Creates a new instance with the specified logger name.
219      *
220      * @param level   the log level
221      * @param hexDump {@code true} if and only if the hex dump of the received
222      *                message is logged
223      */
224     public LoggingHandler(String name, InternalLogLevel level, boolean hexDump) {
225         if (name == null) {
226             throw new NullPointerException("name");
227         }
228         if (level == null) {
229             throw new NullPointerException("level");
230         }
231         logger = InternalLoggerFactory.getInstance(name);
232         this.level = level;
233         this.hexDump = hexDump;
234     }
235 
236     /**
237      * Returns the {@link InternalLogger} that this handler uses to log
238      * a {@link ChannelEvent}.
239      */
240     public InternalLogger getLogger() {
241         return logger;
242     }
243 
244     /**
245      * Returns the {@link InternalLogLevel} that this handler uses to log
246      * a {@link ChannelEvent}.
247      */
248     public InternalLogLevel getLevel() {
249         return level;
250     }
251 
252     /**
253      * Logs the specified event to the {@link InternalLogger} returned by
254      * {@link #getLogger()}. If hex dump has been enabled for this handler,
255      * the hex dump of the {@link ChannelBuffer} in a {@link MessageEvent} will
256      * be logged together.
257      */
258     public void log(ChannelEvent e) {
259         if (getLogger().isEnabled(level)) {
260             String msg = e.toString();
261 
262             // Append hex dump if necessary.
263             if (hexDump && e instanceof MessageEvent) {
264                 MessageEvent me = (MessageEvent) e;
265                 if (me.getMessage() instanceof ChannelBuffer) {
266                     msg += formatBuffer((ChannelBuffer) me.getMessage());
267                 }
268             }
269 
270             // Log the message (and exception if available.)
271             if (e instanceof ExceptionEvent) {
272                 getLogger().log(level, msg, ((ExceptionEvent) e).getCause());
273             } else {
274                 getLogger().log(level, msg);
275             }
276         }
277     }
278 
279     private static String formatBuffer(ChannelBuffer buf) {
280         int length = buf.readableBytes();
281         int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
282         StringBuilder dump = new StringBuilder(rows * 80);
283 
284         dump.append(
285                 NEWLINE + "         +-------------------------------------------------+" +
286                 NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
287                 NEWLINE + "+--------+-------------------------------------------------+----------------+");
288 
289         final int startIndex = buf.readerIndex();
290         final int endIndex = buf.writerIndex();
291 
292         int i;
293         for (i = startIndex; i < endIndex; i ++) {
294             int relIdx = i - startIndex;
295             int relIdxMod16 = relIdx & 15;
296             if (relIdxMod16 == 0) {
297                 dump.append(NEWLINE);
298                 dump.append(Long.toHexString(relIdx & 0xFFFFFFFFL | 0x100000000L));
299                 dump.setCharAt(dump.length() - 9, '|');
300                 dump.append('|');
301             }
302             dump.append(BYTE2HEX[buf.getUnsignedByte(i)]);
303             if (relIdxMod16 == 15) {
304                 dump.append(" |");
305                 for (int j = i - 15; j <= i; j ++) {
306                     dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
307                 }
308                 dump.append('|');
309             }
310         }
311 
312         if ((i - startIndex & 15) != 0) {
313             int remainder = length & 15;
314             dump.append(HEXPADDING[remainder]);
315             dump.append(" |");
316             for (int j = i - remainder; j < i; j ++) {
317                 dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
318             }
319             dump.append(BYTEPADDING[remainder]);
320             dump.append('|');
321         }
322 
323         dump.append(
324                 NEWLINE + "+--------+-------------------------------------------------+----------------+");
325 
326         return dump.toString();
327     }
328 
329     public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
330             throws Exception {
331         log(e);
332         ctx.sendUpstream(e);
333     }
334 
335     public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
336             throws Exception {
337         log(e);
338         ctx.sendDownstream(e);
339     }
340 }