Skip to content

Commit 4a4e2d4

Browse files
authored
Merge pull request #436 from fredthomsen/logging
Logging enhancement
2 parents ca4e2bc + 022c9e4 commit 4a4e2d4

File tree

7 files changed

+168
-7
lines changed

7 files changed

+168
-7
lines changed

CONTRIBUTORS.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,7 @@ In chronological order:
108108

109109
- Added support for equality testing to ``h2.settings.Settings`` objects.
110110

111+
- Fred Thomsen (@fredthomsen)
112+
113+
- Added logging.
114+

HISTORY.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ API Changes (Backward-Compatible)
1111
events.
1212
- Rather than reject outbound Connection-specific headers, h2 will now
1313
normalize the header block by removing them.
14-
- Implement equality for the ``Settings`` class.
14+
- Implement equality for the ``h2.settings.Settings`` class.
1515
- Added ``h2.settings.SettingCodes``, an enum that is used to store all the
1616
HTTP/2 setting codes. This allows us to use a better printed representation of
1717
the setting code in most places that it is used.
@@ -24,7 +24,10 @@ API Changes (Backward-Compatible)
2424
3.0.0.
2525
- Added an optional ``pad_length`` parameter to ``H2Connection.send_data``
2626
to allow the user to include padding on a data frame.
27-
27+
- Added a new parameter to the ``h2.config.H2Configuration`` initializer which
28+
takes a logger. This allows us to log by providing a logger that conforms
29+
to the requirements of this module so that it can be used in different
30+
environments.
2831

2932
Bugfixes
3033
~~~~~~~~

h2/config.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ def __set__(self, instance, value):
2525
setattr(instance, self.attr_name, value)
2626

2727

28+
class DummyLogger(object):
29+
"""
30+
An Logger object that does not actual logging, hence a DummyLogger.
31+
32+
For the class the log operation is merely a no-op. The intent is to avoid
33+
conditionals being sprinkled throughout the hyper-h2 code for calls to
34+
logging functions when no logger is passed into the corresponding object.
35+
"""
36+
def __init__(self, *vargs):
37+
pass
38+
39+
def debug(self, *vargs, **kwargs):
40+
"""
41+
No-op logging. Only level needed for now.
42+
"""
43+
pass
44+
45+
2846
class H2Configuration(object):
2947
"""
3048
An object that controls the way a single HTTP/2 connection behaves.
@@ -70,6 +88,14 @@ class H2Configuration(object):
7088
be skipped, and allow the object to receive headers that may be illegal
7189
according to RFC 7540. Defaults to ``True``.
7290
:type validate_inbound_headers: ``bool``
91+
92+
:param logger: A logger that conforms to the requirements for this module,
93+
those being no I/O and no context switches, which is needed in order
94+
to run in asynchronous operation.
95+
96+
.. versionadded:: 2.6.0
97+
98+
:type logger: ``logging.Logger``
7399
"""
74100
client_side = _BooleanConfigOption('client_side')
75101
validate_outbound_headers = _BooleanConfigOption(
@@ -87,12 +113,14 @@ def __init__(self,
87113
header_encoding='utf-8',
88114
validate_outbound_headers=True,
89115
normalize_outbound_headers=True,
90-
validate_inbound_headers=True):
116+
validate_inbound_headers=True,
117+
logger=None):
91118
self.client_side = client_side
92119
self.header_encoding = header_encoding
93120
self.validate_outbound_headers = validate_outbound_headers
94121
self.normalize_outbound_headers = normalize_outbound_headers
95122
self.validate_inbound_headers = validate_inbound_headers
123+
self.logger = logger or DummyLogger(__name__)
96124

97125
@property
98126
def header_encoding(self):

