-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Cosmos Diagnostics #25678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cosmos Diagnostics #25678
Changes from all commits
c33d42e
3dbbd10
a31ae83
bf2a416
b381009
8e49aff
97a4d97
7c1a152
cfb6768
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -602,6 +602,31 @@ even when it isn't enabled for the client: | |
| database = client.create_database(DATABASE_NAME, logging_enable=True) | ||
| ``` | ||
|
|
||
| Alternatively, you can log using the CosmosHttpLoggingPolicy, which extends from the azure core HttpLoggingPolicy, by passing in your logger to the `logger` argument. | ||
| By default, it will use the behaviour from HttpLoggingPolicy. The `enable_diagnostics_logging` argument will add additional diagnostic information to the logger. | ||
| ```python | ||
| import logging | ||
| from azure.cosmos import CosmosClient | ||
|
|
||
| #Create a logger for the 'azure' SDK | ||
| logger = logging.getLogger('azure') | ||
| logger.setLevel(logging.DEBUG) | ||
|
|
||
| # Configure a file output | ||
| handler = logging.FileHandler(filename="azure") | ||
| logger.addHandler(handler) | ||
|
|
||
| # This client will log diagnostic information from the HTTP session by using the CosmosHttpLoggingPolicy | ||
| client = CosmosClient(URL, credential=KEY, logger=logger, enable_diagnostics_logging=True) | ||
| ``` | ||
| | Name | Policy Flavor | Parameters | Accepted in Init? | Accepted in Request? | Description | | ||
| |-------------------------|------------------|----------------------------|----------------|------------------|-------------------------------------------------------------------------------| | ||
| | CosmosHttpLoggingPolicy | SansIOHTTPPolicy | | | | | | ||
| | | | logger | X | X | If specified, it will be used to log information. | | ||
| | | | enable_diagnostics_logging | X | X | Used to enable logging additional diagnostic information. Defaults to `False` | | ||
| You can learn more about the different policies and how they work [here](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#available-policies) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove this line and the table above. |
||
|
|
||
|
|
||
| ## Next steps | ||
|
|
||
| For more extensive documentation on the Cosmos DB service, see the [Azure Cosmos DB documentation][cosmos_docs] on docs.microsoft.com. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| import os | ||
| import urllib | ||
|
|
||
| from azure.core.pipeline.policies import HttpLoggingPolicy | ||
| import logging | ||
| import types | ||
|
|
||
|
|
||
|
|
||
| class CosmosHttpLoggingPolicy(HttpLoggingPolicy): | ||
|
|
||
| def __init__(self, logger=None, **kwargs): | ||
| self._enable_diagnostics_logging = kwargs.pop("enable_diagnostics_logging", None) | ||
| super().__init__(logger, **kwargs) | ||
| if self._enable_diagnostics_logging: | ||
| self.logger = logger or logging.getLogger(__name__) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this alternative logger? |
||
|
|
||
| def on_request(self, request): # type: (PipelineRequest) -> None | ||
| http_request = request.http_request | ||
| options = request.context.options | ||
| self._enable_diagnostics_logging = request.context.setdefault( | ||
| "enable_diagnostics_logging", | ||
| options.pop("enable_diagnostics_logging", self._enable_diagnostics_logging)) | ||
| if self._enable_diagnostics_logging: | ||
| # Get logger in my context first (request has been retried) | ||
| # then read from kwargs (pop if that's the case) | ||
| # then use my instance logger | ||
| logger = request.context.setdefault("logger", options.pop("logger", self.logger)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this only done in the case of |
||
|
|
||
| if not logger.isEnabledFor(logging.INFO): | ||
| return | ||
|
|
||
| try: | ||
| parsed_url = list(urllib.parse.urlparse(http_request.url)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't duplicate this section of code - could you point out where this deviates from the original implementation so we can figure out the best way to accommodate it?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment applies to response handling. |
||
| parsed_qp = urllib.parse.parse_qsl(parsed_url[4], keep_blank_values=True) | ||
| filtered_qp = [(key, self._redact_query_param(key, value)) for key, value in parsed_qp] | ||
| # 4 is query | ||
| parsed_url[4] = "&".join(["=".join(part) for part in filtered_qp]) | ||
| redacted_url = urllib.parse.urlunparse(parsed_url) | ||
|
|
||
| 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:") | ||
| for header, value in http_request.headers.items(): | ||
| #value = self._redact_header(header, value) | ||
| logger.info(" %r: %r", header, value) | ||
| if isinstance(http_request.body, types.GeneratorType): | ||
| logger.info("File upload") | ||
| return | ||
| try: | ||
| if isinstance(http_request.body, types.AsyncGeneratorType): | ||
| logger.info("File upload") | ||
| return | ||
| except AttributeError: | ||
| pass | ||
| if http_request.body: | ||
| logger.info("A body is sent with the request") | ||
| return | ||
| logger.info("No body was attached to the request") | ||
| return | ||
| log_string = "Request URL: '{}'".format(redacted_url) | ||
| log_string += "\nRequest method: '{}'".format(http_request.method) | ||
| log_string += "\nRequest headers:" | ||
| for header, value in http_request.headers.items(): | ||
| #value = self._redact_header(header, value) | ||
| log_string += "\n '{}': '{}'".format(header, value) | ||
| if isinstance(http_request.body, types.GeneratorType): | ||
| log_string += "\nFile upload" | ||
| logger.info(log_string) | ||
| return | ||
| try: | ||
| if isinstance(http_request.body, types.AsyncGeneratorType): | ||
| log_string += "\nFile upload" | ||
| logger.info(log_string) | ||
| return | ||
| except AttributeError: | ||
| pass | ||
| if http_request.body: | ||
| log_string += "\nA body is sent with the request" | ||
| logger.info(log_string) | ||
| return | ||
| log_string += "\nNo body was attached to the request" | ||
| logger.info(log_string) | ||
|
|
||
| except Exception as err: # pylint: disable=broad-except | ||
| logger.warning("Failed to log request: %s", repr(err)) | ||
| else: | ||
| super().on_request(request) | ||
|
|
||
|
|
||
| def on_response(self, request, response): # type: (PipelineRequest, PipelineResponse) -> None | ||
| if self._enable_diagnostics_logging: | ||
| http_response = response.http_response | ||
| ir = http_response.internal_response | ||
| try: | ||
| logger = response.context["logger"] | ||
|
|
||
| if not logger.isEnabledFor(logging.INFO): | ||
| 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 status reason: %r", ir.reason) | ||
| try: | ||
| logger.info("Elapsed Time: %r", ir.elapsed) | ||
| except AttributeError: | ||
| logger.info("Elapsed Time: %r", None) | ||
| if http_response.status_code >= 400: | ||
| sm = ir.text | ||
| sm.replace("true", "True") | ||
| sm.replace("false", "False") | ||
| temp_sm = eval(sm) | ||
| temp_sm['message'] = temp_sm['message'].replace("\r", " ") | ||
| logger.into("Response status error message: %r", temp_sm['message']) | ||
| logger.info("Response headers:") | ||
| for res_header, value in http_response.headers.items(): | ||
| logger.info(" %r: %r", res_header, value) | ||
| return | ||
| log_string = "Response status: {}".format(http_response.status_code) | ||
| log_string += "\nResponse status reason: {}".format(ir.reason) | ||
| try: | ||
| log_string += "\nElapsed Time: {}".format(ir.elapsed) | ||
| except AttributeError: | ||
| log_string += "\nElapsed Time: {}".format(None) | ||
| if http_response.status_code >= 400: | ||
| sm = ir.text | ||
| sm.replace("true", "True") | ||
| sm.replace("false", "False") | ||
| temp_sm = eval(sm) | ||
| temp_sm['message'] = temp_sm['message'].replace("\r", " ") | ||
| log_string += "\nResponse status error message: {}".format(temp_sm['message']) | ||
| log_string += "\nResponse headers:" | ||
| for res_header, value in http_response.headers.items(): | ||
| log_string += "\n '{}': '{}'".format(res_header, value) | ||
| logger.info(log_string) | ||
| except Exception as err: # pylint: disable=broad-except | ||
| logger.warning("Failed to log response: %s", repr(err)) | ||
| else: | ||
| super().on_response(request, response) | ||
|
|
||
| # # Current System info | ||
| # def get_system_info(self): | ||
| # ret = {} | ||
| # ret["system"] = platform.system() | ||
| # ret["python version"] = platform.python_version() | ||
| # ret["architecture"] = platform.architecture() | ||
| # ret["cpu"] = platform.processor() | ||
| # ret["cpu count"] = os.cpu_count() | ||
| # ret["machine"] = platform.machine() | ||
| # | ||
| # return ret | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The HttpLoggingPolicy is a developer concept - not really an end user concept - so I would not refer to it by name, just discuss the keyword argument.