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