Skip to content

Commit 59a04f4

Browse files
feat(azure-core): Add configurable logging level to HttpLoggingPolicy
- Add logging_level parameter to HttpLoggingPolicy.__init__ with default logging.INFO - Replace hardcoded logging.INFO checks and logger.info() calls with configurable level - Add tests for custom log level in both sync and async test suites - Maintain backward compatibility with default INFO level
1 parent 3ca25cf commit 59a04f4

File tree

3 files changed

+109
-19
lines changed

3 files changed

+109
-19
lines changed

sdk/core/azure-core/azure/core/pipeline/policies/_universal.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ class HttpLoggingPolicy(
389389
390390
:param logger: The logger to use for logging. Default to azure.core.pipeline.policies.http_logging_policy.
391391
:type logger: logging.Logger
392+
:keyword int logging_level: The logging level to use for request and response logs. Defaults to logging.INFO.
392393
"""
393394

394395
DEFAULT_HEADERS_ALLOWLIST: Set[str] = set(
@@ -425,8 +426,9 @@ class HttpLoggingPolicy(
425426
REDACTED_PLACEHOLDER: str = "REDACTED"
426427
MULTI_RECORD_LOG: str = "AZURE_SDK_LOGGING_MULTIRECORD"
427428

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

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

456-
if not logger.isEnabledFor(logging.INFO):
458+
if not logger.isEnabledFor(self.logging_level):
457459
return
458460

459461
try:
@@ -466,25 +468,25 @@ def on_request( # pylint: disable=too-many-return-statements
466468

467469
multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False)
468470
if multi_record:
469-
logger.info("Request URL: %r", redacted_url)
470-
logger.info("Request method: %r", http_request.method)
471-
logger.info("Request headers:")
471+
logger.log(self.logging_level, "Request URL: %r", redacted_url)
472+
logger.log(self.logging_level, "Request method: %r", http_request.method)
473+
logger.log(self.logging_level, "Request headers:")
472474
for header, value in http_request.headers.items():
473475
value = self._redact_header(header, value)
474-
logger.info(" %r: %r", header, value)
476+
logger.log(self.logging_level, " %r: %r", header, value)
475477
if isinstance(http_request.body, types.GeneratorType):
476-
logger.info("File upload")
478+
logger.log(self.logging_level, "File upload")
477479
return
478480
try:
479481
if isinstance(http_request.body, types.AsyncGeneratorType):
480-
logger.info("File upload")
482+
logger.log(self.logging_level, "File upload")
481483
return
482484
except AttributeError:
483485
pass
484486
if http_request.body:
485-
logger.info("A body is sent with the request")
487+
logger.log(self.logging_level, "A body is sent with the request")
486488
return
487-
logger.info("No body was attached to the request")
489+
logger.log(self.logging_level, "No body was attached to the request")
488490
return
489491
log_string = "Request URL: '{}'".format(redacted_url)
490492
log_string += "\nRequest method: '{}'".format(http_request.method)
@@ -494,21 +496,21 @@ def on_request( # pylint: disable=too-many-return-statements
494496
log_string += "\n '{}': '{}'".format(header, value)
495497
if isinstance(http_request.body, types.GeneratorType):
496498
log_string += "\nFile upload"
497-
logger.info(log_string)
499+
logger.log(self.logging_level, log_string)
498500
return
499501
try:
500502
if isinstance(http_request.body, types.AsyncGeneratorType):
501503
log_string += "\nFile upload"
502-
logger.info(log_string)
504+
logger.log(self.logging_level, log_string)
503505
return
504506
except AttributeError:
505507
pass
506508
if http_request.body:
507509
log_string += "\nA body is sent with the request"
508-
logger.info(log_string)
510+
logger.log(self.logging_level, log_string)
509511
return
510512
log_string += "\nNo body was attached to the request"
511-
logger.info(log_string)
513+
logger.log(self.logging_level, log_string)
512514

513515
except Exception: # pylint: disable=broad-except
514516
logger.warning("Failed to log request.")
@@ -535,23 +537,23 @@ def on_response(
535537
logger = request.context.setdefault("logger", options.pop("logger", self.logger))
536538

537539
try:
538-
if not logger.isEnabledFor(logging.INFO):
540+
if not logger.isEnabledFor(self.logging_level):
539541
return
540542

541543
multi_record = os.environ.get(HttpLoggingPolicy.MULTI_RECORD_LOG, False)
542544
if multi_record:
543-
logger.info("Response status: %r", http_response.status_code)
544-
logger.info("Response headers:")
545+
logger.log(self.logging_level, "Response status: %r", http_response.status_code)
546+
logger.log(self.logging_level, "Response headers:")
545547
for res_header, value in http_response.headers.items():
546548
value = self._redact_header(res_header, value)
547-
logger.info(" %r: %r", res_header, value)
549+
logger.log(self.logging_level, " %r: %r", res_header, value)
548550
return
549551
log_string = "Response status: {}".format(http_response.status_code)
550552
log_string += "\nResponse headers:"
551553
for res_header, value in http_response.headers.items():
552554
value = self._redact_header(res_header, value)
553555
log_string += "\n '{}': '{}'".format(res_header, value)
554-
logger.info(log_string)
556+
logger.log(self.logging_level, log_string)
555557
except Exception: # pylint: disable=broad-except
556558
logger.warning("Failed to log response.")
557559

sdk/core/azure-core/tests/async_tests/test_http_logging_policy_async.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,50 @@ def emit(self, record):
255255
mock_handler.reset()
256256

257257

258+
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
259+
def test_http_logger_with_custom_log_level(http_request, http_response):
260+
class MockHandler(logging.Handler):
261+
def __init__(self):
262+
super(MockHandler, self).__init__()
263+
self.messages = []
264+
265+
def reset(self):
266+
self.messages = []
267+
268+
def emit(self, record):
269+
self.messages.append(record)
270+
271+
mock_handler = MockHandler()
272+
273+
logger = logging.getLogger("testlogger")
274+
logger.addHandler(mock_handler)
275+
logger.setLevel(logging.DEBUG)
276+
277+
policy = HttpLoggingPolicy(logger=logger, logging_level=logging.DEBUG)
278+
279+
universal_request = http_request("GET", "http://localhost/")
280+
http_response = create_http_response(http_response, universal_request, None)
281+
http_response.status_code = 202
282+
request = PipelineRequest(universal_request, PipelineContext(None))
283+
284+
policy.on_request(request)
285+
response = PipelineResponse(request, http_response, request.context)
286+
policy.on_response(request, response)
287+
288+
assert all(m.levelname == "DEBUG" for m in mock_handler.messages)
289+
assert len(mock_handler.messages) == 2
290+
messages_request = mock_handler.messages[0].message.split("\n")
291+
messages_response = mock_handler.messages[1].message.split("\n")
292+
assert messages_request[0] == "Request URL: 'http://localhost/'"
293+
assert messages_request[1] == "Request method: 'GET'"
294+
assert messages_request[2] == "Request headers:"
295+
assert messages_request[3] == "No body was attached to the request"
296+
assert messages_response[0] == "Response status: 202"
297+
assert messages_response[1] == "Response headers:"
298+
299+
mock_handler.reset()
300+
301+
258302
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
259303
@pytest.mark.skipif(sys.version_info < (3, 6), reason="types.AsyncGeneratorType does not exist in 3.5")
260304
def test_http_logger_with_generator_body(http_request, http_response):

sdk/core/azure-core/tests/test_http_logging_policy.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,50 @@ def emit(self, record):
260260
mock_handler.reset()
261261

262262

263+
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
264+
def test_http_logger_with_custom_log_level(http_request, http_response):
265+
class MockHandler(logging.Handler):
266+
def __init__(self):
267+
super(MockHandler, self).__init__()
268+
self.messages = []
269+
270+
def reset(self):
271+
self.messages = []
272+
273+
def emit(self, record):
274+
self.messages.append(record)
275+
276+
mock_handler = MockHandler()
277+
278+
logger = logging.getLogger("testlogger")
279+
logger.addHandler(mock_handler)
280+
logger.setLevel(logging.DEBUG)
281+
282+
policy = HttpLoggingPolicy(logger=logger, logging_level=logging.DEBUG)
283+
284+
universal_request = http_request("GET", "http://localhost/")
285+
http_response = create_http_response(http_response, universal_request, None)
286+
http_response.status_code = 202
287+
request = PipelineRequest(universal_request, PipelineContext(None))
288+
289+
policy.on_request(request)
290+
response = PipelineResponse(request, http_response, request.context)
291+
policy.on_response(request, response)
292+
293+
assert all(m.levelname == "DEBUG" for m in mock_handler.messages)
294+
assert len(mock_handler.messages) == 2
295+
messages_request = mock_handler.messages[0].message.split("\n")
296+
messages_response = mock_handler.messages[1].message.split("\n")
297+
assert messages_request[0] == "Request URL: 'http://localhost/'"
298+
assert messages_request[1] == "Request method: 'GET'"
299+
assert messages_request[2] == "Request headers:"
300+
assert messages_request[3] == "No body was attached to the request"
301+
assert messages_response[0] == "Response status: 202"
302+
assert messages_response[1] == "Response headers:"
303+
304+
mock_handler.reset()
305+
306+
263307
@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
264308
def test_http_logger_with_generator_body(http_request, http_response):
265309
class MockHandler(logging.Handler):

0 commit comments

Comments
 (0)