Skip to content
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

feat: Added Extensive Loggers #95

Merged
merged 7 commits into from
Oct 14, 2024
Merged

Conversation

Ashutosh619-sudo
Copy link
Contributor

@Ashutosh619-sudo Ashutosh619-sudo commented Oct 8, 2024

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a method to manage the visibility of private data in API requests.
  • Improvements

    • Enhanced logging capabilities for better tracking of API request payloads and responses.
    • Improved error handling for XML parsing to manage potential bad formats in API responses.
  • Chores

    • Updated version number from 1.23.0 to 1.23.3 in the project setup.

https://app.clickup.com/1864988/v/l/6-901603904304-1

Copy link

coderabbitai bot commented Oct 8, 2024

Walkthrough

The changes in the pull request focus on enhancements to the ApiBase class within the sageintacctsdk/apis/api_base.py file. Key modifications include the addition of logging functionality for HTTP POST requests and improved error handling for XML parsing. A new method, set_show_private, has been introduced to manage the visibility of private data in request payloads. Additionally, the version number in the setup.py file has been updated from 1.23.0 to 1.23.3.

Changes

File Change Summary
sageintacctsdk/apis/api_base.py - Added logging functionality with a logger set to WARNING level.
- Enhanced __post_request method with error handling for XML parsing.
- Introduced set_show_private method to manage the __show_private attribute.
setup.py - Updated version from 1.23.0 to 1.23.3.

Poem

In the code where rabbits play,
Logging now lights the way! 🐇✨
With errors caught and data shown,
Our API's grown, it's fully grown!
Private bits, we can now share,
Hopping forward, without a care!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Oct 8, 2024

PR description must contain a link to a ClickUp (case-insensitive)

1 similar comment
Copy link

github-actions bot commented Oct 8, 2024

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
sageintacctsdk/apis/api_base.py (3)

Line range hint 47-50: Initialize self.__show_private in the constructor

The attribute self.__show_private is used in format_and_send_request, but it is not initialized in the constructor. If set_show_private is not called before format_and_send_request, this could lead to an AttributeError. Consider initializing self.__show_private in the __init__ method with a default value, such as False.


Line range hint 266-270: Avoid silently passing exceptions

In the if self.__show_private: block, catching a KeyError and passing silently can mask underlying issues. Instead of using a try-except block, consider checking if the necessary keys exist before assigning options. For example:

if self.__show_private:
    function_dict = dict_body['request']['operation']['content']['function']
    if 'query' in function_dict:
        options = {'showprivate': True}
        function_dict['query']['options'] = options

This approach ensures that you only attempt to set options when the 'query' key exists, making the code more robust and maintainable.


Line range hint 47-52: Enhance the docstring for set_show_private

To improve clarity, consider enhancing the docstring for set_show_private to better explain its purpose and usage. For example:

def set_show_private(self, show_private: bool):
    """
    Enable or disable the inclusion of private data in API responses.

    :param show_private: If True, include private data in the API request options.
    :return: None
    """

This provides a clearer understanding of the method's functionality for future reference.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c254d56 and 7dc9acf.

📒 Files selected for processing (1)
  • sageintacctsdk/apis/api_base.py (4 hunks)
🧰 Additional context used

Comment on lines 21 to 22
logger = logging.getLogger(__name__)
logger.level = logging.WARNING
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use setLevel method to set the logger level

Instead of setting the logger level directly with logger.level = logging.WARNING, consider using the setLevel method:

logger.setLevel(logging.WARNING)

This is the standard way to configure the logging level and ensures better compatibility with logging configurations.

@@ -195,6 +200,8 @@ def __post_request(self, dict_body: dict, api_url: str):
parsed_xml = xmltodict.parse(raw_response, force_list={self.__dimension})['root']
parsed_response = json.loads(json.dumps(parsed_xml))


logger.debug('Response for post request: %s', raw_response.text)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid logging sensitive information in response

Similarly, logging the entire response with logger.debug('Response for post request: %s', raw_response.text) might expose sensitive information returned from the API. It's advisable to sanitize the response or limit the logging to essential non-sensitive information to enhance security.

