/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.jboss.netty.handler.logging; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.ChannelDownstreamHandler; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelHandler.Sharable; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelUpstreamHandler; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.logging.InternalLogLevel; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; /** * A {@link ChannelHandler} that logs all events via {@link InternalLogger}. * By default, all events are logged at <tt>DEBUG</tt> level. You can extend * this class and override {@link #log(ChannelEvent)} to change the default * behavior. * @apiviz.landmark */ @Sharable public class LoggingHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler { private static final InternalLogLevel DEFAULT_LEVEL = InternalLogLevel.DEBUG; private static final String NEWLINE = String.format("%n"); private static final String[] BYTE2HEX = new String[256]; private static final String[] HEXPADDING = new String[16]; private static final String[] BYTEPADDING = new String[16]; private static final char[] BYTE2CHAR = new char[256]; static { int i; // Generate the lookup table for byte-to-hex-dump conversion for (i = 0; i < 10; i ++) { StringBuilder buf = new StringBuilder(3); buf.append(" 0"); buf.append(i); BYTE2HEX[i] = buf.toString(); } for (; i < 16; i ++) { StringBuilder buf = new StringBuilder(3); buf.append(" 0"); buf.append((char) ('a' + i - 10)); BYTE2HEX[i] = buf.toString(); } for (; i < BYTE2HEX.length; i ++) { StringBuilder buf = new StringBuilder(3); buf.append(' '); buf.append(Integer.toHexString(i)); BYTE2HEX[i] = buf.toString(); } // Generate the lookup table for hex dump paddings for (i = 0; i < HEXPADDING.length; i ++) { int padding = HEXPADDING.length - i; StringBuilder buf = new StringBuilder(padding * 3); for (int j = 0; j < padding; j ++) { buf.append(" "); } HEXPADDING[i] = buf.toString(); } // Generate the lookup table for byte dump paddings for (i = 0; i < BYTEPADDING.length; i ++) { int padding = BYTEPADDING.length - i; StringBuilder buf = new StringBuilder(padding); for (int j = 0; j < padding; j ++) { buf.append(' '); } BYTEPADDING[i] = buf.toString(); } // Generate the lookup table for byte-to-char conversion for (i = 0; i < BYTE2CHAR.length; i ++) { if (i <= 0x1f || i >= 0x7f) { BYTE2CHAR[i] = '.'; } else { BYTE2CHAR[i] = (char) i; } } } private final InternalLogger logger; private final InternalLogLevel level; private final boolean hexDump; /** * Creates a new instance whose logger name is the fully qualified class * name of the instance with hex dump enabled. */ public LoggingHandler() { this(true); } /** * Creates a new instance whose logger name is the fully qualified class * name of the instance. * * @param level the log level */ public LoggingHandler(InternalLogLevel level) { this(level, true); } /** * Creates a new instance whose logger name is the fully qualified class * name of the instance. * * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(boolean hexDump) { this(DEFAULT_LEVEL, hexDump); } /** * Creates a new instance whose logger name is the fully qualified class * name of the instance. * * @param level the log level * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(InternalLogLevel level, boolean hexDump) { if (level == null) { throw new NullPointerException("level"); } logger = InternalLoggerFactory.getInstance(getClass()); this.level = level; this.hexDump = hexDump; } /** * Creates a new instance with the specified logger name and with hex dump * enabled. */ public LoggingHandler(Class<?> clazz) { this(clazz, true); } /** * Creates a new instance with the specified logger name. * * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(Class<?> clazz, boolean hexDump) { this(clazz, DEFAULT_LEVEL, hexDump); } /** * Creates a new instance with the specified logger name. * * @param level the log level */ public LoggingHandler(Class<?> clazz, InternalLogLevel level) { this(clazz, level, true); } /** * Creates a new instance with the specified logger name. * * @param level the log level * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(Class<?> clazz, InternalLogLevel level, boolean hexDump) { if (clazz == null) { throw new NullPointerException("clazz"); } if (level == null) { throw new NullPointerException("level"); } logger = InternalLoggerFactory.getInstance(clazz); this.level = level; this.hexDump = hexDump; } /** * Creates a new instance with the specified logger name and with hex dump * enabled. */ public LoggingHandler(String name) { this(name, true); } /** * Creates a new instance with the specified logger name. * * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(String name, boolean hexDump) { this(name, DEFAULT_LEVEL, hexDump); } /** * Creates a new instance with the specified logger name. * * @param level the log level * @param hexDump {@code true} if and only if the hex dump of the received * message is logged */ public LoggingHandler(String name, InternalLogLevel level, boolean hexDump) { if (name == null) { throw new NullPointerException("name"); } if (level == null) { throw new NullPointerException("level"); } logger = InternalLoggerFactory.getInstance(name); this.level = level; this.hexDump = hexDump; } /** * Returns the {@link InternalLogger} that this handler uses to log * a {@link ChannelEvent}. */ public InternalLogger getLogger() { return logger; } /** * Returns the {@link InternalLogLevel} that this handler uses to log * a {@link ChannelEvent}. */ public InternalLogLevel getLevel() { return level; } /** * Logs the specified event to the {@link InternalLogger} returned by * {@link #getLogger()}. If hex dump has been enabled for this handler, * the hex dump of the {@link ChannelBuffer} in a {@link MessageEvent} will * be logged together. */ public void log(ChannelEvent e) { if (getLogger().isEnabled(level)) { String msg = e.toString(); // Append hex dump if necessary. if (hexDump && e instanceof MessageEvent) { MessageEvent me = (MessageEvent) e; if (me.getMessage() instanceof ChannelBuffer) { msg += formatBuffer((ChannelBuffer) me.getMessage()); } } // Log the message (and exception if available.) if (e instanceof ExceptionEvent) { getLogger().log(level, msg, ((ExceptionEvent) e).getCause()); } else { getLogger().log(level, msg); } } } private static String formatBuffer(ChannelBuffer buf) { int length = buf.readableBytes(); int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4; StringBuilder dump = new StringBuilder(rows * 80); dump.append( NEWLINE + " +-------------------------------------------------+" + NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" + NEWLINE + "+--------+-------------------------------------------------+----------------+"); final int startIndex = buf.readerIndex(); final int endIndex = buf.writerIndex(); int i; for (i = startIndex; i < endIndex; i ++) { int relIdx = i - startIndex; int relIdxMod16 = relIdx & 15; if (relIdxMod16 == 0) { dump.append(NEWLINE); dump.append(Long.toHexString(relIdx & 0xFFFFFFFFL | 0x100000000L)); dump.setCharAt(dump.length() - 9, '|'); dump.append('|'); } dump.append(BYTE2HEX[buf.getUnsignedByte(i)]); if (relIdxMod16 == 15) { dump.append(" |"); for (int j = i - 15; j <= i; j ++) { dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); } dump.append('|'); } } if ((i - startIndex & 15) != 0) { int remainder = length & 15; dump.append(HEXPADDING[remainder]); dump.append(" |"); for (int j = i - remainder; j < i; j ++) { dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]); } dump.append(BYTEPADDING[remainder]); dump.append('|'); } dump.append( NEWLINE + "+--------+-------------------------------------------------+----------------+"); return dump.toString(); } public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { log(e); ctx.sendUpstream(e); } public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { log(e); ctx.sendDownstream(e); } }