h2/connection.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ def _begin_new_stream(self, stream_id, allowed_ids):
506506
:param stream_id: The ID of the stream to open.
507507
:param allowed_ids: What kind of stream ID is allowed.
508508
"""
509+
self.config.logger.debug(
510+
"Attempting to initiate stream ID %d", stream_id
511+
)
509512
outbound = self._stream_id_is_outbound(stream_id)
510513
highest_stream_id = (
511514
self.highest_outbound_stream_id if outbound else
@@ -526,10 +529,12 @@ def _begin_new_stream(self, stream_id, allowed_ids):
526529
inbound_window_size=self.local_settings.initial_window_size,
527530
outbound_window_size=self.remote_settings.initial_window_size
528531
)
532+
self.config.logger.debug("Stream ID %d created", stream_id)
529533
s.max_inbound_frame_size = self.max_inbound_frame_size
530534
s.max_outbound_frame_size = self.max_outbound_frame_size
531535

532536
self.streams[stream_id] = s
537+
self.config.logger.debug("Current streams: %s", self.streams.keys())
533538

534539
if outbound:
535540
self.highest_outbound_stream_id = stream_id
@@ -543,6 +548,7 @@ def initiate_connection(self):
543548
Provides any data that needs to be sent at the start of the connection.
544549
Must be called for both clients and servers.
545550
"""
551+
self.config.logger.debug("Initializing connection")
546552
self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS)
547553
if self.config.client_side:
548554
preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
@@ -552,6 +558,9 @@ def initiate_connection(self):
552558
f = SettingsFrame(0)
553559
for setting, value in self.local_settings.items():
554560
f.settings[setting] = value
561+
self.config.logger.debug(
562+
"Send Settings frame: %s", self.local_settings
563+
)
555564

556565
self._data_to_send += preamble + f.serialize()
557566

@@ -588,8 +597,11 @@ def initiate_upgrade_connection(self, settings_header=None):
588597
For servers, returns nothing.
589598
:rtype: ``bytes`` or ``None``
590599
"""
591-
frame_data = None
600+
self.config.logger.debug(
601+
"Upgrade connection. Current settings: %s", self.local_settings
602+
)
592603

604+
frame_data = None
593605
# Begin by getting the preamble in place.
594606
self.initiate_connection()
595607

@@ -617,6 +629,7 @@ def initiate_upgrade_connection(self, settings_header=None):
617629
ConnectionInputs.SEND_HEADERS if self.config.client_side
618630
else ConnectionInputs.RECV_HEADERS
619631
)
632+
self.config.logger.debug("Process input %s", connection_input)
620633
self.state_machine.process_input(connection_input)
621634

622635
# Set up stream 1.
@@ -686,9 +699,12 @@ def get_next_available_stream_id(self):
686699
# No streams have been opened yet, so return the lowest allowed stream
687700
# ID.
688701
if not self.highest_outbound_stream_id:
689-
return 1 if self.config.client_side else 2
690-
691-
next_stream_id = self.highest_outbound_stream_id + 2
702+
next_stream_id = 1 if self.config.client_side else 2
703+
else:
704+
next_stream_id = self.highest_outbound_stream_id + 2
705+
self.config.logger.debug(
706+
"Next available stream ID %d", next_stream_id
707+
)
692708
if next_stream_id > self.HIGHEST_ALLOWED_STREAM_ID:
693709
raise NoAvailableStreamIDError("Exhausted allowed stream IDs")
694710

@@ -798,6 +814,10 @@ def send_headers(self, stream_id, headers, end_stream=False,
798814
799815
:returns: Nothing
800816
"""
817+
self.config.logger.debug(
818+
"Send headers on stream ID %d", stream_id
819+
)
820+
801821
# Check we can open the stream.
802822
if stream_id not in self.streams:
803823
max_open_streams = self.remote_settings.max_concurrent_streams
@@ -871,6 +891,9 @@ def send_data(self, stream_id, data, end_stream=False, pad_length=None):
871891
:type pad_length: ``int``
872892
:returns: Nothing
873893
"""
894+
self.config.logger.debug(
895+
"Send data on stream ID %d with len %d", stream_id, len(data)
896+
)
874897
frame_size = len(data)
875898
if pad_length is not None:
876899
if not isinstance(pad_length, int):
@@ -879,6 +902,9 @@ def send_data(self, stream_id, data, end_stream=False, pad_length=None):
879902
raise ValueError("pad_length must be within range: [0, 255]")
880903
# Account for padding bytes plus the 1-byte padding length field.
881904
frame_size += pad_length + 1
905+
self.config.logger.debug(
906+
"Frame size on stream ID %d is %d", stream_id, frame_size
907+
)
882908

883909
if frame_size > self.local_flow_control_window(stream_id):
884910
raise FlowControlError(
@@ -899,6 +925,10 @@ def send_data(self, stream_id, data, end_stream=False, pad_length=None):
899925
self._prepare_for_sending(frames)
900926

901927
self.outbound_flow_control_window -= frame_size
928+
self.config.logger.debug(
929+
"Outbound flow control window size is %d",
930+
self.outbound_flow_control_window
931+
)
902932
assert self.outbound_flow_control_window >= 0
903933

904934
def end_stream(self, stream_id):
@@ -912,6 +942,7 @@ def end_stream(self, stream_id):
912942
:type stream_id: ``int``
913943
:returns: Nothing
914944
"""
945+
self.config.logger.debug("End stream ID %d", stream_id)
915946
self.state_machine.process_input(ConnectionInputs.SEND_DATA)
916947
frames = self.streams[stream_id].end_stream()
917948
self._prepare_for_sending(frames)
@@ -953,6 +984,10 @@ def increment_flow_control_window(self, increment, stream_id=None):
953984
f.window_increment = increment
954985
frames = [f]
955986