Copy link

github-actions bot commented Oct 8, 2024

PR description must contain a link to a ClickUp (case-insensitive)

1 similar comment
Copy link

github-actions bot commented Oct 8, 2024

PR description must contain a link to a ClickUp (case-insensitive)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
sageintacctsdk/apis/api_base.py (4)

5-5: LGTM! Consider using setLevel method for setting logger level.

The addition of logging functionality is a good practice. However, as suggested in a previous review, consider using the setLevel method instead of directly setting the logger level:

logger = logging.getLogger(__name__)
logger.setLevel(logging.WARNING)

This is the standard way to configure the logging level and ensures better compatibility with logging configurations.

Also applies to: 21-22


Line range hint 53-59: LGTM! Consider adding a docstring for clarity.

The set_show_private method is a good addition for controlling the visibility of private data. To improve clarity, consider adding a docstring explaining the purpose of the __show_private attribute and its impact on the API behavior.

Example:

def set_show_private(self, show_private: bool):
    """
    Set whether to show private data in API requests.

    :param show_private: If True, include private data in requests.
    """
    self.__show_private = show_private

Line range hint 196-200: LGTM! Consider enhancing error handling and logging.

The addition of XML parsing error handling is a good improvement for dealing with potentially malformed responses from Sage Intacct. To further enhance this:

  1. Consider logging the parsing error for debugging purposes.
  2. You might want to raise a custom exception or add a warning log to track the frequency of these malformed responses.

Example:

try:
    parsed_xml = xmltodict.parse(raw_response.text, force_list={self.__dimension})
except Exception as e:
    logger.warning(f"Received malformed XML from Sage Intacct: {str(e)}")
    raw_response = '<root>' + raw_response.text + '</root>'
    parsed_xml = xmltodict.parse(raw_response, force_list={self.__dimension})['root']

Line range hint 324-328: LGTM! Consider improving clarity of the conditional logic.

The addition of the showprivate option based on the __show_private attribute is a good feature. To improve clarity and robustness, consider the following suggestions:

  1. Instead of using a try-except block, you could use a more explicit conditional check:

    if self.__show_private and 'query' in dict_body['request']['operation']['content']['function']:
        dict_body['request']['operation']['content']['function']['query']['options'] = {'showprivate': True}
  2. Add a comment explaining the purpose of this feature and when it should be used.

These changes will make the code more readable and self-explanatory.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7dc9acf and b67eb29.

📒 Files selected for processing (1)
  • sageintacctsdk/apis/api_base.py (5 hunks)
🧰 Additional context used
🔇 Additional comments (1)
sageintacctsdk/apis/api_base.py (1)

Line range hint 1-600: Overall improvements to ApiBase class

The changes made to the ApiBase class in this pull request significantly enhance its functionality and robustness:

  1. Addition of logging improves debuggability.
  2. Enhanced XML parsing error handling increases resilience against malformed responses.
  3. The new set_show_private method and related logic provide flexibility in handling private data.

These improvements will make the SDK more reliable and easier to work with. The suggested minor adjustments in the previous comments will further refine these enhancements.

Great work on these updates!

