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 
103     private final InternalLogger logger;
104     private final InternalLogLevel level;
105     private final boolean hexDump;
106 
107     /**
108      * Creates a new instance whose logger name is the fully qualified class
109      * name of the instance with hex dump enabled.
110      */
111     public LoggingHandler() {
112         this(true);
113     }
114 
115     /**
116      * Creates a new instance whose logger name is the fully qualified class
117      * name of the instance.
118      *
119      * @param level   the log level
120      */
121     public LoggingHandler(InternalLogLevel level) {
122         this(level, true);
123     }
124 
125     /**
126      * Creates a new instance whose logger name is the fully qualified class
127      * name of the instance.
128      *
129      * @param hexDump {@code true} if and only if the hex dump of the received
130      *                message is logged
131      */
132     public LoggingHandler(boolean hexDump) {
133         this(DEFAULT_LEVEL, hexDump);
134     }
135 
136     /**
137      * Creates a new instance whose logger name is the fully qualified class
138      * name of the instance.
139      *
140      * @param level   the log level
141      * @param hexDump {@code true} if and only if the hex dump of the received
142      *                message is logged
143      */
144     public LoggingHandler(InternalLogLevel level, boolean hexDump) {
145         if (level == null) {
146             throw new NullPointerException("level");
147         }
148 
149         logger = InternalLoggerFactory.getInstance(getClass());
150         this.level = level;
151         this.hexDump = hexDump;
152     }
153 
154     /**
155      * Creates a new instance with the specified logger name and with hex dump
156      * enabled.
157      */
158     public LoggingHandler(Class<?> clazz) {
159         this(clazz, true);
160     }
161 
162     /**
163      * Creates a new instance with the specified logger name.
164      *
165      * @param hexDump {@code true} if and only if the hex dump of the received
166      *                message is logged
167      */
168     public LoggingHandler(Class<?> clazz, boolean hexDump) {
169         this(clazz, DEFAULT_LEVEL, hexDump);
170     }
171 
172     /**
173      * Creates a new instance with the specified logger name.
174      *
175      * @param level   the log level
176      */
177     public LoggingHandler(Class<?> clazz, InternalLogLevel level) {
178         this(clazz, level, true);
179     }
180 
181     /**
182      * Creates a new instance with the specified logger name.
183      *
184      * @param level   the log level
185      * @param hexDump {@code true} if and only if the hex dump of the received
186      *                message is logged
187      */
188     public LoggingHandler(Class<?> clazz, InternalLogLevel level, boolean hexDump) {
189         if (clazz == null) {
190             throw new NullPointerException("clazz");
191         }
192         if (level == null) {
193             throw new NullPointerException("level");
194         }
195         logger = InternalLoggerFactory.getInstance(clazz);
196         this.level = level;
197         this.hexDump = hexDump;
198     }
199 
200     /**
201      * Creates a new instance with the specified logger name and with hex dump
202      * enabled.
203      */
204     public LoggingHandler(String name) {
205         this(name, true);
206     }
207 
208     /**
209      * Creates a new instance with the specified logger name.
210      *
211      * @param hexDump {@code true} if and only if the hex dump of the received
212      *                message is logged
213      */
214     public LoggingHandler(String name, boolean hexDump) {
215         this(name, DEFAULT_LEVEL, hexDump);
216     }
217 
218     /**
219      * Creates a new instance with the specified logger name.
220      *
221      * @param level   the log level
222      * @param hexDump {@code true} if and only if the hex dump of the received
223      *                message is logged
224      */
225     public LoggingHandler(String name, InternalLogLevel level, boolean hexDump) {
226         if (name == null) {
227             throw new NullPointerException("name");
228         }
229         if (level == null) {
230             throw new NullPointerException("level");
231         }
232         logger = InternalLoggerFactory.getInstance(name);
233         this.level = level;
234         this.hexDump = hexDump;
235     }
236 
237     /**
238      * Returns the {@link InternalLogger} that this handler uses to log
239      * a {@link ChannelEvent}.
240      */
241     public InternalLogger getLogger() {
242         return logger;
243     }
244 
245     /**
246      * Returns the {@link InternalLogLevel} that this handler uses to log
247      * a {@link ChannelEvent}.
248      */
249     public InternalLogLevel getLevel() {
250         return level;
251     }
252 
253     /**
254      * Logs the specified event to the {@link InternalLogger} returned by
255      * {@link #getLogger()}. If hex dump has been enabled for this handler,
256      * the hex dump of the {@link ChannelBuffer} in a {@link MessageEvent} will
257      * be logged together.
258      */
259     public void log(ChannelEvent e) {
260         if (getLogger().isEnabled(level)) {
261             String msg = e.toString();
262 
263             // Append hex dump if necessary.
264             if (hexDump && e instanceof MessageEvent) {
265                 MessageEvent me = (MessageEvent) e;
266                 if (me.getMessage() instanceof ChannelBuffer) {
267                     msg += formatBuffer((ChannelBuffer) me.getMessage());
268                 }
269             }
270 
271             // Log the message (and exception if available.)
272             if (e instanceof ExceptionEvent) {
273                 getLogger().log(level, msg, ((ExceptionEvent) e).getCause());
274             } else {
275                 getLogger().log(level, msg);
276             }
277         }
278     }
279 
280     private static String formatBuffer(ChannelBuffer buf) {
281         int length = buf.readableBytes();
282         int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
283         StringBuilder dump = new StringBuilder(rows * 80);
284 
285         dump.append(
286                 NEWLINE + "         +-------------------------------------------------+" +
287                 NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
288                 NEWLINE + "+--------+-------------------------------------------------+----------------+");
289 
290         final int startIndex = buf.readerIndex();
291         final int endIndex = buf.writerIndex();
292 
293         int i;
294         for (i = startIndex; i < endIndex; i ++) {
295             int relIdx = i - startIndex;
296             int relIdxMod16 = relIdx & 15;
297             if (relIdxMod16 == 0) {
298                 dump.append(NEWLINE);
299                 dump.append(Long.toHexString(relIdx & 0xFFFFFFFFL | 0x100000000L));
300                 dump.setCharAt(dump.length() - 9, '|');
301                 dump.append('|');
302             }
303             dump.append(BYTE2HEX[buf.getUnsignedByte(i)]);
304             if (relIdxMod16 == 15) {
305                 dump.append(" |");
306                 for (int j = i - 15; j <= i; j ++) {
307                     dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
308                 }
309                 dump.append('|');
310             }
311         }
312 
313         if ((i - startIndex & 15) != 0) {
314             int remainder = length & 15;
315             dump.append(HEXPADDING[remainder]);
316             dump.append(" |");
317             for (int j = i - remainder; j < i; j ++) {
318                 dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
319             }
320             dump.append(BYTEPADDING[remainder]);
321             dump.append('|');
322         }
323 
324         dump.append(
325                 NEWLINE + "+--------+-------------------------------------------------+----------------+");
326 
327         return dump.toString();
328     }
329 
330     public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)
331             throws Exception {
332         log(e);
333         ctx.sendUpstream(e);
334     }
335 
336     public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)
337             throws Exception {
338         log(e);
339         ctx.sendDownstream(e);
340     }
341 }