987+
self.config.logger.debug(
988+
"Increase stream ID %d flow control window by %d",
989+
stream_id, increment
990+
)
956991
self._prepare_for_sending(frames)
957992

958993
def push_stream(self, stream_id, promised_stream_id, request_headers):
@@ -977,6 +1012,10 @@ def push_stream(self, stream_id, promised_stream_id, request_headers):
9771012
:class:`HeaderTuple <hpack:hpack.HeaderTuple>` objects.
9781013
:returns: Nothing
9791014
"""
1015+
self.config.logger.debug(
1016+
"Send Push Promise frame on stream ID %d", stream_id
1017+
)
1018+
9801019
if not self.remote_settings.enable_push:
9811020
raise ProtocolError("Remote peer has disabled stream push")
9821021

@@ -1010,6 +1049,8 @@ def ping(self, opaque_data):
10101049
PING frame.
10111050
:returns: Nothing
10121051
"""
1052+
self.config.logger.debug("Send Ping frame")
1053+
10131054
if not isinstance(opaque_data, bytes) or len(opaque_data) != 8:
10141055
raise ValueError("Invalid value for ping data: %r" % opaque_data)
10151056

@@ -1035,6 +1076,7 @@ def reset_stream(self, stream_id, error_code=0):
10351076
:type error_code: ``int``
10361077
:returns: Nothing
10371078
"""
1079+
self.config.logger.debug("Reset stream ID %d", stream_id)
10381080
self.state_machine.process_input(ConnectionInputs.SEND_RST_STREAM)
10391081
stream = self._get_stream_by_id(stream_id)
10401082
frames = stream.reset_stream(error_code)
@@ -1057,6 +1099,7 @@ def close_connection(self, error_code=0, additional_data=None,
10571099
by the sender. Defaults to ``highest_inbound_stream_id``.
10581100
:returns: Nothing
10591101
"""
1102+
self.config.logger.debug("Close connection")
10601103
self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY)
10611104

10621105
# Additional_data must be bytes
@@ -1081,6 +1124,9 @@ def update_settings(self, new_settings):
10811124
10821125
:param new_settings: A dictionary of {setting: new value}
10831126
"""
1127+
self.config.logger.debug(
1128+
"Update connection settings to %s", new_settings
1129+
)
10841130
self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS)
10851131
self.local_settings.update(new_settings)
10861132
s = SettingsFrame(0)
@@ -1314,6 +1360,10 @@ def acknowledge_received_data(self, acknowledged_size, stream_id):
13141360
:returns: Nothing
13151361
:rtype: ``None``
13161362
"""
1363+
self.config.logger.debug(
1364+
"Ack received data on stream ID %d with size %d",
1365+
stream_id, acknowledged_size
1366+
)
13171367
if stream_id <= 0:
13181368
raise ValueError(
13191369
"Stream ID %d is not valid for acknowledge_received_data" %
@@ -1459,6 +1509,10 @@ def receive_data(self, data):
14591509
:returns: A list of events that the remote peer triggered by sending
14601510
this data.
14611511
"""
1512+
self.config.logger.debug(
1513+
"Process received data on connection. Received data: %r", data
1514+
)
1515+
14621516
events = []
14631517
self.incoming_buffer.add_data(data)
14641518
self.incoming_buffer.max_frame_size = self.max_inbound_frame_size

0 commit comments

Comments
 (0)