@@ -186,6 +190,7 @@ def __post_request(self, dict_body: dict, api_url: str):
A response from the request (dict).
"""

logger.debug('Payload for post request: %s', dict_body)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve logging security and efficiency.

The addition of logging for request payloads and responses is beneficial for debugging. However, consider the following improvements:

  1. Sensitive data protection: As mentioned in a previous review, logging entire payloads and responses may expose sensitive information. Implement data sanitization before logging.

  2. Avoid redundant logging: The debug log on line 204 and info log on line 245 both log the response. Consider removing one of them to avoid redundancy.

  3. Use appropriate log levels: Ensure that debug logs are only enabled in non-production environments to prevent performance issues and potential data leaks.

Example of sanitized logging:

logger.debug('Payload for post request: %s', self._sanitize_data(dict_body))
logger.info('Response status: %s', raw_response.status_code)

Implement a _sanitize_data method to remove or mask sensitive information before logging.

Also applies to: 204-204, 245-245

Copy link
Contributor

@ashwin1111 ashwin1111 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump version as well

@@ -196,6 +201,7 @@ def __post_request(self, dict_body: dict, api_url: str):
parsed_response = json.loads(json.dumps(parsed_xml))

if raw_response.status_code == 200:
logger.debug('Response for post request: %s', raw_response.text)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (parsed_response['response']['control']['status'] == 'failure' or api_response['authentication']['status'] == 'failure' or api_response['result']['status'] == 'failure'):
logger.info
else:
logger.debug

@@ -196,6 +201,12 @@ def __post_request(self, dict_body: dict, api_url: str):
parsed_response = json.loads(json.dumps(parsed_xml))

if raw_response.status_code == 200:
if (parsed_response['response']['control']['status'] == 'failure' or parsed_response['response']['operation']['authentication']['status'] == 'failure' or \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle all keys safely, they may not exist in all case. do it like parsed_response.get('response', {}).get('control', {}).get('status', {})

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
sageintacctsdk/apis/api_base.py (1)

204-213: Approve changes with a minor suggestion

The improvements in error handling and conditional logging are good. The use of get method with default values for safely accessing nested dictionary keys is a best practice.

Consider applying this approach consistently to all dictionary accesses in this block:

result_status = response.get('operation', {}).get('result', {}).get('status', '')

This ensures uniform error handling throughout the method.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1ee9134 and d078f5e.

📒 Files selected for processing (1)
  • sageintacctsdk/apis/api_base.py (5 hunks)
🧰 Additional context used
🔇 Additional comments (4)
sageintacctsdk/apis/api_base.py (4)

Line range hint 193-214: Improve logging security and efficiency

While the addition of logging is beneficial for debugging, consider the following improvements:

  1. Sensitive data protection: Implement data sanitization before logging payloads and responses to prevent exposure of sensitive information.

  2. Use appropriate log levels: Ensure that debug logs are only enabled in non-production environments to prevent performance issues and potential data leaks.

Example of sanitized logging:

logger.debug('Payload for post request: %s', self._sanitize_data(dict_body))
logger.info('Response status: %s', raw_response.status_code)

Implement a _sanitize_data method to remove or mask sensitive information before logging.


Line range hint 52-58: Approve addition of set_show_private method

The new set_show_private method is a good addition to the ApiBase class. It provides more flexibility in controlling the visibility of private data in API requests. The implementation is correct and consistent with the class's coding style.

Make sure to document this new functionality in the class or module docstring to inform users of this new capability.


Line range hint 1-600: Overall improvements enhance code robustness

The changes made to this file, including the addition of logging, improved error handling, and the new set_show_private method, significantly enhance the robustness and flexibility of the ApiBase class. These improvements will aid in debugging and provide more control over API interactions.

Consider the following to further improve the code:

  1. Add comprehensive docstrings for new methods and update existing ones to reflect changes.
  2. Implement a data sanitization method for logging to ensure sensitive information is not exposed.
  3. Review the entire class to ensure consistent use of safe dictionary access methods (get with default values) throughout.

These changes represent a positive step in the evolution of this SDK.


5-5: 🛠️ Refactor suggestion

Use setLevel method for setting logger level

The addition of logging is a good practice. However, consider using the setLevel method instead of directly setting the level:

-logger.level = logging.WARNING
+logger.setLevel(logging.WARNING)

This is the standard way to configure the logging level and ensures better compatibility with logging configurations.

Also applies to: 21-22

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
sageintacctsdk/apis/api_base.py (4)

Line range hint 193-214: Enhance logging security and error handling

The improvements in logging and error handling are commendable. However, consider the following enhancements:

  1. Implement data sanitization before logging to prevent exposure of sensitive information:
def sanitize_data(data):
    # Implement logic to remove or mask sensitive fields
    return sanitized_data

logger.debug('Payload for post request: %s', sanitize_data(dict_body))
  1. Consider using a try-except block for XML parsing instead of a bare except:
try:
    parsed_xml = xmltodict.parse(raw_response.text, force_list={self.__dimension})
except xml.parsers.expat.ExpatError:
    # Handle specific XML parsing errors
    raw_response = '<root>' + raw_response.text + '</root>'
    parsed_xml = xmltodict.parse(raw_response, force_list={self.__dimension})['root']
  1. Use logging.exception() to log the full stack trace in case of exceptions.

204-213: Approve changes with suggestions for further improvement

The improvements in response parsing and conditional logging are good. However, consider the following enhancements:

  1. Further sanitize the logged response to exclude sensitive information:
def sanitize_response(response):
    # Implement logic to remove or mask sensitive fields
    return sanitized_response

if control_status == 'failure' or auth_status == 'failure' or result_status == 'failure':
    logger.info('Response status: %s', sanitize_response(raw_response.text))
else:
    logger.debug('Response status: %s', sanitize_response(raw_response.text))
  1. Consider using an enumeration for status values to avoid magic strings:
from enum import Enum

class Status(Enum):
    SUCCESS = 'success'
    FAILURE = 'failure'

if control_status == Status.FAILURE.value or auth_status == Status.FAILURE.value or result_status == Status.FAILURE.value:
    # ... (logging logic)

Line range hint 51-57: Approve new method with suggestion for input validation

The addition of the set_show_private method is a good way to control the visibility of private data. Consider adding input validation:

def set_show_private(self, show_private: bool):
    """
    Set the show private for APIs
    :param show_private: boolean
    :return: None
    """
    if not isinstance(show_private, bool):
        raise ValueError("show_private must be a boolean")
    self.__show_private = show_private

This ensures that only boolean values are accepted for the show_private parameter.


Line range hint 336-341: Approve changes with suggestion for improved exception handling

The addition of the showprivate option is a good way to control the visibility of private data in API requests. However, the exception handling could be improved:

  1. Use a more specific exception type instead of a bare except:
if self.__show_private:
    try:
        dict_body['request']['operation']['content']['function']['query']['options'] = {'showprivate': True}
    except KeyError:
        # The 'query' key is not present in the function dictionary
        pass
  1. Consider adding the 'options' key if it doesn't exist, instead of catching an exception:
if self.__show_private:
    function = dict_body['request']['operation']['content']['function']
    if 'query' in function:
        function['query'].setdefault('options', {})['showprivate'] = True

This approach is more explicit and avoids the use of exceptions for control flow.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d078f5e and 1409af4.

📒 Files selected for processing (2)
  • sageintacctsdk/apis/api_base.py (5 hunks)
  • setup.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • setup.py
🧰 Additional context used
🔇 Additional comments (1)
sageintacctsdk/apis/api_base.py (1)

Line range hint 1-577: Overall improvements are commendable

The changes made to this file significantly enhance the ApiBase class:

  1. Addition of logging improves debuggability.
  2. Improved error handling, especially for XML parsing, increases robustness.
  3. New functionality for managing private data visibility adds flexibility to API interactions.

Consider the following final suggestions:

  1. Add unit tests for the new and modified methods to ensure their correctness.
  2. Update the class and method docstrings to reflect the new functionality and parameters.
  3. Consider adding a changelog to track these significant improvements.

Great work on these enhancements!

@@ -2,6 +2,7 @@
API Base class with util functions
"""
import json
import logging
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve logger initialization

Good addition of logging functionality. However, consider the following improvements:

  1. Use setLevel method instead of directly setting the level attribute:
logger = logging.getLogger(__name__)
logger.setLevel(logging.WARNING)
  1. Consider allowing the log level to be configurable, perhaps through an environment variable or a configuration file.

Also applies to: 21-22

Comment on lines +254 to +255

logger.info('Response for post request: %s', raw_response.text)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove redundant logging

This logging statement is redundant with the logging added earlier in the method. Additionally, logging the entire response text might expose sensitive information. Consider removing this line or modifying it to log only non-sensitive information:

logger.info('Response status code: %s', raw_response.status_code)

@Ashutosh619-sudo Ashutosh619-sudo merged commit 4750c1f into master Oct 14, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants