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