Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions sdk/core/azure-core/azure/core/pipeline/policies/_universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ class HttpLoggingPolicy(

:param logger: The logger to use for logging. Default to azure.core.pipeline.policies.http_logging_policy.
:type logger: logging.Logger
:keyword int logging_level: The logging level to use for request and response logs. Defaults to logging.INFO.
:type logging_level: int
"""

DEFAULT_HEADERS_ALLOWLIST: Set[str] = set(
Expand Down Expand Up @@ -425,8 +427,11 @@ class HttpLoggingPolicy(
REDACTED_PLACEHOLDER: str = "REDACTED"
MULTI_RECORD_LOG: str = "AZURE_SDK_LOGGING_MULTIRECORD"

def __init__(self, logger: Optional[logging.Logger] = None, **kwargs: Any): # pylint: disable=unused-argument
def __init__(
self, logger: Optional[logging.Logger] = None, *, logging_level: int = logging.INFO, **kwargs: Any
): # pylint: disable=unused-argument
self.logger: logging.Logger = logger or logging.getLogger("azure.core.pipeline.policies.http_logging_policy")
self.logging_level: int = logging_level
self.allowed_query_params: Set[str] = set()
self.allowed_header_names: Set[str] = set(self.__class__.DEFAULT_HEADERS_ALLOWLIST)

Expand All @@ -453,7 +458,7 @@ def on_request( # pylint: disable=too-many-return-statements
# then use my instance logger
logger = request.context.setdefault("logger", options.pop("logger", self.logger))

if not logger.isEnabledFor(logging.INFO):
if not logger.isEnabledFor(self.logging_level):
return

try:
Expand All @@ -466,25 +471,25 @@ def on_request( # pylint: disable=too-many-return-statements

multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False)
if multi_record:
logger.info("Request URL: %r", redacted_url)
logger.info("Request method: %r", http_request.method)
logger.info("Request headers:")
logger.log(self.logging_level, "Request URL: %r", redacted_url)
logger.log(self.logging_level, "Request method: %r", http_request.method)
logger.log(self.logging_level, "Request headers:")
for header, value in http_request.headers.items():
value = self._redact_header(header, value)
logger.info(" %r: %r", header, value)
logger.log(self.logging_level, " %r: %r", header, value)
if isinstance(http_request.body, types.GeneratorType):
logger.info("File upload")
logger.log(self.logging_level, "File upload")
return
try:
if isinstance(http_request.body, types.AsyncGeneratorType):
logger.info("File upload")
logger.log(self.logging_level, "File upload")
return
except AttributeError:
pass
if http_request.body:
logger.info("A body is sent with the request")
logger.log(self.logging_level, "A body is sent with the request")
return
logger.info("No body was attached to the request")
logger.log(self.logging_level, "No body was attached to the request")
return
log_string = "Request URL: '{}'".format(redacted_url)
log_string += "\nRequest method: '{}'".format(http_request.method)
Expand All @@ -494,21 +499,21 @@ def on_request( # pylint: disable=too-many-return-statements
log_string += "\n '{}': '{}'".format(header, value)
if isinstance(http_request.body, types.GeneratorType):
log_string += "\nFile upload"
logger.info(log_string)
logger.log(self.logging_level, log_string)
return
try:
if isinstance(http_request.body, types.AsyncGeneratorType):
log_string += "\nFile upload"
logger.info(log_string)
logger.log(self.logging_level, log_string)
return
except AttributeError:
pass
if http_request.body:
log_string += "\nA body is sent with the request"
logger.info(log_string)
logger.log(self.logging_level, log_string)
return
log_string += "\nNo body was attached to the request"
logger.info(log_string)
logger.log(self.logging_level, log_string)

except Exception: # pylint: disable=broad-except
logger.warning("Failed to log request.")
Expand All @@ -535,23 +540,23 @@ def on_response(
logger = request.context.setdefault("logger", options.pop("logger", self.logger))

try:
if not logger.isEnabledFor(logging.INFO):
if not logger.isEnabledFor(self.logging_level):
return

multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False)
if multi_record:
logger.info("Response status: %r", http_response.status_code)
logger.info("Response headers:")
logger.log(self.logging_level, "Response status: %r", http_response.status_code)
logger.log(self.logging_level, "Response headers:")
for res_header, value in http_response.headers.items():
value = self._redact_header(res_header, value)
logger.info(" %r: %r", res_header, value)
logger.log(self.logging_level, " %r: %r", res_header, value)
return
log_string = "Response status: {}".format(http_response.status_code)
log_string += "\nResponse headers:"
for res_header, value in http_response.headers.items():
value = self._redact_header(res_header, value)
log_string += "\n '{}': '{}'".format(res_header, value)
logger.info(log_string)
logger.log(self.logging_level, log_string)
except Exception: # pylint: disable=broad-except
logger.warning("Failed to log response.")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,50 @@ def emit(self, record):
mock_handler.reset()


@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
def test_http_logger_with_custom_log_level(http_request, http_response):
class MockHandler(logging.Handler):
def __init__(self):
super(MockHandler, self).__init__()
self.messages = []

def reset(self):
self.messages = []

def emit(self, record):
self.messages.append(record)

mock_handler = MockHandler()

logger = logging.getLogger("testlogger")
logger.addHandler(mock_handler)
logger.setLevel(logging.DEBUG)

policy = HttpLoggingPolicy(logger=logger, logging_level=logging.DEBUG)

universal_request = http_request("GET", "http://localhost/")
http_response = create_http_response(http_response, universal_request, None)
http_response.status_code = 202
request = PipelineRequest(universal_request, PipelineContext(None))

policy.on_request(request)
response = PipelineResponse(request, http_response, request.context)
policy.on_response(request, response)

assert all(m.levelname == "DEBUG" for m in mock_handler.messages)
assert len(mock_handler.messages) == 2
messages_request = mock_handler.messages[0].message.split("\n")
messages_response = mock_handler.messages[1].message.split("\n")
assert messages_request[0] == "Request URL: 'http://localhost/'"
assert messages_request[1] == "Request method: 'GET'"
assert messages_request[2] == "Request headers:"
assert messages_request[3] == "No body was attached to the request"
assert messages_response[0] == "Response status: 202"
assert messages_response[1] == "Response headers:"

mock_handler.reset()


@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
@pytest.mark.skipif(sys.version_info < (3, 6), reason="types.AsyncGeneratorType does not exist in 3.5")
def test_http_logger_with_generator_body(http_request, http_response):
Expand Down
44 changes: 44 additions & 0 deletions sdk/core/azure-core/tests/test_http_logging_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,50 @@ def emit(self, record):
mock_handler.reset()


@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
def test_http_logger_with_custom_log_level(http_request, http_response):
class MockHandler(logging.Handler):
def __init__(self):
super(MockHandler, self).__init__()
self.messages = []

def reset(self):
self.messages = []

def emit(self, record):
self.messages.append(record)

mock_handler = MockHandler()

logger = logging.getLogger("testlogger")
logger.addHandler(mock_handler)
logger.setLevel(logging.DEBUG)

policy = HttpLoggingPolicy(logger=logger, logging_level=logging.DEBUG)

universal_request = http_request("GET", "http://localhost/")
http_response = create_http_response(http_response, universal_request, None)
http_response.status_code = 202
request = PipelineRequest(universal_request, PipelineContext(None))

policy.on_request(request)
response = PipelineResponse(request, http_response, request.context)
policy.on_response(request, response)

assert all(m.levelname == "DEBUG" for m in mock_handler.messages)
assert len(mock_handler.messages) == 2
messages_request = mock_handler.messages[0].message.split("\n")
messages_response = mock_handler.messages[1].message.split("\n")
assert messages_request[0] == "Request URL: 'http://localhost/'"
assert messages_request[1] == "Request method: 'GET'"
assert messages_request[2] == "Request headers:"
assert messages_request[3] == "No body was attached to the request"
assert messages_response[0] == "Response status: 202"
assert messages_response[1] == "Response headers:"

mock_handler.reset()


@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
def test_http_logger_with_generator_body(http_request, http_response):
class MockHandler(logging.Handler):
Expand Down
Loading