View Javadoc
1   /*
2    * Copyright 2014 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.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufUtil;
20  import io.netty.channel.ChannelHandlerAdapter;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.handler.logging.LogLevel;
23  import io.netty.util.internal.logging.InternalLogLevel;
24  import io.netty.util.internal.logging.InternalLogger;
25  import io.netty.util.internal.logging.InternalLoggerFactory;
26  
27  import static io.netty.util.internal.ObjectUtil.checkNotNull;
28  
29  /**
30   * Logs HTTP2 frames for debugging purposes.
31   */
32  public class Http2FrameLogger extends ChannelHandlerAdapter {
33  
34      public enum Direction {
35          INBOUND,
36          OUTBOUND
37      }
38  
39      private static final int BUFFER_LENGTH_THRESHOLD = 64;
40      private final InternalLogger logger;
41      private final InternalLogLevel level;
42  
43      public Http2FrameLogger(LogLevel level) {
44          this(checkAndConvertLevel(level), InternalLoggerFactory.getInstance(Http2FrameLogger.class));
45      }
46  
47      public Http2FrameLogger(LogLevel level, String name) {
48          this(checkAndConvertLevel(level), InternalLoggerFactory.getInstance(checkNotNull(name, "name")));
49      }
50  
51      public Http2FrameLogger(LogLevel level, Class<?> clazz) {
52          this(checkAndConvertLevel(level), InternalLoggerFactory.getInstance(checkNotNull(clazz, "clazz")));
53      }
54  
55      private Http2FrameLogger(InternalLogLevel level, InternalLogger logger) {
56          this.level = level;
57          this.logger = logger;
58      }
59  
60      private static InternalLogLevel checkAndConvertLevel(LogLevel level) {
61          return checkNotNull(level, "level").toInternalLevel();
62      }
63  
64      public boolean isEnabled() {
65          return logger.isEnabled(level);
66      }
67  
68      public void logData(Direction direction, ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
69              boolean endStream) {
70          if (isEnabled()) {
71              logger.log(level, "{} {} DATA: streamId={} padding={} endStream={} length={} bytes={}", ctx.channel(),
72                      direction.name(), streamId, padding, endStream, data.readableBytes(), toString(data));
73          }
74      }
75  
76      public void logHeaders(Direction direction, ChannelHandlerContext ctx, int streamId, Http2Headers headers,
77              int padding, boolean endStream) {
78          if (isEnabled()) {
79              logger.log(level, "{} {} HEADERS: streamId={} headers={} padding={} endStream={}", ctx.channel(),
80                      direction.name(), streamId, headers, padding, endStream);
81          }
82      }
83  
84      public void logHeaders(Direction direction, ChannelHandlerContext ctx, int streamId, Http2Headers headers,
85              int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) {
86          if (isEnabled()) {
87              logger.log(level, "{} {} HEADERS: streamId={} headers={} streamDependency={} weight={} exclusive={} " +
88                      "padding={} endStream={}", ctx.channel(),
89                      direction.name(), streamId, headers, streamDependency, weight, exclusive, padding, endStream);
90          }
91      }
92  
93      public void logPriority(Direction direction, ChannelHandlerContext ctx, int streamId, int streamDependency,
94              short weight, boolean exclusive) {
95          if (isEnabled()) {
96              logger.log(level, "{} {} PRIORITY: streamId={} streamDependency={} weight={} exclusive={}", ctx.channel(),
97                      direction.name(), streamId, streamDependency, weight, exclusive);
98          }
99      }
100 
101     public void logRstStream(Direction direction, ChannelHandlerContext ctx, int streamId, long errorCode) {
102         if (isEnabled()) {
103             logger.log(level, "{} {} RST_STREAM: streamId={} errorCode={}", ctx.channel(),
104                     direction.name(), streamId, errorCode);
105         }
106     }
107 
108     public void logSettingsAck(Direction direction, ChannelHandlerContext ctx) {
109         logger.log(level, "{} {} SETTINGS: ack=true", ctx.channel(), direction.name());
110     }
111 
112     public void logSettings(Direction direction, ChannelHandlerContext ctx, Http2Settings settings) {
113         if (isEnabled()) {
114             logger.log(level, "{} {} SETTINGS: ack=false settings={}", ctx.channel(), direction.name(), settings);
115         }
116     }
117 
118     public void logPing(Direction direction, ChannelHandlerContext ctx, long data) {
119         if (isEnabled()) {
120             logger.log(level, "{} {} PING: ack=false bytes={}", ctx.channel(),
121                     direction.name(), data);
122         }
123     }
124 
125     public void logPingAck(Direction direction, ChannelHandlerContext ctx, long data) {
126         if (isEnabled()) {
127             logger.log(level, "{} {} PING: ack=true bytes={}", ctx.channel(),
128                     direction.name(), data);
129         }
130     }
131 
132     public void logPushPromise(Direction direction, ChannelHandlerContext ctx, int streamId, int promisedStreamId,
133             Http2Headers headers, int padding) {
134         if (isEnabled()) {
135             logger.log(level, "{} {} PUSH_PROMISE: streamId={} promisedStreamId={} headers={} padding={}",
136                     ctx.channel(), direction.name(), streamId, promisedStreamId, headers, padding);
137         }
138     }
139 
140     public void logGoAway(Direction direction, ChannelHandlerContext ctx, int lastStreamId, long errorCode,
141             ByteBuf debugData) {
142         if (isEnabled()) {
143             logger.log(level, "{} {} GO_AWAY: lastStreamId={} errorCode={} length={} bytes={}", ctx.channel(),
144                     direction.name(), lastStreamId, errorCode, debugData.readableBytes(), toString(debugData));
145         }
146     }
147 
148     public void logWindowsUpdate(Direction direction, ChannelHandlerContext ctx, int streamId,
149             int windowSizeIncrement) {
150         if (isEnabled()) {
151             logger.log(level, "{} {} WINDOW_UPDATE: streamId={} windowSizeIncrement={}", ctx.channel(),
152                     direction.name(), streamId, windowSizeIncrement);
153         }
154     }
155 
156     public void logUnknownFrame(Direction direction, ChannelHandlerContext ctx, byte frameType, int streamId,
157             Http2Flags flags, ByteBuf data) {
158         if (isEnabled()) {
159             logger.log(level, "{} {} UNKNOWN: frameType={} streamId={} flags={} length={} bytes={}", ctx.channel(),
160                     direction.name(), frameType & 0xFF, streamId, flags.value(), data.readableBytes(), toString(data));
161         }
162     }
163 
164     private String toString(ByteBuf buf) {
165         if (level == InternalLogLevel.TRACE || buf.readableBytes() <= BUFFER_LENGTH_THRESHOLD) {
166             // Log the entire buffer.
167             return ByteBufUtil.hexDump(buf);
168         }
169 
170         // Otherwise just log the first 64 bytes.
171         int length = Math.min(buf.readableBytes(), BUFFER_LENGTH_THRESHOLD);
172         return ByteBufUtil.hexDump(buf, buf.readerIndex(), length) + "...";
173     }
174 }