From 569df7abdd9ed1a2d5ebb1cda7e193a40f84c47e Mon Sep 17 00:00:00 2001 From: Shivam Garg Date: Thu, 18 Jun 2026 09:25:59 +0100 Subject: [PATCH] Release 4.9.1 (#419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Clarify use of the next banch * Add __version__ attribute using VERSION file as single source of truth This implements the requested feature to expose bugsnag.__version__ for programmatic version checking, as per PEP 396. Changes: - Created VERSION file containing current version (4.8.0) - Updated bugsnag/__init__.py to read from VERSION and expose __version__ - Updated setup.py to read version from VERSION file - Updated bugsnag/notifier.py to read version from VERSION file - Added MANIFEST.in to include VERSION in package distributions - Updated CHANGELOG.md with enhancement entry - Updated CONTRIBUTING.md to document simplified release process Benefits: - Single source of truth for version number (no more manual updates in 3 places) - bugsnag.__version__ now available for programmatic access - Graceful fallback to 'unknown' if VERSION file is missing * Add __version__ attribute with Makefile-based version management Implements bugsnag.__version__ for programmatic version checking (PEP 396). Uses Makefile approach (inspired by bugsnag-cocoa) to address performance concerns. Changes: - Added __version__ = '4.8.0' to bugsnag/__init__.py - Hardcoded version='4.8.0' in setup.py - Hardcoded 'version': '4.8.0' in bugsnag/notifier.py - Added Makefile with bump target for automated version updates - Updated CHANGELOG.md with enhancement entry - Updated CONTRIBUTING.md to document Makefile usage - Removed VERSION file and MANIFEST.in (no runtime file I/O overhead) - Updated features/support/env.rb (removed VERSION from test fixtures) Usage: - Users: bugsnag.__version__ returns '4.8.0' - Maintainers: make VERSION=x.y.z bump (updates all 3 files) Benefits: - Zero runtime overhead (no file I/O during import) - bugsnag.__version__ attribute now available - Automated version updates via single Makefile command - Addresses reviewer feedback on performance concerns * Address review feedback - Restore flask extras_require (accidental removal) - Update CHANGELOG.md format per reviewer suggestion - All 532 core unit tests passing * Stabilize AWS Maze Runner & migrate ASGI tests to Starlette 1.0+ (#411) Address PLAT-15480: Fix CI failures in AWS Lambda Maze Runner tests and ensure ASGI integration tests are compatible with Starlette 1.0+ Changes: * Enable Python 3.14 for AWS Lambda feature tests (removed @not-python-3.14 tags) * Increase Maze Runner timeouts for AWS Lambda cold starts (10s → 20s) * Migrate ASGI tests from deprecated @app.route() to app.add_route() for Starlette 1.0+ compatibility * Support for level parameter in constructor * PLAT-5615 level parameter fixes * PLAT-5615 lint issue fixes * PLAT-5615 minor issue fixes * PLAT-16543 Strengthen gitignore rules * remove duplicate entry from gitignore file * PLAT-16625-github action nodejs version upgrade * release v4.9.1 --------- Co-authored-by: Steve Kirkland Co-authored-by: Steve Kirkland Co-authored-by: SB-PawanN Co-authored-by: ForroKulcs <65923725+ForroKulcs@users.noreply.github.com> Co-authored-by: SB-jigneshR Co-authored-by: Aditya Patil Co-authored-by: Khushbu Maheshwari --- .github/workflows/maze-runner.yml | 2 +- .github/workflows/python-package.yml | 4 +- .gitignore | 31 ++++++++++- CHANGELOG.md | 7 +++ bugsnag/__init__.py | 2 +- bugsnag/client.py | 8 ++- bugsnag/handlers.py | 4 +- bugsnag/notifier.py | 2 +- setup.py | 2 +- tests/test_handlers.py | 80 ++++++++++++++++++++++++++++ 10 files changed, 130 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 1fe130bd..85d26756 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -12,7 +12,7 @@ jobs: python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install libcurl4-openssl-dev and net-tools run: | diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ee790e55..5cfef9cf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,10 +16,10 @@ jobs: os: 'ubuntu-22.04' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index a0aa2a33..54bf33ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,16 @@ .aws-sam -maze-runner.log maze_output -Gemfile.lock +# Python compiled files *.py[co] +*.pyd +__pycache__/ # Packages *.egg *.egg* *.egg-info +*.whl dist build eggs @@ -25,6 +27,8 @@ pip-log.txt # Unit test / coverage reports .coverage* .tox +.pytest_cache/ +.mypy_cache/ htmlcov #Translations @@ -45,3 +49,26 @@ bin include lib pyvenv.cfg + +# Logs +*.log + +# Lock files +*.lock + +# Environment & secrets +.env +.env.* + +# OS files +.DS_Store +Thumbs.db + +# Ruby version manager (for Maze Runner local testing) +.ruby-version + +# Gitleaks +.gitleaks.toml +.pre-commit-config.yaml +.gitleaks-baseline.json + diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fda530e..7c3d525b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## v4.9.1 (2026-06-18) + +### Enhancements + +* Add support for `level` parameter in `BugsnagHandler` constructor and `Client.log_handler()` method. + [#416](https://github.com/bugsnag/bugsnag-python/pull/416) + ## v4.9.0 (2026-04-21) ### Enhancements diff --git a/bugsnag/__init__.py b/bugsnag/__init__.py index e9d93e08..925ae5b1 100644 --- a/bugsnag/__init__.py +++ b/bugsnag/__init__.py @@ -18,7 +18,7 @@ clear_feature_flag, clear_feature_flags, aws_lambda_handler) -__version__ = '4.9.0' +__version__ = '4.9.1' __all__ = ('Client', 'Event', 'Configuration', 'RequestConfiguration', 'configuration', 'configure', 'configure_request', diff --git a/bugsnag/client.py b/bugsnag/client.py index 9717ce9a..00dba65d 100644 --- a/bugsnag/client.py +++ b/bugsnag/client.py @@ -1,4 +1,5 @@ import builtins +import logging import sys import threading import warnings @@ -234,9 +235,12 @@ def should_deliver(self, event: Event) -> bool: def log_handler( self, - extra_fields: Optional[List[str]] = None + extra_fields: Optional[Dict[str, List[str]]] = None, + level: int = logging.NOTSET ) -> BugsnagHandler: - return BugsnagHandler(client=self, extra_fields=extra_fields) + return BugsnagHandler( + client=self, extra_fields=extra_fields, level=level + ) @property def feature_flags(self) -> List[FeatureFlag]: diff --git a/bugsnag/handlers.py b/bugsnag/handlers.py index ded43a19..f741dfab 100644 --- a/bugsnag/handlers.py +++ b/bugsnag/handlers.py @@ -9,11 +9,11 @@ class BugsnagHandler(logging.Handler, object): - def __init__(self, client=None, extra_fields=None): + def __init__(self, client=None, extra_fields=None, level=logging.NOTSET): """ Creates a new handler which sends records to Bugsnag """ - super(BugsnagHandler, self).__init__() + super(BugsnagHandler, self).__init__(level=level) self.client = client self.custom_metadata_fields = extra_fields self.callbacks = [self.extract_default_metadata, diff --git a/bugsnag/notifier.py b/bugsnag/notifier.py index 426ab721..709088ad 100644 --- a/bugsnag/notifier.py +++ b/bugsnag/notifier.py @@ -1,5 +1,5 @@ _NOTIFIER_INFORMATION = { 'name': 'Python Bugsnag Notifier', 'url': 'https://github.com/bugsnag/bugsnag-python', - 'version': '4.9.0' + 'version': '4.9.1' } diff --git a/setup.py b/setup.py index 68d2c745..be3e9477 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='bugsnag', - version='4.9.0', + version='4.9.1', description='Automatic error monitoring for django, flask, etc.', long_description=__doc__, author='Simon Maynard', diff --git a/tests/test_handlers.py b/tests/test_handlers.py index ed385648..1460cd5a 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -689,3 +689,83 @@ def test_log_filter_leaves_breadcrumbs_when_handler_has_no_level(self): assert breadcrumb.message == 'Everything is not fine' assert breadcrumb.type == BreadcrumbType.LOG assert breadcrumb.metadata == {'logLevel': 'ERROR'} + + def test_handler_level_via_constructor(self): + """Test setting level via constructor parameter""" + handler = BugsnagHandler(level=logging.ERROR) + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + + # Should NOT report (below handler level) + logger.warning('This is just a warning') + self.assertSentReportCount(0) + + # Should report (at handler level) + logger.error('This is an error') + logger.removeHandler(handler) + self.assertSentReportCount(1) + + json_body = self.server.events_received[0]['json_body'] + event = json_body['events'][0] + exception = event['exceptions'][0] + self.assertEqual('LogERROR', exception['errorClass']) + + def test_handler_level_default_notset(self): + """Test default level is NOTSET""" + handler = BugsnagHandler() + self.assertEqual(handler.level, logging.NOTSET) + + def test_handler_level_with_client_and_extra_fields(self): + """Test level parameter works with other parameters""" + client = Client( + api_key='abcdef', + endpoint=self.server.events_url, + session_endpoint=self.server.sessions_url, + asynchronous=False + ) + handler = BugsnagHandler( + client=client, + extra_fields={'fruit': ['grapes']}, + level=logging.WARNING + ) + self.assertEqual(handler.level, logging.WARNING) + self.assertEqual(handler.client, client) + self.assertEqual(handler.custom_metadata_fields, {'fruit': ['grapes']}) + + def test_client_log_handler_with_level(self): + """Test client.log_handler() with level parameter""" + client = Client( + api_key='abcdef', + endpoint=self.server.events_url, + session_endpoint=self.server.sessions_url, + asynchronous=False + ) + + with scoped_logger() as logger: + handler = client.log_handler(level=logging.ERROR) + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + + logger.warning('Warning message') + self.assertSentReportCount(0) + + logger.error('Error message') + self.assertSentReportCount(1) + + json_body = self.server.events_received[0]['json_body'] + event = json_body['events'][0] + exception = event['exceptions'][0] + self.assertEqual('LogERROR', exception['errorClass']) + + def test_backward_compatibility_no_level(self): + """Ensure backward compatibility when level not specified""" + # All these should work without breaking + h1 = BugsnagHandler() + h2 = BugsnagHandler(client=None) + h3 = BugsnagHandler(extra_fields={'test': ['field']}) + h4 = BugsnagHandler(client=None, extra_fields={'test': ['field']}) + + # All should have default NOTSET level + for handler in [h1, h2, h3, h4]: + self.assertEqual(handler.level, logging.NOTSET)