Repository: rollbar/pyrollbar Branch: master Commit: c01b721d24c1 Files: 104 Total size: 465.5 KB Directory structure: gitextract_s7qvive8/ ├── .flake8 ├── .github/ │ ├── pull_request_template.md │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── THANKS.md ├── pyproject.toml └── rollbar/ ├── __init__.py ├── cli.py ├── contrib/ │ ├── __init__.py │ ├── asgi/ │ │ ├── __init__.py │ │ ├── integration.py │ │ ├── middleware.py │ │ └── types.py │ ├── bottle/ │ │ └── __init__.py │ ├── django/ │ │ ├── __init__.py │ │ ├── context_processors.py │ │ ├── middleware.py │ │ ├── models.py │ │ ├── tests.py │ │ └── utils.py │ ├── django_rest_framework/ │ │ └── __init__.py │ ├── fastapi/ │ │ ├── __init__.py │ │ ├── logger.py │ │ ├── middleware.py │ │ ├── routing.py │ │ └── utils.py │ ├── flask/ │ │ └── __init__.py │ ├── pyramid/ │ │ └── __init__.py │ ├── quart/ │ │ └── __init__.py │ ├── rq/ │ │ └── __init__.py │ └── starlette/ │ ├── __init__.py │ ├── logger.py │ ├── middleware.py │ └── requests.py ├── examples/ │ ├── asgi/ │ │ └── app.py │ ├── django/ │ │ └── app.py │ ├── fastapi/ │ │ ├── app.py │ │ ├── app_global_request.py │ │ ├── app_logger.py │ │ └── app_middleware.py │ ├── flask/ │ │ └── app.py │ ├── starlette/ │ │ ├── app.py │ │ ├── app_global_request.py │ │ └── app_logger.py │ └── twisted/ │ └── simpleserv.py ├── lib/ │ ├── __init__.py │ ├── _async.py │ ├── events.py │ ├── filters/ │ │ ├── __init__.py │ │ └── basic.py │ ├── payload.py │ ├── session.py │ ├── thread_pool.py │ ├── transform.py │ ├── transforms/ │ │ ├── __init__.py │ │ ├── batched.py │ │ ├── scrub.py │ │ ├── scrub_redact.py │ │ ├── scruburl.py │ │ ├── serializable.py │ │ └── shortener.py │ ├── transport.py │ ├── traverse.py │ └── type_info.py ├── logger.py └── test/ ├── __init__.py ├── asgi_tests/ │ ├── __init__.py │ ├── test_integration.py │ ├── test_middleware.py │ └── test_spec.py ├── async_tests/ │ ├── __init__.py │ └── test_async.py ├── fastapi_tests/ │ ├── __init__.py │ ├── test_logger.py │ ├── test_middleware.py │ ├── test_routing.py │ └── test_utils.py ├── flask_tests/ │ ├── __init__.py │ └── test_flask.py ├── starlette_tests/ │ ├── __init__.py │ ├── test_logger.py │ ├── test_middleware.py │ └── test_requests.py ├── test_basic_filters.py ├── test_batched_transform.py ├── test_custom_filters.py ├── test_lib.py ├── test_loghandler.py ├── test_pyramid.py ├── test_rollbar.py ├── test_scrub_redact_transform.py ├── test_scrub_transform.py ├── test_scruburl_transform.py ├── test_serializable_transform.py ├── test_session.py ├── test_shortener_transform.py ├── test_transform.py ├── test_traverse.py ├── twisted_tests/ │ ├── __init__.py │ └── test_twisted.py └── utils.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .flake8 ================================================ [flake8] max-line-length = 100 max-complexity = 10 exclude = test/* ================================================ FILE: .github/pull_request_template.md ================================================ ## Description of the change > Please include a summary of the change and which issues are fixed. > Please also include relevant motivation and context. ## Type of change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Maintenance - [ ] New release ## Related issues > Shortcut stories and GitHub issues (delete irrelevant) - Fix [SC-] - Fix #1 ## Checklists ### Development - [ ] Lint rules pass locally - [ ] The code changed/added as part of this pull request has been covered with tests - [ ] All tests related to the changed code pass in development ### Code review - [ ] This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached - [ ] "Ready for review" label attached to the PR and reviewers assigned - [ ] Issue from task tracker has a link to this pull request - [ ] Changes have been reviewed by at least one other engineer ================================================ FILE: .github/workflows/ci.yml ================================================ name: Pyrollbar CI on: push: branches: [ master ] tags: [ v* ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.9, '3.10', 3.11, 3.12, 3.13, 3.14] framework: - NONE - FLASK_VERSION=2.3.3 - FLASK_VERSION=3.1.2 - DJANGO_VERSION=4.2.25 - DJANGO_VERSION=5.2.7 - PYRAMID_VERSION=1.10.8 - PYRAMID_VERSION=2.0.2 - FASTAPI_VERSION=0.115.1 httpx==0.27.2 python-multipart==0.0.12 - FASTAPI_VERSION=0.118.3 httpx==0.28.1 python-multipart==0.0.20 exclude: # Test frameworks on the python versions they support, according to pypi registry # Django - framework: DJANGO_VERSION=5.2.7 python-version: 3.9 steps: - uses: actions/checkout@v2 with: submodules: recursive - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install package with test dependencies run: pip install --group test . - name: Set the framework if: ${{ matrix.framework != 'NONE' }} run: echo ${{ matrix.framework }} >> $GITHUB_ENV - name: Install Flask if: ${{ contains(matrix.framework, 'FLASK_VERSION') }} run: pip install Flask==$FLASK_VERSION - name: Install Twisted if: ${{ contains(matrix.framework, 'TWISTED_VERSION') }} run: pip install Twisted==$TWISTED_VERSION idna==2.10 - name: Install Django if: ${{ contains(matrix.framework, 'DJANGO_VERSION') }} run: pip install Django==$DJANGO_VERSION - name: Install Pyramid if: ${{ contains(matrix.framework, 'PYRAMID_VERSION') }} run: pip install pyramid==$PYRAMID_VERSION - name: Install Starlette if: ${{ contains(matrix.framework, 'STARLETTE_VERSION') }} run: pip install starlette==$STARLETTE_VERSION - name: Install FastAPI if: ${{ contains(matrix.framework, 'FASTAPI_VERSION') }} run: pip install fastapi==$FASTAPI_VERSION - name: Run tests run: pytest ================================================ FILE: .gitignore ================================================ *.pyc *.swp *.swo dist/ build/ rollbar.egg-info/ *.egg .eggs/ .idea/ *~ Pipfile Pipfile.lock .pytest_cache/ .python-version .tox/ /venv* ================================================ FILE: CHANGELOG.md ================================================ # Change Log The change log is also available on the [GitHub Releases Page](https://github.com/rollbar/pyrollbar/releases). **1.4.0-beta** - Added support for Python 3.14 by @danielmorell in [#484](https://github.com/rollbar/pyrollbar/pull/484) - Added support for error correlation headers. by @danielmorell in [#491](https://github.com/rollbar/pyrollbar/pull/491) **1.3.0** - Added support for classes to define the method `__rollbar_repr__` to control how objects are serialized by @danielmorell in [#479](https://github.com/rollbar/pyrollbar/pull/479) - Added support for Python 3.13 by @danielmorell in [#477](https://github.com/rollbar/pyrollbar/pull/477) - Removed support for Python 3.6 by @danielmorell in [#480](https://github.com/rollbar/pyrollbar/pull/480) - Updated framework versions tested against by @danielmorell in [#480](https://github.com/rollbar/pyrollbar/pull/480) **1.2.0** - Added support for custom payload transforms by @danielmorell in [#470](https://github.com/rollbar/pyrollbar/pull/470) - Added custom data to the message body by @waltjones in [#473](https://github.com/rollbar/pyrollbar/pull/473) - Added support for host override setting by @danielmorell in [#468](https://github.com/rollbar/pyrollbar/pull/468) - Fixed `RollbarHandler` reconfigures root logger by @ilkecan in [#463](https://github.com/rollbar/pyrollbar/pull/463) - Fixed `include_request_body` setting not checked for various frameworks by @danielmorell in [#469](https://github.com/rollbar/pyrollbar/pull/469) - Fixed `namedtuple` fields not being scrubbed by @danielmorell in [#474](https://github.com/rollbar/pyrollbar/pull/474) **1.1.2** Fixed build missing entrypoints for pyramid and cli in [#471](https://github.com/rollbar/pyrollbar/pull/471) **1.1.1** Fixed #465 httpx 0.28.0 not compatible with pyrollbar by @danielmorell in [#466](https://github.com/rollbar/pyrollbar/pull/466) **1.1.0** - Updated supported/tested frameworks and modernized tests and packaging by @danielmorell in [#455](https://github.com/rollbar/pyrollbar/pull/455) - Fixed #398 FastAPI integration fails if docs are disabled by @danielmorell in [#459](https://github.com/rollbar/pyrollbar/pull/459) - Support `pathlib.Path()` objects by @singingwolfboy in [$450](https://github.com/rollbar/pyrollbar/pull/450) - Added support for Python 3.12 by @danielmorell in [#460](https://github.com/rollbar/pyrollbar/pull/460) - Changed the `ShortenerTransform` to use breadth first traversal by @danielmorell in [#461](https://github.com/rollbar/pyrollbar/pull/461) - Fixed shortener multi level shortening by @danielmorell and @pawelsz-rb. See [#449](https://github.com/rollbar/pyrollbar/pull/449) **1.1.0-beta** - Updated supported/tested frameworks and modernized tests and packaging by @danielmorell in [#455](https://github.com/rollbar/pyrollbar/pull/455) - Fixed #398 FastAPI integration fails if docs are disabled by @danielmorell in [#459](https://github.com/rollbar/pyrollbar/pull/459) - Support `pathlib.Path()` objects by @singingwolfboy in [$450](https://github.com/rollbar/pyrollbar/pull/450) - Added support for Python 3.12 by @danielmorell in [#460](https://github.com/rollbar/pyrollbar/pull/460) - Changed the `ShortenerTransform` to use breadth first traversal by @danielmorell in [#461](https://github.com/rollbar/pyrollbar/pull/461) **1.1.0-alpha** - Fixed shortener multi level shortening by @danielmorell and @pawelsz-rb. See [#449](https://github.com/rollbar/pyrollbar/pull/449) **1.0.0** - Fixed handling `sensitive_post_parameters` decorator in Django by @pawelsz-rb. See [#413](https://github.com/rollbar/pyrollbar/pull/413) - Fixed Werkzeug DeprecationWarning of `BaseRequest` by @compyman. See [#410](https://github.com/rollbar/pyrollbar/pull/410) - Fixed missing locals shortening on items with "trace_chain" instead of "trace" by @terencehonles. See [#365](https://github.com/rollbar/pyrollbar/pull/365) - Fixed FastAPI version comparison by @ayharano. See [#433](https://github.com/rollbar/pyrollbar/pull/433) - Fixed #436 `WSGIRequest` has no attribute `sensitive_post_parameters`. by @danielmorell. See [#437](https://github.com/rollbar/pyrollbar/pull/437) - Added new `thread_pool` handler by @danielmorell. See [#416](https://github.com/rollbar/pyrollbar/pull/416) - Added Rollbar branding to the readme by @paulserraino. See [#418](https://github.com/rollbar/pyrollbar/pull/418) - Added batched transform to increase sanitization and serialization performance by @ijsnow. See [#421](https://github.com/rollbar/pyrollbar/pull/421) - Replaced unittest2 with unittest by @brianr. See [#420](https://github.com/rollbar/pyrollbar/pull/420) - Removed unittest2 by @mcepl. See [#419](https://github.com/rollbar/pyrollbar/pull/419) - Remove deprecated flask `before_first_request` by @albertyw. See [#428](https://github.com/rollbar/pyrollbar/pull/428) - Removed support for Python 2 by @danielmorell. See [#435](https://github.com/rollbar/pyrollbar/pull/435) - Updated the base Ubuntu for CI to 20.04 test runner by @danielmorell and @waltjones. See [#427](https://github.com/rollbar/pyrollbar/pull/427) - Replaced `httpx.post` `data` kwarg with `content` by @ayharano. See [#425](https://github.com/rollbar/pyrollbar/pull/425) **0.16.3** - Pinned Python 2 CI tests to legacy version of dependencies. See [#408](https://github.com/rollbar/pyrollbar/pull/408) - Add PyPI badge for supported Python versions. See [#408](https://github.com/rollbar/pyrollbar/pull/401) - Add Django 4.0+ compatibility. See [#408](https://github.com/rollbar/pyrollbar/pull/400) - Update PR template. See [#408](https://github.com/rollbar/pyrollbar/pull/395) - SC-95272: Remove support for Python 3.3. See [#408](https://github.com/rollbar/pyrollbar/pull/394) **0.16.2** - Fix building person data in Django. See [#385](https://github.com/rollbar/pyrollbar/pull/385) - Fix circular error logging for non-HTTP events in Starlette. See [#390](https://github.com/rollbar/pyrollbar/pull/390) - Fix Python 3.4 builds. See [#389](https://github.com/rollbar/pyrollbar/pull/389) **0.16.1** - Fix PyPI artifacts **0.16.0** - Add support for FastAPI framework. See [#373](https://github.com/rollbar/pyrollbar/pull/373) - Add support for Starlette framework. See [#373](https://github.com/rollbar/pyrollbar/pull/373) - Add support for ASGI-based frameworks. See [#373](https://github.com/rollbar/pyrollbar/pull/373) - Add support for HTTPX async handler. See [#373](https://github.com/rollbar/pyrollbar/pull/373) - Add support for async report_exc_info and report_message. See [#373](https://github.com/rollbar/pyrollbar/pull/373) - Collect user IP from X-Forwarded-For, fall back to X-Real-Ip. See [#370](https://github.com/rollbar/pyrollbar/pull/370) - Improve examples. See [#368](https://github.com/rollbar/pyrollbar/pull/368) - Fix Python 3.3 builds. See [#374](https://github.com/rollbar/pyrollbar/pull/374) - Fix Flask 0.x builds. See [#376](https://github.com/rollbar/pyrollbar/pull/376) **0.15.2** - Add support for whitelist/blacklist for safelist/blocklist. See [#354](https://github.com/rollbar/pyrollbar/pull/343) - Add Twisted to the available frameworks. See [#360](https://github.com/rollbar/pyrollbar/pull/360) **0.15.1** - Add support to Python 3.8. See [#351](https://github.com/rollbar/pyrollbar/pull/351) - Fix deque test. See [#349](https://github.com/rollbar/pyrollbar/pull/349) - Add alternatives to exception handler. See [#335](https://github.com/rollbar/pyrollbar/pull/335) - Change dict_merge to allow strict mode. See [#339](https://github.com/rollbar/pyrollbar/pull/339) - Improve scrubbing test case. See [#343](https://github.com/rollbar/pyrollbar/pull/343) **0.15.0** - Prevent recursive re-raising of exceptions. See [#317](https://github.com/rollbar/pyrollbar/pull/317) - Correctly apply logger formatting to Rollbar messages. See [#312](https://github.com/rollbar/pyrollbar/pull/312) - Fix deprecation warnings. See [#325](https://github.com/rollbar/pyrollbar/pull/319) and [#331](https://github.com/rollbar/pyrollbar/pull/331) - Allow the request pool to be configured. See [#305](https://github.com/rollbar/pyrollbar/pull/305) - Use callable() instead of try/except TypeError. See [#319](https://github.com/rollbar/pyrollbar/pull/319) - Update Travis CI matrix. See [#317](https://github.com/rollbar/pyrollbar/pull/317) - Fix Travis build errors. See [#328](https://github.com/rollbar/pyrollbar/pull/328) - Update trove classifiers. See [#331](https://github.com/rollbar/pyrollbar/pull/331) **0.14.7** - Allow the raw request body to be included if desired. See [#304](https://github.com/rollbar/pyrollbar/pull/304) - Send Rollbar access token in HTTP header. See [#303](https://github.com/rollbar/pyrollbar/pull/303) - Add support for Django 1.7 & 1.8 in \_build_django_request_data. See [#301](https://github.com/rollbar/pyrollbar/pull/301) - Add support for Quart framework. See [#300](https://github.com/rollbar/pyrollbar/pull/300) **0.14.6** - Add the authorization header to the default scrub fields list. See [#299](https://github.com/rollbar/pyrollbar/pull/299) - Encode the payload properly for newer versions of Twisted. See [#298](https://github.com/rollbar/pyrollbar/pull/298) - Don't fail to send payloads because some inner object is not JSON serializable. See [#297](https://github.com/rollbar/pyrollbar/pull/297) - Allow floats as circular references. See [#291](https://github.com/rollbar/pyrollbar/pull/291) **0.14.5** - Fix bug in which error params were not being passed correctly to Pyramid middleware. See [#287](https://github.com/rollbar/pyrollbar/pull/287) **0.14.4** - Fix bug in Pyramid middleware where exc_info was not being passed to handle_error. See [#283](https://github.com/rollbar/pyrollbar/pull/283) - Fix bug where errors in the serialization of local variables caused errors to be dropped. They will now make it to Rollbar. See [#284](https://github.com/rollbar/pyrollbar/pull/284) **0.14.3** - Add support for HTTP(S) proxies. See [#276](https://github.com/rollbar/pyrollbar/pull/276) **0.14.2** - Fix bug with file-based logging config in Python 3. See [#277](https://github.com/rollbar/pyrollbar/issues/277) - Fix bug in Django middleware when request has no META attribute. See [#273](https://github.com/rollbar/pyrollbar/pull/273) **0.14.1** - Add Django middlewares that differentiate between 404 and other exceptions. See [#270](https://github.com/rollbar/pyrollbar/pull/270) - Make Werkzeug request handling more general. See [#271](https://github.com/rollbar/pyrollbar/pull/271) - Fix incorrect handling rollbar.init() arguments when using LOGGER in Django. See [#235](https://github.com/rollbar/pyrollbar/pull/235) - Use thread local storage for not thead-safe requests.Sessions. See [#269](https://github.com/rollbar/pyrollbar/pull/269) - Swallow known error that happens in add_person_data. See [#268](https://github.com/rollbar/pyrollbar/pull/268) - Only write a log about a rate limit once. See [#267](https://github.com/rollbar/pyrollbar/pull/267) - Adapt Django view monkey patch to support Django 2. See [#257](https://github.com/rollbar/pyrollbar/pull/257) - Add support request objects from Django REST framework. See [#18](https://github.com/rollbar/pyrollbar/pull/18) - Add support for Falcon framework requests. See [#51](https://github.com/rollbar/pyrollbar/pull/51) - Add support for Django Channels' AsgiRequest. See [#272](https://github.com/rollbar/pyrollbar/pull/272) **0.14.0** - Create the configuration options, `capture_username` and `capture_email`. Prior to this release, if we gather person data automatically, we would try to capture the id, email, and username. Starting with this release by default we will only capture the id. If you set `capture_username` to `True` then we will also attempt to capture the username. Similarly for `capture_email` with the email. (See [#262](https://github.com/rollbar/pyrollbar/pull/262)) - Create the configuration option `capture_ip`. This can take one of three values: `True`, `'anonymize'`, or `False`. This controls how we handle IP addresses that are captured from requests. If `True`, then we will send the full IP address. This is the current behaviour and the default. If set to the string `'anonymize'` which is also available as the constant `ANONYMIZE` on the `rollbar` module, we will mask out the least significant bits of the IP address. If set to `False`, then we will not capture the IP address. (See [#262](https://github.com/rollbar/pyrollbar/pull/262)) - Fix `request.files_keys` for Flask [#263](https://github.com/rollbar/pyrollbar/pull/263) - If you call `init` multiple times we will update the settings at each call. Prior to this release we emitted a warning and did not update settings. [#259](https://github.com/rollbar/pyrollbar/pull/259) - Better Tornado support [#256](https://github.com/rollbar/pyrollbar/pull/256) **0.13.18** - See Release Notes **0.13.17** - Fix deprecation warning related to Logging.warn - Fix bug where non-copyable objects could cause an exception if they end up trying to get passed to one of the logging methods. - Fix bug where both `trace` and `trace_chain` could appear in the final payload, which is not allowed by the API. **0.13.16** - Fix PyPI documentation **0.13.15** - Fix shortener issue for Python 3 **0.13.14** - Fix bug that caused some payload objects to be turned into the wrong type when shortening is applied. This would lead to API rejections. See [#200](https://github.com/rollbar/pyrollbar/pull/200) - Add `suppress_reinit_warning` option if you want to allow calling init twice. See [#198](https://github.com/rollbar/pyrollbar/pull/198) - Pass through keyword arguments from the logging handler to the underling Rollbar init call. See [#203](https://github.com/rollbar/pyrollbar/pull/203) **0.13.13** - Add support for AWS Lambda. See [#191](https://github.com/rollbar/pyrollbar/pull/191) **0.13.12** - Remove the Django request body from the payload as it can contain sensitive data. See [#174](https://github.com/rollbar/pyrollbar/pull/174) - Allow users to shorten arbitrary parts of the payload. See [#173](https://github.com/rollbar/pyrollbar/pull/173) - Fix a Django deprecation warning. See [#165](https://github.com/rollbar/pyrollbar/pull/165) **0.13.11** - Handle environments where `sys.argv` does not exist. See [#131](https://github.com/rollbar/pyrollbar/pull/131) **0.13.10** - Gather request method from WebOb requests. See [#152](https://github.com/rollbar/pyrollbar/pull/152) **0.13.9** - Change `_check_config()` to deal with agent handler. See [#147](https://github.com/rollbar/pyrollbar/pull/147) - Fix settings values not being booleans in Pyramid. See [#150](https://github.com/rollbar/pyrollbar/pull/150) **0.13.8** - Fix regression from 0.13.7. See [#141](https://github.com/rollbar/pyrollbar/pull/141) **0.13.7** - Update Django middleware to support Django 1.10+. See [#138](https://github.com/rollbar/pyrollbar/pull/138) **0.13.6** - Fixed a referenced before assignment in the failsafe. See [#136](https://github.com/rollbar/pyrollbar/pull/136) **0.13.5** - Fixed record message formatting issues breaking the log handler's history. See [#135](https://github.com/rollbar/pyrollbar/pull/135) **0.13.4** - Fixed failsafe handling for payloads that are too large. See [#133](https://github.com/rollbar/pyrollbar/pull/133) **0.13.3** - Improved handling of Enums. See [#121](https://github.com/rollbar/pyrollbar/pull/121) **0.13.2** - Improved handling of Nan and (Negative)Infinity. See [#117](https://github.com/rollbar/pyrollbar/pull/117) - RollbarHandler now ignores log records from Rollbar. See [#118](https://github.com/rollbar/pyrollbar/pull/118) **0.13.1** - Failsafe handling for payloads that are too large. See [#116](https://github.com/rollbar/pyrollbar/pull/116) - Failsafe Behavior - Log an error containing the original payload and the UUID from it - Send a new payload to Rollbar with the custom attribute containing the UUID and host from the original payload **0.13.0** - Frame payload refactor and varargs scrubbing. See [#113](https://github.com/rollbar/pyrollbar/pull/113) - Frame Payload Changes - remove args and kwargs - add argspec as the list of argument names to the function call - add varargspec as the name of the list containing the arbitrary unnamed positional arguments to the function call if any exist - add keywordspec as the name of the object containing the arbitrary keyword arguments to the function call if any exist - Other Changes: - Arguments with default values are no longer removed from args and placed into kwargs - varargs are now scrubbable and scrubbed by default - Switched to using a Session object to perform HTTPS requests to optimize for keepalive connections. See [#114](https://github.com/rollbar/pyrollbar/pull/114) **0.12.1** - Keep blank values from request query strings when scrubbing URLs. See [#110](https://github.com/rollbar/pyrollbar/pull/110) **0.12.0** - Fix and update Twisted support. See [#109](https://github.com/rollbar/pyrollbar/pull/109) - **Breaking Changes**: [treq](https://github.com/twisted/treq) is now required for using Twisted with pyrollbar. **0.11.6** - Improve object handling for SQLAlchemy. See [#108](https://github.com/rollbar/pyrollbar/pull/108) **0.11.5** - Fixed a bug when custom `__repr__()` calls resulted in an exception being thrown. See [#102](https://github.com/rollbar/pyrollbar/pull/102) **0.11.4** - Revert changes from 0.11.3 since they ended-up having the unintended side effect by that exceptions messages weren't processing as expected. - Update settings in init first so that custom scrub_fields entries are handled correctly **0.11.3** - Obey safe repr for exceptions. See [#91](https://github.com/rollbar/pyrollbar/pull/91) **0.11.2** - Fixed a bug when calling logging.exception() when not in an exception handler. Now it correctly determines it doesn't have any exception info and uses report_message() instead of report_exc_info(). **0.11.1** - Added a new configuration option to expose the serializer's `safelisted_types` param - Allows users to safelist types to be serialized using `repr(obj)` instead of `str(type(obj))` - Fixed a bug that was not taking the `safe_repr` option into account. See [#87](https://github.com/rollbar/pyrollbar/pull/87) **0.11.0** - Overhauled the scrubbing and serialization mechanisms to provide deep object scrubbing and better handling of UTF-8 data from local variables. See [#75](https://github.com/rollbar/pyrollbar/pull/75) - This fixes a bunch of problems with reporting local variables, including `UnicodeEncodeError`s and attempting to read variables after the thread they were in has died. - Local variables and payload data is now sent over in their original structure. - If a variable was a `dict`, it will be transmitted as a `dict` instead of turned into a string representation of the variable. - The entire payload is now scrubbed and URL password fields are scrubbed as well. - Added a Django example. - Wrote many, many more tests :) - Integrated the `six` library to provide cleaner support for Python3. - Added some additional scrub fields. **0.10.1** - Added a warning message if `init()` is called more than once. **0.10.0** - Added support for Twisted framework. See [#69](https://github.com/rollbar/pyrollbar/pull/69) - Fix a bug that was causing max recursion errors while collecting local variables. See [#77](https://github.com/rollbar/pyrollbar/pull/77) - Added a configuration option, `safe_repr: True` which will cause payload serialization to use the type name for non-built-in objects. This option defaults to `True` which may cause data reported to Rollbar to contain less information for custom types. Prior to this change, serialization of custom objects called `__repr__()` which may have had undesired side effects. - Fixed a bug that did not correctly handle anonymous tuple arguments while gathering local variables. **0.9.14** - Fix logging loop when using Flask in a non-request context, and also using the Rollbar logging handler. See [#68](https://github.com/rollbar/pyrollbar/pull/68) **0.9.13** - If present, get request from log record. Otherwise try to guess current request as usual. **0.9.12** - Fix a bug that was causing a crash while reporting an error that happened in a Werkzeug request that had no `request.json`. See [#64](https://github.com/rollbar/pyrollbar/pull/64) **0.9.11** - Implement workarounds for NaN and Infinity "numbers" in payloads. See [#62](https://github.com/rollbar/pyrollbar/pull/62) **0.9.10** - Fix request data collection in Flask 0.9. See [#61](https://github.com/rollbar/pyrollbar/pull/61) **0.9.9** - Add exception handler for RQ (requires some instrumentation). See [#57](https://github.com/rollbar/pyrollbar/pull/57) - Scrub fields inside `extra_data` - Gather the process PID and report it along with the other 'server' data **0.9.8** - Support bare WSGI requests ([#55](https://github.com/rollbar/pyrollbar/pull/55)) **0.9.7** - Add support for Google App Engine ([#53](https://github.com/rollbar/pyrollbar/pull/53)) **0.9.6** - Fix memory leak when using the RollbarHandler logging handler (see [#43](https://github.com/rollbar/pyrollbar/pull/43)) - Fix bug where named function arguments were not scrubbed correctly **0.9.5** - Fix bug with local variable gathering that was breaking when getting the arguments for a class constructor. **0.9.4** - Request headers are now scrubbed, [pr#41](https://github.com/rollbar/pyrollbar/pull/41). **0.9.3** - `exception_level_filters` can now take a string that defines the class to filter, [#38](https://github.com/rollbar/pyrollbar/pull/38). **0.9.2** - Added an option to disable SSL certificate verification, [#36](https://github.com/rollbar/pyrollbar/pull/36). - Added `__version__` specifier to `__init__.py`. **0.9.1** New features: - For Tornado requests, gather the request start time. See [#33](https://github.com/rollbar/pyrollbar/pull/33) - Add handler which uses Tornado's `AsyncHTTPClient`. To use this, set your 'handler' to 'tornado'. See [#34](https://github.com/rollbar/pyrollbar/pull/34) **0.9.0** - Improvements to RollbarHandler logging handler. It now: - extracts more information out of each record (i.e. metadata like pathname and creation time) - uses the format string, with arguments not yet replaced, as the main message body. This will result in much better grouping in Rollbar. Note about upgrading from 0.8.x: unless you are using RollbarHandler, there are no breaking changes. If you are using RolbarHandler, then this will change the way your data appears in Rollbar (to the better, in our opinion). **0.8.3** - Provide a way to blocklist types from being repr()'d while gathering local variables. **0.8.2** - Fix uncaught ImproperlyConfigured exception when importing Rollbar in a Django REST Framework environment without a settings module loaded ([#28](https://github.com/rollbar/pyrollbar/pull/28)) **0.8.1** - Only attempt local variable extraction if traceback frames are of the correct type, print a warning otherwise - Fix JSON request param extraction for Werkzeug requests (Pyramid, Flask, etc) **0.8.0** - Local variables collection now enabled by default. - Fixed scrubbing for utf8 param names. **0.7.6** - Added local variables for all in-project frames and the last frame. **0.7.5** - Initial support for sending args and kwargs for traceback frames. - Optimization to send the access token in a header. **0.7.4** - Level kwarg added to `rollbar.report_exc_info()` ([#22](https://github.com/rollbar/pyrollbar/pull/22)) **0.7.3** - Added in an optional `endpoint` parameter to `search_items()`. **0.7.2** - Fix for scrubbing werkzeug json bodies ([#20](https://github.com/rollbar/pyrollbar/pull/20)) **0.7.1** - Support scrubbing for werkzeug json bodies ([#19](https://github.com/rollbar/pyrollbar/pull/19)) **0.7.0** - Python 3 support - Now support extracting data from Django REST framework requests - New `enabled` configuration setting **0.6.2** - Fixed json request data formatting for reports in Bottle requests - Now send json request data for Django and Pyramid apps - Set framework and request context properly for all reports in Flask and Bottle apps **0.6.1** - Added Django, Pyramid, Flask and Bottle support for default contexts. **0.6.0** - `report_message()` now returns the UUID of the reported occurrence. **0.5.14** - Fix bug with non-JSON post data in Flask - Add slightly better integration with Flask. See [rollbar-flask-example](https://github.com/rollbar/rollbar-flask-example) for example usage. **0.5.13** - Collect JSON post data in Flask when mimetype is `application/json` **0.5.12** - Add sys.argv to server data **0.5.11** - Don't report bottle.BaseResponse exceptions in the bottle plugin **0.5.10** - Added `code_version` configuration setting - Added support for bottle request objects **0.5.9** - Added a command line interface for reporting messages to Rollbar **0.5.8** - Added `allow_logging_basic_config` config flag for compatability with Flask. If using Flask, set to False. **0.5.7** - Added `exception_level_filters` configuration setting to customize the level that specific exceptions are reported as. **0.5.6** - First argument to `rollbar.report_exc_info()` is now optional. You can now call it with no arguments from within an `except` block, and it will behave is if you had called like `rollbar.report_exc_info(sys.exc_info())` **0.5.5** - Support for ignoring exceptions by setting `exc._rollbar_ignore = True`. Such exceptions reported through rollbar.report_exc_info() -- which is used under the hood in the Django and Pyramid middlewares -- will be ignored instead of reported. **0.5.4** - Django: catch exceptions when patching the debugview, for better support for django 1.3. **0.5.3** - Fixed bug when reporting messages without a request object **0.5.2** - Fixed bug where django debug page can get patched twice **0.5.1** - Catching possible malformed API responses **0.5.0** - Rename to rollbar **0.4.1** - report_exc_info() now takes two additional named args: `extra_data` and `payload_data`, like report_message(). - on 429 response (over rate limit), log a warning but don't parse and print an exception. **0.3.2** - Added new default scrub fields **0.3.1** - Fixed pypi package **0.3.0** - Merge django-ratchet and pyramid_ratchet into pyratchet - Add ability to write to a ratchet-agent log file **0.2.0** - Add "person" support **0.1.14** - Added payload_data arg to report_message() **0.1.13** - Added extra_data arg to report_message() **0.1.12** - Use custom JSON encoder to skip objects that can't be encoded. - Bump default timeout from 1 to 3 seconds. **0.1.11** - Sensitive params now scrubbed out of POST. Param name list is customizable via the `scrub_fields` config option. **0.1.10** - Add support for Tornado request objects (`tornado.httpserver.HTTPRequest`) **0.1.9** - Fix support for Pyramid request objects **0.1.8** - Add support for Django request objects ================================================ FILE: LICENSE ================================================ Copyright (c) 2014 Rollbar, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

rollbar-logo

Pyrollbar

Proactively discover, predict, and resolve errors in real-time with Rollbar’s error monitoring platform. Start tracking errors today!

[![Build Status](https://github.com/rollbar/pyrollbar/workflows/Pyrollbar%20CI/badge.svg?tag=latest)](https://github.com/rollbar/pyrollbar/actions) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/rollbar) Python notifier for reporting exceptions, errors, and log messages to [Rollbar](https://rollbar.com). ## Key benefits of using Pyrollbar are: - **Frameworks:** Pyrollbar supports popular Python frameworks such as Django, Flask, FastAPI, AWS Lambda and more! - **Automatic error grouping:** Rollbar aggregates Occurrences caused by the same error into Items that represent application issues. Learn more about reducing log noise. - **Advanced search:** Filter items by many different properties. Learn more about search. - **Customizable notifications:** Rollbar supports several messaging and incident management tools where your team can get notified about errors and important events by real-time alerts. Learn more about Rollbar notifications. ## Python Versions Supported | PyRollbar Version | Python Version Compatibility | Support Level | |-------------------|-----------------------------------------------|---------------------| | 1.4.0 | 3.9, 3.10, 3.11, 3.12, 3.13, 3.14 | Full | | 0.16.3 | 2.7, 3.4, 3.5, 3.6, 3.7. 3.8, 3.9, 3.10, 3.11 | Security Fixes Only | #### Support Level Definitions **Full** - We will support new features of the library and test against all supported versions. **Security Fixes Only** - We will only provide critical security fixes for the library. ## Frameworks Supported Generally, PyRollbar can be used with any Python framework. However, we have official support for the following frameworks: | Framework | Support Duration | Tested Versions | |-----------|----------------------------|-----------------| | Celery | Release +1 year | None | | Django | Release or LTS end +1 year | 4.2, 5.2 | | FastAPI | Release +1 year | 0.115, 0.118 | | Flask | Release +1 year | 2.3, 3.1 | | Pyramid | Release +1 year | 1.10, 2.0 | Official support means that we ship and maintain integrations for these frameworks. It also means that we test against these frameworks as part of our CI pipeline. Generally, we will support the last year of releases for a framework. If a framework has a defined support period (including LTS releases), we will support the release for the duration of that period plus one year. ### Community Supported There are also a number of community-supported integrations available. For more information, see the [Python SDK docs](https://docs.rollbar.com/docs/python-community-supported-sdks). ## Setup Instructions 1. [Sign up for a Rollbar account](https://rollbar.com/signup) 2. Follow the [Quick Start](https://docs.rollbar.com/docs/python#section-quick-start) instructions in our [Python SDK docs](https://docs.rollbar.com/docs/python) to install pyrollbar and configure it for your platform. ## Usage and Reference For complete usage instructions and configuration reference, see our [Python SDK docs](https://docs.rollbar.com/docs/python). ## Release History & Changelog See our [Releases](https://github.com/rollbar/pyrollbar/releases) page for a list of all releases, including changes. ## Help / Support If you run into any issues, please email us at [support@rollbar.com](mailto:support@rollbar.com) For bug reports, please [open an issue on GitHub](https://github.com/rollbar/pyrollbar/issues/new). ## Contributing 1. Fork it 2. Create your feature branch (```git checkout -b my-new-feature```). 3. Commit your changes (```git commit -am 'Added some feature'```) 4. Push to the branch (```git push origin my-new-feature```) 5. Create new Pull Request Tests are in `rollbar/test`. To run the tests: `python setup.py test` ================================================ FILE: THANKS.md ================================================ # Contributors Huge thanks to the following contributors (by github username). For the most up-to-date list, see https://github.com/rollbar/pyrollbar/graphs/contributors - [alex-laties](https://github.com/alex-laties) - [dmitry-mukhin](https://github.com/dmitry-mukhin) - [homm](https://github.com/homm) - [isra17](https://github.com/isra17) - [jamesonjlee](https://github.com/jamesonjlee) - [jbowes](https://github.com/jbowes) - [jbrumwell](https://github.com/jbrumwell) - [jedipi](https://github.com/jedipi) - [juggernaut](https://github.com/juggernaut) - [rhcarvalho](https://github.com/rhcarvalho) - [tschieggm](https://github.com/tschieggm) - [zvirusz](https://github.com/zvirusz) - [benkuhn](https://github.com/benkuhn) - [pmourlanne](https://github.com/pmourlanne) ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "rollbar" dynamic = ["version"] description = "Easy and powerful exception tracking with Rollbar. Send messages and exceptions with arbitrary context, get back aggregates, and debug production issues quickly." readme = "README.md" license = {file = "LICENSE"} maintainers = [{name = "Rollbar, Inc.", email = "support@rollbar.com"}] classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Bottle", "Framework :: Django", "Framework :: Flask", "Framework :: Pylons", "Framework :: Pyramid", "Framework :: Twisted", "Intended Audience :: Developers", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development", "Topic :: Software Development :: Bug Tracking", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Quality Assurance", "Topic :: System :: Logging", "Topic :: System :: Monitoring", ] requires-python = ">=3.9" dependencies = [ "requests>=0.12.1", ] [dependency-groups] test = [ "blinker", "httpx", "pytest", "webob", ] [project.urls] Homepage = "https://rollbar.com/" Documentation = "https://docs.rollbar.com/docs/python" Changes = "https://github.com/rollbar/pyrollbar/blob/master/CHANGELOG.md" Source = "https://github.com/rollbar/pyrollbar/" [project.scripts] rollbar = "rollbar.cli:main" [project.entry-points."paste.filter_app_factory"] pyramid = "rollbar.contrib.pyramid:create_rollbar_middleware" [tool.pytest] testpaths = [ "rollbar/test", ] [tool.setuptools.dynamic] version = {attr = "rollbar.__version__"} [tool.tox] requires = ["tox>=4.22"] [tool.tox.env_run_base] dependency_groups = ["test"] description = "run unit tests" commands = [["pytest", "{posargs}"]] ================================================ FILE: rollbar/__init__.py ================================================ from __future__ import absolute_import, annotations from __future__ import unicode_literals import copy import functools import inspect import json import logging import os import socket import sys import threading import time import traceback import types import uuid import wsgiref.util import warnings import queue from urllib.parse import parse_qs, urljoin import requests from rollbar.lib import events, filters, dict_merge, transport, defaultJSONEncode from rollbar.lib.payload import Attribute from rollbar.lib.session import get_current_session, set_current_session, parse_session_request_baggage_headers __version__ = '1.4.0-beta' __log_name__ = 'rollbar' log = logging.getLogger(__log_name__) # import request objects from various frameworks, if available try: from webob import BaseRequest as WebobBaseRequest except ImportError: WebobBaseRequest = None try: from django.core.exceptions import ImproperlyConfigured except ImportError: DjangoHttpRequest = None RestFrameworkRequest = None else: try: from django.http import HttpRequest as DjangoHttpRequest except (ImportError, ImproperlyConfigured): DjangoHttpRequest = None try: from rest_framework.request import Request as RestFrameworkRequest except (ImportError, ImproperlyConfigured): RestFrameworkRequest = None del ImproperlyConfigured try: from werkzeug.wrappers import Request as WerkzeugRequest except (ImportError, SyntaxError): WerkzeugRequest = None try: from werkzeug.local import LocalProxy as WerkzeugLocalProxy except (ImportError, SyntaxError): WerkzeugLocalProxy = None try: from tornado.httpserver import HTTPRequest as TornadoRequest except ImportError: TornadoRequest = None try: from bottle import BaseRequest as BottleRequest except ImportError: BottleRequest = None try: from sanic.request import Request as SanicRequest except ImportError: SanicRequest = None try: from google.appengine.api.urlfetch import fetch as AppEngineFetch except (ImportError, KeyError): AppEngineFetch = None try: from starlette.requests import Request as StarletteRequest except ImportError: StarletteRequest = None try: from fastapi.requests import Request as FastAPIRequest except ImportError: FastAPIRequest = None try: import httpx except ImportError: httpx = None AsyncHTTPClient = httpx def passthrough_decorator(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap try: from tornado.httpclient import AsyncHTTPClient as TornadoAsyncHTTPClient except ImportError: TornadoAsyncHTTPClient = None try: import treq from twisted.python import log as twisted_log from twisted.web.iweb import IPolicyForHTTPS from twisted.web.client import BrowserLikePolicyForHTTPS, Agent from twisted.internet.ssl import CertificateOptions from twisted.internet import task, defer, ssl, reactor from zope.interface import implementer @implementer(IPolicyForHTTPS) class VerifyHTTPS(object): def __init__(self): # by default, handle requests like a browser would self.default_policy = BrowserLikePolicyForHTTPS() def creatorForNetloc(self, hostname, port): # check if the hostname is in the the whitelist, otherwise return the default policy if not SETTINGS['verify_https']: return ssl.CertificateOptions(verify=False) return self.default_policy.creatorForNetloc(hostname, port) def log_handler(event): """ Default uncaught error handler """ try: if not event.get('isError') or 'failure' not in event: return err = event['failure'] # Don't report Rollbar internal errors to ourselves if issubclass(err.type, ApiException): log.error('Rollbar internal error: %s', err.value) else: report_exc_info((err.type, err.value, err.getTracebackObject())) except: log.exception('Error while reporting to Rollbar') # Add Rollbar as a log handler which will report uncaught errors twisted_log.addObserver(log_handler) except ImportError: treq = None try: from falcon import Request as FalconRequest except ImportError: FalconRequest = None def get_request(): """ Get the current request object. Implementation varies on library support. Modified below when we know which framework is being used. """ # TODO(cory): add in a generic _get_locals_request() which # will iterate up through the call stack and look for a variable # that appears to be valid request object. for fn in (_get_fastapi_request, _get_starlette_request, _get_bottle_request, _get_flask_request, _get_pyramid_request, _get_pylons_request): try: req = fn() if req is not None: return req except: pass return None def _get_bottle_request(): if BottleRequest is None: return None from bottle import request return request def _get_flask_request(): if WerkzeugRequest is None: return None from flask import request return request def _get_pyramid_request(): if WebobBaseRequest is None: return None from pyramid.threadlocal import get_current_request return get_current_request() def _get_pylons_request(): if WebobBaseRequest is None: return None from pylons import request return request def _get_starlette_request(): # Do not modify the returned object if StarletteRequest is None: return None from rollbar.contrib.starlette import get_current_request return get_current_request() def _get_fastapi_request(): # Do not modify the returned object if FastAPIRequest is None: return None from rollbar.contrib.fastapi import get_current_request return get_current_request() BASE_DATA_HOOK = None agent_log = None VERSION = __version__ DEFAULT_ENDPOINT = 'https://api.rollbar.com/api/1/' DEFAULT_TIMEOUT = 3 ANONYMIZE = 'anonymize' DEFAULT_LOCALS_SIZES = { 'maxlevel': 5, 'maxdict': 10, 'maxlist': 10, 'maxtuple': 10, 'maxset': 10, 'maxfrozenset': 10, 'maxdeque': 10, 'maxarray': 10, 'maxstring': 100, 'maxlong': 40, 'maxother': 100, } # configuration settings # configure by calling init() or overriding directly SETTINGS = { 'access_token': None, 'enabled': True, 'environment': 'production', 'exception_level_filters': [], 'root': None, # root path to your code 'host': None, # custom hostname of the current host 'branch': None, # git branch name 'code_version': None, # 'blocking', 'thread' (default), 'async', 'agent', 'tornado', 'gae', 'twisted', 'httpx' or 'thread_pool' # 'async' requires Python 3.4 or higher. # 'httpx' requires Python 3.7 or higher. # 'thread_pool' requires Python 3.2 or higher. 'handler': 'default', 'thread_pool_workers': None, 'endpoint': DEFAULT_ENDPOINT, 'timeout': DEFAULT_TIMEOUT, 'agent.log_file': 'log.rollbar', 'scrub_fields': [ 'pw', 'passwd', 'password', 'secret', 'confirm_password', 'confirmPassword', 'password_confirmation', 'passwordConfirmation', 'access_token', 'accessToken', 'auth', 'authentication', 'authorization', ], 'url_fields': ['url', 'link', 'href'], 'notifier': { 'name': 'pyrollbar', 'version': VERSION }, 'allow_logging_basic_config': True, # set to False to avoid a call to logging.basicConfig() 'locals': { 'enabled': True, 'safe_repr': True, 'scrub_varargs': True, 'sizes': DEFAULT_LOCALS_SIZES, 'safelisted_types': [], 'whitelisted_types': [] }, 'verify_https': True, 'shortener_keys': [], 'suppress_reinit_warning': False, 'capture_email': False, 'capture_username': False, 'capture_ip': True, 'log_all_rate_limited_items': True, 'log_payload_on_error': True, 'http_proxy': None, 'http_proxy_user': None, 'http_proxy_password': None, 'include_request_body': False, 'request_pool_connections': None, 'request_pool_maxsize': None, 'request_max_retries': None, 'batch_transforms': False, 'custom_transforms': [], } _CURRENT_LAMBDA_CONTEXT = None _LAST_RESPONSE_STATUS = None # Set in init() _transforms = [] _serialize_transform = None _scrub_redact_transform = None _initialized = False from rollbar.lib.transforms.scrub_redact import REDACT_REF from rollbar.lib import transforms from rollbar.lib import type_info from rollbar.lib.transforms.scrub import ScrubTransform from rollbar.lib.transforms.scruburl import ScrubUrlTransform from rollbar.lib.transforms.scrub_redact import ScrubRedactTransform from rollbar.lib.transforms.serializable import SerializableTransform from rollbar.lib.transforms.shortener import ShortenerTransform from rollbar.lib.transforms.batched import BatchedTransform ## public api def init(access_token, environment='production', scrub_fields=None, url_fields=None, **kw): """ Saves configuration variables in this module's SETTINGS. access_token: project access token. Get this from the Rollbar UI: - click "Settings" in the top nav - click "Projects" in the left nav - copy-paste the appropriate token. environment: environment name. Can be any string; suggestions: 'production', 'development', 'staging', 'yourname' **kw: provided keyword arguments will override keys in SETTINGS. """ global SETTINGS, agent_log, _initialized, _transforms, _serialize_transform, _scrub_redact_transform, _threads if scrub_fields is not None: SETTINGS['scrub_fields'] = list(scrub_fields) if url_fields is not None: SETTINGS['url_fields'] = list(url_fields) # Merge the extra config settings into SETTINGS SETTINGS = dict_merge(SETTINGS, kw) if _initialized: # NOTE: Temp solution to not being able to re-init. # New versions of pyrollbar will support re-initialization # via the (not-yet-implemented) configure() method. if not SETTINGS.get('suppress_reinit_warning'): log.warning('Rollbar already initialized. Ignoring re-init.') return SETTINGS['access_token'] = access_token SETTINGS['environment'] = environment _configure_transport(**SETTINGS) if SETTINGS.get('allow_logging_basic_config'): logging.basicConfig() if SETTINGS.get('handler') == 'agent': agent_log = _create_agent_log() elif SETTINGS.get('handler') == 'thread_pool': from rollbar.lib.thread_pool import init_pool init_pool(SETTINGS.get('thread_pool_workers', None)) if not SETTINGS['locals']['safelisted_types'] and SETTINGS['locals']['whitelisted_types']: warnings.warn('whitelisted_types deprecated use safelisted_types instead', DeprecationWarning) SETTINGS['locals']['safelisted_types'] = SETTINGS['locals']['whitelisted_types'] # We will perform these transforms in order: # 1. Serialize the payload to be all python built-in objects # 2. Scrub the payloads based on the key suffixes in SETTINGS['scrub_fields'] # 3. Scrub URLs in the payload for keys that end with 'url' # 4. Optional - If local variable gathering is enabled, transform the # trace frame values using the ShortReprTransform. _serialize_transform = SerializableTransform(safe_repr=SETTINGS['locals']['safe_repr'], safelist_types=SETTINGS['locals']['safelisted_types']) _scrub_redact_transform = ScrubRedactTransform(suffixes=[(field,) for field in SETTINGS['scrub_fields']], redact_char='*') # A list of key prefixes to apply our shortener transform to. The request # being included in the body key is old behavior and is being retained for # backwards compatibility. shortener_keys = [ ('request', 'POST'), ('request', 'json'), ('body', 'request', 'POST'), ('body', 'request', 'json'), ] if SETTINGS['locals']['enabled']: for prefix in (('body', 'trace'), ('body', 'trace_chain', '*')): shortener_keys.append(prefix + ('frames', '*', 'code')) shortener_keys.append(prefix + ('frames', '*', 'args', '*')) shortener_keys.append(prefix + ('frames', '*', 'kwargs', '*')) shortener_keys.append(prefix + ('frames', '*', 'locals', '*')) shortener_keys.extend(SETTINGS['shortener_keys']) shortener = ShortenerTransform(safe_repr=SETTINGS['locals']['safe_repr'], keys=shortener_keys, **SETTINGS['locals']['sizes']) _transforms = [ shortener, # priority: 10 _scrub_redact_transform, # priority: 20 _serialize_transform, # priority: 30 ScrubUrlTransform(suffixes=[(field,) for field in SETTINGS['url_fields']], params_to_scrub=SETTINGS['scrub_fields']) # priority: 50 ] # Add custom transforms if len(SETTINGS['custom_transforms']) > 0: _transforms.extend(SETTINGS['custom_transforms']) # Sort the transforms by priority _transforms = sorted(_transforms, key=lambda x: x.priority) _threads = queue.Queue() events.reset() filters.add_builtin_filters(SETTINGS) _initialized = True def _configure_transport(**kw): configuration = _requests_configuration(**kw) transport.configure_pool(**configuration) def _requests_configuration(**kw): keys = { 'request_pool_connections': 'pool_connections', 'request_pool_maxsize': 'pool_maxsize', 'request_max_retries': 'max_retries', } return {keys[k]: kw.get(k, None) for k in keys} def lambda_function(f): """ Decorator for making error handling on AWS Lambda easier """ @functools.wraps(f) def wrapper(event, context): global _CURRENT_LAMBDA_CONTEXT _CURRENT_LAMBDA_CONTEXT = context try: result = f(event, context) return wait(lambda: result) except: cls, exc, trace = sys.exc_info() report_exc_info((cls, exc, trace.tb_next)) wait() raise return wrapper def report_exc_info(exc_info=None, request=None, extra_data=None, payload_data=None, level=None, **kw): """ Reports an exception to Rollbar, using exc_info (from calling sys.exc_info()) exc_info: optional, should be the result of calling sys.exc_info(). If omitted, sys.exc_info() will be called here. request: optional, a WebOb, Werkzeug-based or Sanic request object. extra_data: optional, will be included in the 'custom' section of the payload payload_data: optional, dict that will override values in the final payload (e.g. 'level' or 'fingerprint') kw: provided for legacy purposes; unused. Example usage: rollbar.init(access_token='YOUR_PROJECT_ACCESS_TOKEN') try: do_something() except: rollbar.report_exc_info(sys.exc_info(), request, {'foo': 'bar'}, {'level': 'warning'}) """ if exc_info is None: exc_info = sys.exc_info() try: return _report_exc_info(exc_info, request, extra_data, payload_data, level=level) except Exception as e: log.exception("Exception while reporting exc_info to Rollbar. %r", e) def report_message(message, level='error', request=None, extra_data=None, payload_data=None): """ Reports an arbitrary string message to Rollbar. message: the string body of the message level: level to report at. One of: 'critical', 'error', 'warning', 'info', 'debug' request: the request object for the context of the message extra_data: optional, will be included in the 'custom' section of the payload payload_data: param names to pass in the 'data' level of the payload; overrides defaults. """ try: return _report_message(message, level, request, extra_data, payload_data) except Exception as e: log.exception("Exception while reporting message to Rollbar. %r", e) def send_payload(payload, access_token): """ Sends a payload object, (the result of calling _build_payload() + _serialize_payload()). Uses the configured handler from SETTINGS['handler'] Available handlers: - 'blocking': calls _send_payload() (which makes an HTTP request) immediately, blocks on it - 'thread': starts a single-use thread that will call _send_payload(). returns immediately. - 'async': calls _send_payload_async() (which makes an async HTTP request using default async handler) - 'agent': writes to a log file to be processed by rollbar-agent - 'tornado': calls _send_payload_tornado() (which makes an async HTTP request using tornado's AsyncHTTPClient) - 'gae': calls _send_payload_appengine() (which makes a blocking call to Google App Engine) - 'twisted': calls _send_payload_twisted() (which makes an async HTTP request using Twisted and Treq) - 'httpx': calls _send_payload_httpx() (which makes an async HTTP request using HTTPX) - 'thread_pool': uses a pool of worker threads to make HTTP requests off the main thread. Returns immediately. """ payload = events.on_payload(payload) if payload is False: return if sys.version_info >= (3, 6): from rollbar.lib._async import get_current_handler handler = get_current_handler() else: handler = SETTINGS.get('handler') if handler == 'twisted': payload['data']['framework'] = 'twisted' payload_str = _serialize_payload(payload) if handler == 'blocking': _send_payload(payload_str, access_token) elif handler == 'agent': agent_log.error(payload_str) elif handler == 'tornado': if TornadoAsyncHTTPClient is None: log.error('Unable to find tornado') return _send_payload_tornado(payload_str, access_token) elif handler == 'gae': if AppEngineFetch is None: log.error('Unable to find AppEngine URLFetch module') return _send_payload_appengine(payload_str, access_token) elif handler == 'twisted': if treq is None: log.error('Unable to find Treq') return _send_payload_twisted(payload_str, access_token) elif handler == 'httpx': if httpx is None: log.error('Unable to find HTTPX') return _send_payload_httpx(payload_str, access_token) elif handler == 'async': if AsyncHTTPClient is None: log.error('Unable to find async handler') return _send_payload_async(payload_str, access_token) elif handler == 'thread': _send_payload_thread(payload_str, access_token) elif handler == 'thread_pool': _send_payload_thread_pool(payload_str, access_token) else: # default to 'thread' _send_payload_thread(payload_str, access_token) def search_items(title, return_fields=None, access_token=None, endpoint=None, **search_fields): """ Searches a project for items that match the input criteria. title: all or part of the item's title to search for. return_fields: the fields that should be returned for each item. e.g. ['id', 'project_id', 'status'] will return a dict containing only those fields for each item. access_token: a project access token. If this is not provided, the one provided to init() will be used instead. search_fields: additional fields to include in the search. currently supported: status, level, environment """ if not title: return [] if return_fields is not None: return_fields = ','.join(return_fields) return _get_api('search/', title=title, fields=return_fields, access_token=access_token, endpoint=endpoint, **search_fields) def wait(f=None): _threads.join() if f is not None: return f() class ApiException(Exception): """ This exception will be raised if there was a problem decoding the response from an API call. """ pass class ApiError(ApiException): """ This exception will be raised if the API response contains an 'err' field, denoting there was a problem fulfilling the api request. """ pass class Result(object): """ This class encapsulates the response from an API call. Usage: result = search_items(title='foo', fields=['id']) print result.data """ def __init__(self, access_token, path, params, data): self.access_token = access_token self.path = path self.params = params self.data = data def __str__(self): return str(self.data) class PagedResult(Result): """ This class wraps the response from an API call that responded with a page of results. Usage: result = search_items(title='foo', fields=['id']) print 'First page: %d, data: %s' % (result.page, result.data) result = result.next_page() print 'Second page: %d, data: %s' % (result.page, result.data) """ def __init__(self, access_token, path, page_num, params, data, endpoint=None): super(PagedResult, self).__init__(access_token, path, params, data) self.page = page_num self.endpoint = endpoint def next_page(self): params = copy.copy(self.params) params['page'] = self.page + 1 return _get_api(self.path, endpoint=self.endpoint, **params) def prev_page(self): if self.page <= 1: return self params = copy.copy(self.params) params['page'] = self.page - 1 return _get_api(self.path, endpoint=self.endpoint, **params) ## internal functions def _resolve_exception_class(idx, filter): cls, level = filter if isinstance(cls, str): # Lazily resolve class name parts = cls.split('.') module = '.'.join(parts[:-1]) if module in sys.modules and hasattr(sys.modules[module], parts[-1]): cls = getattr(sys.modules[module], parts[-1]) SETTINGS['exception_level_filters'][idx] = (cls, level) else: cls = None return cls, level def _filtered_level(exception): for i, filter in enumerate(SETTINGS['exception_level_filters']): cls, level = _resolve_exception_class(i, filter) if cls and isinstance(exception, cls): return level return None def _is_ignored(exception): return _filtered_level(exception) == 'ignored' def _create_agent_log(): """ Creates .rollbar log file for use with rollbar-agent """ log_file = SETTINGS['agent.log_file'] if not log_file.endswith('.rollbar'): log.error("Provided agent log file does not end with .rollbar, which it must. " "Using default instead.") log_file = DEFAULTS['agent.log_file'] retval = logging.getLogger('rollbar_agent') handler = logging.FileHandler(log_file, 'a', 'utf-8') formatter = logging.Formatter('%(message)s') handler.setFormatter(formatter) retval.addHandler(handler) retval.setLevel(logging.WARNING) return retval def _report_exc_info(exc_info, request, extra_data, payload_data, level=None): """ Called by report_exc_info() wrapper """ if not _check_config(): return filtered_level = _filtered_level(exc_info[1]) if level is None: level = filtered_level filtered_exc_info = events.on_exception_info(exc_info, request=request, extra_data=extra_data, payload_data=payload_data, level=level) if filtered_exc_info is False: return cls, exc, trace = filtered_exc_info data = _build_base_data(request) if level is not None: data['level'] = level # walk the trace chain to collect cause and context exceptions trace_chain = _walk_trace_chain(cls, exc, trace) extra_trace_data = None if len(trace_chain) > 1: data['body'] = { 'trace_chain': trace_chain } if payload_data and ('body' in payload_data) and ('trace' in payload_data['body']): extra_trace_data = payload_data['body']['trace'] del payload_data['body']['trace'] else: data['body'] = { 'trace': trace_chain[0] } if extra_data: extra_data = extra_data if not isinstance(extra_data, dict): extra_data = {'value': extra_data} if extra_trace_data: extra_data = dict_merge(extra_data, extra_trace_data, silence_errors=True) data['custom'] = extra_data if extra_trace_data and not extra_data: data['custom'] = extra_trace_data request = _get_actual_request(request) _add_request_data(data, request) _add_person_data(data, request) _add_lambda_context_data(data) _add_session_data(data) data['server'] = _build_server_data() if payload_data: data = dict_merge(data, payload_data, silence_errors=True) payload = _build_payload(data) send_payload(payload, payload.get('access_token')) return data['uuid'] def _walk_trace_chain(cls, exc, trace): trace_chain = [_trace_data(cls, exc, trace)] seen_exceptions = {exc} while True: exc = getattr(exc, '__cause__', None) or getattr(exc, '__context__', None) if not exc: break trace_chain.append(_trace_data(type(exc), exc, getattr(exc, '__traceback__', None))) if exc in seen_exceptions: break seen_exceptions.add(exc) return trace_chain def _trace_data(cls, exc, trace): # exception info # most recent call last raw_frames = traceback.extract_tb(trace) frames = [{'filename': f[0], 'lineno': f[1], 'method': f[2], 'code': f[3]} for f in raw_frames] trace_data = { 'frames': frames, 'exception': { 'class': getattr(cls, '__name__', cls.__class__.__name__), 'message': str(exc), } } _add_locals_data(trace_data, (cls, exc, trace)) return trace_data def _report_message(message, level, request, extra_data, payload_data): """ Called by report_message() wrapper """ if not _check_config(): return filtered_message = events.on_message(message, request=request, extra_data=extra_data, payload_data=payload_data, level=level) if filtered_message is False: return data = _build_base_data(request, level=level) # message data['body'] = { 'message': { 'body': filtered_message } } if extra_data: extra_data = extra_data data['body']['message'].update(extra_data) data['custom'] = extra_data request = _get_actual_request(request) _add_request_data(data, request) _add_person_data(data, request) _add_lambda_context_data(data) _add_session_data(data) data['server'] = _build_server_data() if payload_data: data = dict_merge(data, payload_data, silence_errors=True) payload = _build_payload(data) send_payload(payload, payload.get('access_token')) return data['uuid'] def _add_session_data(data: dict) -> None: """ Adds session data to the payload data if it can be found in the current session or request. """ session_data = get_current_session() if session_data: _add_session_attributes(data, session_data) return request = _session_data_from_request(data) if request is None: return session_data = parse_session_request_baggage_headers(request.get('headers', None)) if session_data: _add_session_attributes(data, session_data) def _add_session_attributes(data: dict, session_data: list[Attribute]) -> None: """ Adds session attributes to the payload data. This function is careful to not overwrite any existing data in the payload. """ if 'attributes' not in data: data['attributes'] = session_data return existing_keys = {a['key'] for a in data['attributes']} for attribute in session_data: if attribute['key'] not in existing_keys: data['attributes'].append(attribute) def _session_data_from_request(data: dict) -> dict: """ Tries to find session data in the request object. Use the request object if provided, otherwise check the data as it may already contain the request object. This is true for some frameworks (e.g. Django). """ if data is not None and 'request' in data: return data.get('request', None) return _get_actual_request(_build_request_data(get_request())) def _check_config(): if not SETTINGS.get('enabled'): log.info("pyrollbar: Not reporting because rollbar is disabled.") return False # skip access token check for the agent handler if SETTINGS.get('handler') == 'agent': return True # make sure we have an access_token if not SETTINGS.get('access_token'): log.warning("pyrollbar: No access_token provided. Please configure by calling rollbar.init() with your access token.") return False return True def _build_base_data(request, level='error'): data = { 'timestamp': int(time.time()), 'environment': SETTINGS['environment'], 'level': level, 'language': 'python %s' % '.'.join(str(x) for x in sys.version_info[:3]), 'notifier': SETTINGS['notifier'], 'uuid': str(uuid.uuid4()), } if SETTINGS.get('code_version'): data['code_version'] = SETTINGS['code_version'] if BASE_DATA_HOOK: BASE_DATA_HOOK(request, data) return data def _add_person_data(data, request): try: person_data = _build_person_data(request) except Exception as e: log.exception("Exception while building person data for Rollbar payload: %r", e) else: if person_data: if not SETTINGS['capture_username'] and 'username' in person_data: person_data['username'] = None if not SETTINGS['capture_email'] and 'email' in person_data: person_data['email'] = None data['person'] = person_data def _build_person_data(request): """ Returns a dictionary describing the logged-in user using data from `request`. Try request.rollbar_person first, then 'user', then 'user_id' """ if hasattr(request, 'rollbar_person'): rollbar_person_prop = request.rollbar_person person = rollbar_person_prop() if callable(rollbar_person_prop) else rollbar_person_prop if person and isinstance(person, dict): return person else: return None if StarletteRequest: from rollbar.contrib.starlette.requests import hasuser else: def hasuser(request): return True if hasuser(request) and hasattr(request, 'user'): user_prop = request.user user = user_prop() if callable(user_prop) else user_prop if not user: return None elif isinstance(user, dict): return user else: retval = {} if getattr(user, 'id', None): retval['id'] = str(user.id) elif getattr(user, 'user_id', None): retval['id'] = str(user.user_id) # id is required, so only include username/email if we have an id if retval.get('id'): username = getattr(user, 'username', None) email = getattr(user, 'email', None) retval.update({ 'username': username, 'email': email }) return retval if hasattr(request, 'user_id'): user_id_prop = request.user_id user_id = user_id_prop() if callable(user_id_prop) else user_id_prop if not user_id: return None return {'id': str(user_id)} def _get_func_from_frame(frame): func_name = inspect.getframeinfo(frame).function caller = frame.f_back if caller: func = caller.f_locals.get(func_name, caller.f_globals.get(func_name)) else: func = None return func def _add_locals_data(trace_data, exc_info): if not SETTINGS['locals']['enabled']: return frames = trace_data['frames'] cur_tb = exc_info[2] frame_num = 0 num_frames = len(frames) while cur_tb: cur_frame = frames[frame_num] tb_frame = cur_tb.tb_frame cur_tb = cur_tb.tb_next if not isinstance(tb_frame, types.FrameType): # this can happen if the traceback or frame is wrapped in some way, # for example by `ExceptionInfo` in # https://github.com/celery/billiard/blob/master/billiard/einfo.py log.warning('Traceback frame not a types.FrameType. Ignoring.') frame_num += 1 continue # Create placeholders for argspec/varargspec/keywordspec/locals argspec = None varargspec = None keywordspec = None _locals = {} try: arginfo = inspect.getargvalues(tb_frame) # Optionally fill in locals for this frame if arginfo.locals and _check_add_locals(cur_frame, frame_num, num_frames): # Get all of the named args argspec = arginfo.args if arginfo.varargs is not None: varargspec = arginfo.varargs if SETTINGS['locals']['scrub_varargs']: temp_varargs = list(arginfo.locals[varargspec]) for i, arg in enumerate(temp_varargs): temp_varargs[i] = REDACT_REF arginfo.locals[varargspec] = tuple(temp_varargs) if arginfo.keywords is not None: keywordspec = arginfo.keywords _locals.update(arginfo.locals.items()) except Exception: log.exception('Error while extracting arguments from frame. Ignoring.') # Finally, serialize each arg/kwarg/local separately so that we only report # CircularReferences for each variable, instead of for the entire payload # as would be the case if we serialized that payload in one-shot. if argspec: cur_frame['argspec'] = argspec if varargspec: cur_frame['varargspec'] = varargspec if keywordspec: cur_frame['keywordspec'] = keywordspec if _locals: try: cur_frame['locals'] = {k: _serialize_frame_data(v) for k, v in _locals.items()} except Exception: log.exception('Error while serializing frame data.') frame_num += 1 def _serialize_frame_data(data): return transforms.transform( data, [_scrub_redact_transform, _serialize_transform], batch_transforms=SETTINGS['batch_transforms'] ) def _add_lambda_context_data(data): """ Attempts to add information from the lambda context if it exists """ global _CURRENT_LAMBDA_CONTEXT context = _CURRENT_LAMBDA_CONTEXT if context is None: return try: lambda_data = { 'lambda': { 'remaining_time_in_millis': context.get_remaining_time_in_millis(), 'function_name': context.function_name, 'function_version': context.function_version, 'arn': context.invoked_function_arn, 'request_id': context.aws_request_id, } } if 'custom' in data: data['custom'] = dict_merge(data['custom'], lambda_data, silence_errors=True) else: data['custom'] = lambda_data except Exception as e: log.exception("Exception while adding lambda context data: %r", e) finally: _CURRENT_LAMBDA_CONTEXT = None def _add_request_data(data, request): """ Attempts to build request data; if successful, sets the 'request' key on `data`. """ try: request_data = _build_request_data(request) except Exception as e: log.exception("Exception while building request_data for Rollbar payload: %r", e) else: if request_data: _filter_ip(request_data, SETTINGS['capture_ip']) data['request'] = request_data def _check_add_locals(frame, frame_num, total_frames): """ Returns True if we should record local variables for the given frame. """ # Include the last frames locals # Include any frame locals that came from a file in the project's root root = SETTINGS.get('root') if root: # coerce to string, in case root is a Path object root = str(root) else: root = '' return any(((frame_num == total_frames - 1), ('root' in SETTINGS and (frame.get('filename') or '').lower().startswith(root.lower())))) def _get_actual_request(request): if WerkzeugLocalProxy and isinstance(request, WerkzeugLocalProxy): try: actual_request = request._get_current_object() except RuntimeError: return None return actual_request return request def _build_request_data(request): """ Returns a dictionary containing data from the request. """ # webob (pyramid) if WebobBaseRequest and isinstance(request, WebobBaseRequest): return _build_webob_request_data(request) # django if DjangoHttpRequest and isinstance(request, DjangoHttpRequest): return _build_django_request_data(request) # django rest framework if RestFrameworkRequest and isinstance(request, RestFrameworkRequest): return _build_django_request_data(request) # werkzeug (flask) if WerkzeugRequest and isinstance(request, WerkzeugRequest): return _build_werkzeug_request_data(request) # tornado if TornadoRequest and isinstance(request, TornadoRequest): return _build_tornado_request_data(request) # bottle if BottleRequest and isinstance(request, BottleRequest): return _build_bottle_request_data(request) # Sanic if SanicRequest and isinstance(request, SanicRequest): return _build_sanic_request_data(request) # falcon if FalconRequest and isinstance(request, FalconRequest): return _build_falcon_request_data(request) # Plain wsgi (should be last) if isinstance(request, dict) and 'wsgi.version' in request: return _build_wsgi_request_data(request) # FastAPI (built on top of Starlette, so keep the order) if FastAPIRequest and isinstance(request, FastAPIRequest): return _build_fastapi_request_data(request) # Starlette (should be the last one for Starlette based frameworks) if StarletteRequest and isinstance(request, StarletteRequest): return _build_starlette_request_data(request) return None def _build_webob_request_data(request): request_data = { 'url': request.url, 'GET': dict(request.GET), 'user_ip': _extract_user_ip(request), 'headers': dict(request.headers), 'method': request.method, } try: if request.json: request_data['json'] = request.json except: pass # pyramid matchdict if getattr(request, 'matchdict', None): request_data['params'] = request.matchdict # workaround for webob bug when the request body contains binary data but has a text # content-type try: request_data['POST'] = dict(request.POST) except UnicodeDecodeError: request_data['body'] = request.body return request_data def _extract_wsgi_headers(items): headers = {} for k, v in items: if k.startswith('HTTP_'): header_name = '-'.join(k[len('HTTP_'):].replace('_', ' ').title().split(' ')) headers[header_name] = v return headers def _build_django_request_data(request): url = request.build_absolute_uri() request_data = { 'url': url, 'method': request.method, 'GET': dict(request.GET), 'POST': dict(request.POST), 'user_ip': _wsgi_extract_user_ip(request.META), } if SETTINGS['include_request_body']: try: request_data['body'] = request.body except: pass request_data['headers'] = _extract_wsgi_headers(request.META.items()) return request_data def _build_werkzeug_request_data(request): request_data = { 'url': request.url, 'GET': dict(request.args), 'POST': dict(request.form), 'user_ip': _extract_user_ip(request), 'headers': dict(request.headers), 'method': request.method, 'files_keys': list(request.files.keys()), } if SETTINGS['include_request_body']: try: if request.json: request_data['body'] = request.json except Exception: pass return request_data def _build_tornado_request_data(request): request_data = { 'url': request.full_url(), 'user_ip': request.remote_ip, 'headers': dict(request.headers), 'method': request.method, 'files_keys': request.files.keys(), 'start_time': getattr(request, '_start_time', None), } request_data[request.method] = request.arguments return request_data def _build_bottle_request_data(request): request_data = { 'url': request.url, 'user_ip': request.remote_addr, 'headers': dict(request.headers), 'method': request.method, 'GET': dict(request.query) } if SETTINGS['include_request_body']: if request.json: try: request_data['body'] = request.body.getvalue() except: pass else: request_data['POST'] = dict(request.forms) return request_data def _build_sanic_request_data(request): request_data = { 'url': request.url, 'user_ip': request.remote_addr, 'headers': request.headers, 'method': request.method, 'GET': dict(request.args) } if SETTINGS['include_request_body']: if request.json: try: request_data['body'] = request.json except: pass else: request_data['POST'] = request.form return request_data def _build_falcon_request_data(request): request_data = { 'url': request.url, 'user_ip': _wsgi_extract_user_ip(request.env), 'headers': dict(request.headers), 'method': request.method, 'GET': dict(request.params), 'context': dict(request.context), } return request_data def _build_wsgi_request_data(request): request_data = { 'url': wsgiref.util.request_uri(request), 'user_ip': _wsgi_extract_user_ip(request), 'method': request.get('REQUEST_METHOD'), } if 'QUERY_STRING' in request: request_data['GET'] = parse_qs(request['QUERY_STRING'], keep_blank_values=True) # Collapse single item arrays request_data['GET'] = {k: (v[0] if len(v) == 1 else v) for k, v in request_data['GET'].items()} request_data['headers'] = _extract_wsgi_headers(request.items()) if SETTINGS['include_request_body']: try: length = int(request.get('CONTENT_LENGTH', 0)) except ValueError: length = 0 input = request.get('wsgi.input') if length and input and hasattr(input, 'seek') and hasattr(input, 'tell'): pos = input.tell() input.seek(0, 0) request_data['body'] = input.read(length) input.seek(pos, 0) return request_data def _build_starlette_request_data(request): from starlette.datastructures import UploadFile request_data = { 'url': str(request.url), 'GET': dict(request.query_params), 'headers': dict(request.headers), 'method': request.method, 'user_ip': _starlette_extract_user_ip(request), 'params': dict(request.path_params), } if hasattr(request, '_form') and request._form is not None: request_data['POST'] = { k: v.filename if isinstance(v, UploadFile) else v for k, v in request._form.items() } request_data['files_keys'] = [ field.filename for field in request._form.values() if isinstance(field, UploadFile) ] if hasattr(request, '_body'): body = request._body.decode() else: body = None if body and SETTINGS['include_request_body']: request_data['body'] = body if hasattr(request, '_json'): request_data['json'] = request._json elif body: try: request_data['json'] = json.loads(body) except json.JSONDecodeError: pass # Filter out empty values request_data = {k: v for k, v in request_data.items() if v} return request_data def _build_fastapi_request_data(request): return _build_starlette_request_data(request) def _filter_ip(request_data, capture_ip): if 'user_ip' not in request_data or capture_ip == True: return current_ip = request_data['user_ip'] if not current_ip: return new_ip = current_ip if not capture_ip: new_ip = None elif capture_ip == ANONYMIZE: try: if '.' in current_ip: new_ip = '.'.join(current_ip.split('.')[0:3]) + '.0' elif ':' in current_ip: parts = current_ip.split(':') if len(parts) > 2: terminal = '0000:0000:0000:0000:0000' new_ip = ':'.join(parts[0:3] + [terminal]) else: new_ip = None except: new_ip = None request_data['user_ip'] = new_ip def _build_server_data(): """ Returns a dictionary containing information about the server environment. """ # server environment host = SETTINGS.get('host') or socket.gethostname() server_data = { 'host': host, 'pid': os.getpid() } # argv does not always exist in embedded python environments argv = getattr(sys, 'argv', None) if argv: server_data['argv'] = argv for key in ['branch', 'root']: if SETTINGS.get(key): server_data[key] = SETTINGS[key] return server_data def _transform(obj, key=None): return transforms.transform( obj, _transforms, key=key, batch_transforms=SETTINGS['batch_transforms'] ) def _build_payload(data): """ Returns the full payload as a string. """ for k, v in data.items(): data[k] = _transform(v, key=(k,)) payload = { 'access_token': SETTINGS['access_token'], 'data': data } return payload def _serialize_payload(payload): return json.dumps(payload, default=defaultJSONEncode) def _send_payload(payload_str, access_token): try: _post_api('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) try: _threads.get_nowait() _threads.task_done() except queue.Empty: pass def _send_payload_thread(payload_str, access_token): thread = threading.Thread(target=_send_payload, args=(payload_str, access_token)) _threads.put(thread) thread.start() def _send_payload_pool(payload_str, access_token): try: _post_api('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) def _send_payload_thread_pool(payload_str, access_token): from rollbar.lib.thread_pool import submit submit(_send_payload_pool, payload_str, access_token) def _send_payload_appengine(payload_str, access_token): try: _post_api_appengine('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) def _post_api_appengine(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: headers['X-Rollbar-Access-Token'] = access_token url = urljoin(SETTINGS['endpoint'], path) resp = AppEngineFetch(url, method="POST", payload=payload_str, headers=headers, allow_truncated=False, deadline=SETTINGS.get('timeout', DEFAULT_TIMEOUT), validate_certificate=SETTINGS.get('verify_https', True)) return _parse_response(path, SETTINGS['access_token'], payload_str, resp) def _post_api(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: headers['X-Rollbar-Access-Token'] = access_token url = urljoin(SETTINGS['endpoint'], path) resp = transport.post(url, data=payload_str, headers=headers, timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), verify=SETTINGS.get('verify_https', True), proxy=SETTINGS.get('http_proxy'), proxy_user=SETTINGS.get('http_proxy_user'), proxy_password=SETTINGS.get('http_proxy_password')) return _parse_response(path, SETTINGS['access_token'], payload_str, resp) def _get_api(path, access_token=None, endpoint=None, **params): access_token = access_token or SETTINGS['access_token'] url = urljoin(endpoint or SETTINGS['endpoint'], path) params['access_token'] = access_token resp = transport.get(url, params=params, verify=SETTINGS.get('verify_https', True), proxy=SETTINGS.get('http_proxy'), proxy_user=SETTINGS.get('http_proxy_user'), proxy_password=SETTINGS.get('http_proxy_password')) return _parse_response(path, access_token, params, resp, endpoint=endpoint) def _send_payload_tornado(payload_str, access_token): try: _post_api_tornado('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) def _post_api_tornado(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: headers['X-Rollbar-Access-Token'] = access_token else: access_token = SETTINGS['access_token'] url = urljoin(SETTINGS['endpoint'], path) def post_tornado_cb(resp): r = requests.Response() r._content = resp.body r.status_code = resp.code r.headers.update(resp.headers) try: _parse_response(path, access_token, payload_str, r) except Exception as e: log.exception('Exception while posting item %r', e) TornadoAsyncHTTPClient().fetch(url, callback=post_tornado_cb, raise_error=False, body=payload_str, method='POST', connect_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT), request_timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT)) def _send_payload_twisted(payload_str, access_token): try: _post_api_twisted('item/', payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) def _post_api_twisted(path, payload_str, access_token=None): def post_data_cb(data, resp): resp._content = data _parse_response(path, SETTINGS['access_token'], payload_str, resp) def post_cb(resp): r = requests.Response() r.status_code = resp.code r.headers.update(resp.headers.getAllRawHeaders()) return treq.content(resp).addCallback(post_data_cb, r) headers = {'Content-Type': ['application/json; charset=utf-8']} if access_token is not None: headers['X-Rollbar-Access-Token'] = [access_token] url = urljoin(SETTINGS['endpoint'], path) try: encoded_payload = payload_str.encode('utf8') except (UnicodeDecodeError, UnicodeEncodeError): encoded_payload = payload_str treq_client = treq.client.HTTPClient(Agent(reactor, contextFactory=VerifyHTTPS())) d = treq_client.post(url, encoded_payload, headers=headers, timeout=SETTINGS.get('timeout', DEFAULT_TIMEOUT)) d.addCallback(post_cb) def _send_payload_httpx(payload_str, access_token): from rollbar.lib._async import call_later, _post_api_httpx try: call_later(_post_api_httpx('item/', payload_str, access_token=access_token)) except Exception as e: log.exception('Exception while posting item %r', e) def _send_payload_async(payload_str, access_token): try: _send_payload_httpx(payload_str, access_token=access_token) except Exception as e: log.exception('Exception while posting item %r', e) def _send_failsafe(message, uuid, host): body_message = ('Failsafe from pyrollbar: {0}. Original payload may be found ' 'in your server logs by searching for the UUID.').format(message) data = { 'level': 'error', 'environment': SETTINGS['environment'], 'body': { 'message': { 'body': body_message } }, 'notifier': SETTINGS['notifier'], 'custom': { 'orig_uuid': uuid, 'orig_host': host }, 'failsafe': True, 'internal': True, } payload = _build_payload(data) try: send_payload(payload, SETTINGS['access_token']) except Exception: log.exception('Rollbar: Error sending failsafe.') def _parse_response(path, access_token, params, resp, endpoint=None): if isinstance(resp, requests.Response): try: data = resp.text except Exception: data = resp.content log.error('resp.text is undefined, resp.content is %r', resp.content) else: data = resp.content global _LAST_RESPONSE_STATUS last_response_was_429 = _LAST_RESPONSE_STATUS == 429 _LAST_RESPONSE_STATUS = resp.status_code if resp.status_code == 429: if SETTINGS['log_all_rate_limited_items'] or not last_response_was_429: log.warning("Rollbar: over rate limit, data was dropped.") if SETTINGS['log_payload_on_error']: log.warning("Payload was: %r", params) return elif resp.status_code == 502: log.exception('Rollbar api returned a 502') return elif resp.status_code == 413: uuid = None host = None try: payload = json.loads(params) uuid = payload['data']['uuid'] host = payload['data']['server']['host'] log.error("Rollbar: request entity too large for UUID %r\n.", uuid) if SETTINGS['log_payload_on_error']: log.error("Payload:\n%r", payload) except (TypeError, ValueError): log.exception('Unable to decode JSON for failsafe.') except KeyError: log.exception('Unable to find payload parameters for failsafe.') _send_failsafe('payload too large', uuid, host) # TODO: Should we return here? elif resp.status_code != 200: log.warning("Got unexpected status code from Rollbar api: %s\nResponse:\n%s", resp.status_code, data) # TODO: Should we also return here? try: json_data = json.loads(data) except (TypeError, ValueError): log.exception('Could not decode Rollbar api response:\n%s', data) raise ApiException('Request to %s returned invalid JSON response', path) else: if json_data.get('err'): raise ApiError(json_data.get('message') or 'Unknown error') result = json_data.get('result', {}) if 'page' in result: return PagedResult(access_token, path, result['page'], params, result, endpoint=endpoint) else: return Result(access_token, path, params, result) def _extract_user_ip_from_headers(request): forwarded_for = request.headers.get('X-Forwarded-For') if forwarded_for: return forwarded_for real_ip = request.headers.get('X-Real-Ip') if real_ip: return real_ip return None def _extract_user_ip(request): return _extract_user_ip_from_headers(request) or request.remote_addr def _wsgi_extract_user_ip(environ): forwarded_for = environ.get('HTTP_X_FORWARDED_FOR') if forwarded_for: return forwarded_for real_ip = environ.get('HTTP_X_REAL_IP') if real_ip: return real_ip return environ['REMOTE_ADDR'] def _starlette_extract_user_ip(request): if not hasattr(request, 'client'): return _extract_user_ip_from_headers(request) if not hasattr(request.client, 'host'): return _extract_user_ip_from_headers(request) return request.client.host or _extract_user_ip_from_headers(request) ================================================ FILE: rollbar/cli.py ================================================ import optparse import sys import rollbar VERSION = '0.1' verbose = False def _gen_report_message(level): def _wrapped(lines): line_data = '\n'.join(lines) if verbose: print('Rollbar [%s]: %s' % (level, line_data)) rollbar.report_message(line_data, level=level, extra_data={'cli_version': VERSION}) return _wrapped CMDS = { 'debug': _gen_report_message('debug'), 'info': _gen_report_message('info'), 'warning': _gen_report_message('warning'), 'error': _gen_report_message('error'), 'critical': _gen_report_message('critical'), } def main(): global verbose parser = optparse.OptionParser(version='%prog version ' + VERSION) parser.add_option('-t', '--access_token', dest='access_token', help="You project's access token from rollbar.com.", metavar='ACCESS_TOKEN') parser.add_option('-e', '--environment', dest='environment', help="The environment to report errors and messages to. \ Can be any string; suggestions: 'production', 'development', 'staging'", metavar='ENVIRONMENT') parser.add_option('-u', '--url', dest='endpoint_url', help="The Rollbar API endpoint url to send data to.", metavar='ENDPOINT_URL', default=rollbar.DEFAULT_ENDPOINT) parser.add_option('-m', '--handler', dest='handler', help="The method in which to report errors.", metavar='HANDLER', choices=["thread", "blocking", "agent"], default="blocking") parser.add_option('-v', '--verbose', dest='verbose', help="Print verbose output.", action='store_true', default=False) options, args = parser.parse_args() access_token = options.access_token env = options.environment endpoint = options.endpoint_url handler = options.handler verbose = options.verbose if not access_token: parser.error('missing access_token') if not env: parser.error('missing environment') rollbar.init(access_token, environment=env, endpoint=endpoint, handler=handler) def _do_cmd(cmd_name, line): cmd = CMDS.get(cmd_name.lower()) if cmd: cmd([line]) return True return False if len(args) > 1: sent = _do_cmd(args[0], ' '.join(args[1:])) sys.exit(0 if sent else 1) cur_cmd_name = None try: cur_line = sys.stdin.readline() while cur_line: cur_line = cur_line.strip() parts = cur_line.split(' ') if parts: cur_cmd_name = parts[0] parts = parts[1:] _do_cmd(cur_cmd_name, ' '.join(parts)) cur_line = sys.stdin.readline() except (KeyboardInterrupt, SystemExit) as e: pass ================================================ FILE: rollbar/contrib/__init__.py ================================================ ================================================ FILE: rollbar/contrib/asgi/__init__.py ================================================ __all__ = ['ReporterMiddleware'] from .middleware import ReporterMiddleware ================================================ FILE: rollbar/contrib/asgi/integration.py ================================================ import inspect import functools import rollbar class IntegrationBase: """ Superclass for class integrations. """ def __init__(self): if hasattr(self, '_integrate'): self._integrate() class integrate: """ Integrates functions and classes (derived from IntegrationBase) in the SDK. """ def __init__(self, *, framework_name='unknown'): self._framework_name = framework_name def __call__(self, obj): if inspect.isclass(obj): obj._integrate = self._register_hook return obj else: return self._insert_hook(obj) def _register_hook(self): def _hook(request, data): data['framework'] = self._framework_name rollbar.BASE_DATA_HOOK = _hook def _insert_hook(self, func): @functools.wraps(func) def wrapper(*args, **kwargs): self._register_hook() return func(*args, **kwargs) return wrapper ================================================ FILE: rollbar/contrib/asgi/middleware.py ================================================ import logging import sys from typing import Iterable import rollbar from .integration import IntegrationBase, integrate from .types import ASGIApp, Receive, Scope, Send from rollbar.lib._async import RollbarAsyncError, try_report from rollbar.lib.session import set_current_session, reset_current_session log = logging.getLogger(__name__) @integrate(framework_name='asgi') class ReporterMiddleware(IntegrationBase): def __init__(self, app: ASGIApp) -> None: super().__init__() self.app = app async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope['type'] == 'http': set_current_session(self._format_headers(scope['headers'])) try: await self.app(scope, receive, send) except Exception: if scope['type'] == 'http': exc_info = sys.exc_info() try: await try_report(exc_info) except RollbarAsyncError: log.warning( 'Failed to report asynchronously. Trying to report synchronously.' ) rollbar.report_exc_info(exc_info) raise finally: if scope['type'] == 'http': reset_current_session() @staticmethod def _format_headers(headers: Iterable[tuple[bytes, bytes]]) -> dict[str, str]: """ Convert list of header tuples to a dictionary with string keys and values. Headers are expected to be in the format: [(b'header-name', b'header-value'), ...] """ return {key.decode('latin-1'): value.decode('latin-1') for key, value in headers} ================================================ FILE: rollbar/contrib/asgi/types.py ================================================ # Copyright © 2018, [Encode OSS Ltd](https://www.encode.io/). # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import typing Scope = typing.MutableMapping[str, typing.Any] Message = typing.MutableMapping[str, typing.Any] Receive = typing.Callable[[], typing.Awaitable[Message]] Send = typing.Callable[[Message], typing.Awaitable[None]] ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]] ================================================ FILE: rollbar/contrib/bottle/__init__.py ================================================ import bottle, rollbar, sys class RollbarBottleReporter(object): ''' A Bottle plugin that reports errors to Rollbar All args and kwargs are passed to `rollbar.init` ''' name = 'rollbar-bottle-reporter' api = 2 def __init__(self, *args, **kwargs): if 'exception_level_filters' in kwargs: kwargs['exception_level_filters'].append((bottle.BaseResponse, 'ignored')) else: kwargs['exception_level_filters'] = [(bottle.BaseResponse, 'ignored')] rollbar.init(*args, **kwargs) def hook(request, data): data['framework'] = 'bottle' if request: route = request['bottle.route'] data['context'] = route.name or route.rule rollbar.BASE_DATA_HOOK = hook def __call__(self, callback): def wrapper(*args, **kwargs): try: return callback(*args, **kwargs) except Exception as e: rollbar.report_exc_info(sys.exc_info(), request=bottle.request) raise return wrapper ================================================ FILE: rollbar/contrib/django/__init__.py ================================================ ================================================ FILE: rollbar/contrib/django/context_processors.py ================================================ """ django-rollbar context processor To install, add the following in your settings.py: 1. add 'rollbar.contrib.django.context_processors.rollbar_settings' to TEMPLATE_CONTEXT_PROCESSORS 2. add a section like this: ROLLBAR = { 'client_access_token': 'tokengoeshere', } 3. you can now access your rollbar settings as rollbar_settings from within your django templates See README.rst for full installation and configuration instructions. """ from django.conf import settings def rollbar_settings(request): """Grabs the rollbar settings to make them available to templates.""" if not hasattr(settings, 'ROLLBAR'): return {} return {'rollbar_settings': settings.ROLLBAR} ================================================ FILE: rollbar/contrib/django/middleware.py ================================================ r""" django-rollbar middleware There are two options for installing the Rollbar middleware. Both options require modifying your settings.py file. The first option is to use 'rollbar.contrib.django.middleware.RollbarNotifierMiddleware' which will report all exceptions to Rollbar including 404s. This middlware should be placed as the last item in your middleware list which is: * MIDDLEWARE_CLASSES in Django 1.9 and earlier * MIDDLEWARE in Django 1.10 and up The other option is two use the two separate middlewares: * 'rollbar.contrib.django.middleware.RollbarNotifierMiddlewareExcluding404' * 'rollbar.contrib.django.middleware.RollbarNotifierMiddlewareOnly404' The Excluding404 middleware should be placed as the last item in your middleware list, and the Only404 middleware should be placed as the first item in your middleware list. This allows 404s to be processed by your other middlewares before sendind an item to Rollbar. Therefore if you handle the 404 differently in a way that returns a response early you won't end up with a Rollbar item. Regardless of which method you use, you also should add a section to settings.py like this: ROLLBAR = { 'access_token': 'tokengoeshere', } This can be used for passing configuration options to Rollbar. Additionally, you can use the key 'ignorable_404_urls' to set an iterable of regular expression patterns to use to determine whether a 404 exception should be ignored based on the full url path for the request. For example, import re ROLLBAR = { 'access_token': 'YOUR_TOKEN', 'ignorable_404_urls': ( re.compile(r'/index\.php'), re.compile('/foobar'), ), } To get more control of middleware and enrich it with custom data you can subclass any of the middleware classes described above and optionally override the methods: def get_extra_data(self, request, exc): ''' May be defined. Must return a dict or None. Use it to put some custom extra data on rollbar event. ''' return def get_payload_data(self, request, exc): ''' May be defined. Must return a dict or None. Use it to put some custom payload data on rollbar event. ''' return You would then insert your custom subclass into your middleware configuration in the same place as the base class as described above. For example: 1. create a 'middleware.py' file on your project (name is up to you) 2. import the rollbar default middleware: 'from rollbar.contrib.django.middleware import RollbarNotifierMiddleware' 3. create your own middleware like this: class CustomRollbarNotifierMiddleware(RollbarNotifierMiddleware): def get_extra_data(self, request, exc): ''' May be defined. Must return a dict or None. Use it to put some custom extra data on rollbar event. ''' return def get_payload_data(self, request, exc): ''' May be defined. Must return a dict or None. Use it to put some custom payload data on rollbar event. ''' return 4. add 'path.to.your.CustomRollbarNotifierMiddleware' in your settings.py to a. MIDDLEWARE_CLASSES in Django 1.9 and earlier b. MIDDLEWARE in Django 1.10 and up 5. add a section like this in your settings.py: ROLLBAR = { 'access_token': 'tokengoeshere', } See README.rst for full installation and configuration instructions. """ import logging import sys import rollbar from django.core.exceptions import MiddlewareNotUsed from django.conf import settings from django.http import Http404 from rollbar import set_current_session from rollbar.lib.session import reset_current_session try: from django.urls import resolve except ImportError: from django.core.urlresolvers import resolve try: from django.utils.deprecation import MiddlewareMixin except ImportError: from rollbar.contrib.django.utils import MiddlewareMixin log = logging.getLogger(__name__) DEFAULTS = { 'web_base': 'https://rollbar.com', 'enabled': True, 'patch_debugview': True, 'exception_level_filters': [ (Http404, 'warning') ] } def _patch_debugview(rollbar_web_base): try: from django.views import debug except ImportError: return if rollbar_web_base.endswith('/'): rollbar_web_base = rollbar_web_base[:-1] # modify the TECHNICAL_500_TEMPLATE new_data = """ {% if view_in_rollbar_url %}

View in Rollbar

{% endif %} """ insert_before = '' replacement = new_data + insert_before if hasattr(debug, 'TECHNICAL_500_TEMPLATE'): if new_data in debug.TECHNICAL_500_TEMPLATE: return debug.TECHNICAL_500_TEMPLATE = debug.TECHNICAL_500_TEMPLATE.replace(insert_before, replacement, 1) elif hasattr(debug, 'CURRENT_DIR'): # patch ExceptionReporter.get_traceback_html if this version of Django is using # the file system templates rather than the ones in code # This code comes from: # https://github.com/django/django/blob/d79cf1e9e2887aa12567c8f27e384195253cb847/django/views/debug.py#L329,L334 # There are theoretical issues with the code below, for example t.render could throw because # t might be None, but this is the code from Django from pathlib import Path from django.template import Context def new_get_traceback_html(exception_reporter): """Return HTML version of debug 500 HTTP error page.""" with Path(debug.CURRENT_DIR, 'templates', 'technical_500.html').open() as fh: template_string = fh.read() template_string = template_string.replace(insert_before, replacement, 1) t = debug.DEBUG_ENGINE.from_string(template_string) c = Context(exception_reporter.get_traceback_data(), use_l10n=False) return t.render(c) debug.ExceptionReporter.get_traceback_html = new_get_traceback_html else: # patch ExceptionReporter.get_traceback_html for Django versions 4.0+ def new_get_traceback_html(self): """Return HTML version of debug 500 HTTP error page.""" with self.html_template_path.open(encoding='utf-8') as fh: t = debug.DEBUG_ENGINE.from_string(fh.read()) c = Context(self.get_traceback_data(), use_l10n=False) return t.render(c) if hasattr(debug.ExceptionReporter, '__rollbar__patched'): return # patch ExceptionReporter.get_traceback_data old_get_traceback_data = debug.ExceptionReporter.get_traceback_data def new_get_traceback_data(exception_reporter): data = old_get_traceback_data(exception_reporter) try: item_uuid = exception_reporter.request.META.get('rollbar.uuid') if item_uuid: url = '%s/item/uuid/?uuid=%s' % (rollbar_web_base, item_uuid) data['view_in_rollbar_url'] = url except: log.exception("Exception while adding view-in-rollbar link to technical_500_template.") return data debug.ExceptionReporter.get_traceback_data = new_get_traceback_data debug.ExceptionReporter.__rollbar__patched = True def _should_ignore_404(url): url_patterns = getattr(settings, 'ROLLBAR', {}).get('ignorable_404_urls', ()) return any(p.search(url) for p in url_patterns) def _apply_sensitive_post_params(request): sensitive_post_parameters = getattr( request, "sensitive_post_parameters", [] ) if not sensitive_post_parameters: return mutable = request.POST._mutable request.POST._mutable = True if sensitive_post_parameters == "__ALL__": for param in request.POST: request.POST[param] = "******" return for param in sensitive_post_parameters: if param in request.POST: request.POST[param] = "******" request.POST._mutable = mutable class RollbarNotifierMiddleware(MiddlewareMixin): def __init__(self, get_response=None): super(RollbarNotifierMiddleware, self).__init__(get_response) self.settings = getattr(settings, 'ROLLBAR', {}) if not self.settings.get('access_token'): raise MiddlewareNotUsed if not self._get_setting('enabled'): raise MiddlewareNotUsed self._ensure_log_handler() kw = self.settings.copy() access_token = kw.pop('access_token') environment = kw.pop('environment', 'development' if settings.DEBUG else 'production') kw.setdefault('exception_level_filters', DEFAULTS['exception_level_filters']) # ignorable_404_urls is only relevant for this middleware not as an argument to init kw.pop('ignorable_404_urls', None) rollbar.init(access_token, environment, **kw) def hook(request, data): try: # try django 1.5 method for getting url_name url_name = request.resolver_match.url_name except: # fallback to older method try: url_name = resolve(request.path_info).url_name except: url_name = None if url_name: data['context'] = url_name data['framework'] = 'django' if request and hasattr(request, 'META'): request.META['rollbar.uuid'] = data['uuid'] rollbar.BASE_DATA_HOOK = hook # monkeypatch debug module if self._get_setting('patch_debugview'): try: _patch_debugview(self._get_setting('web_base')) except Exception as e: log.error( "Rollbar - unable to monkeypatch debugview to add 'View in Rollbar' link." " To disable, set `ROLLBAR['patch_debugview'] = False` in settings.py." " Exception was: %r", e ) def __call__(self, request): headers = {} for k, v in request.META.items(): if k.startswith('HTTP_'): header_name = '-'.join(k[len('HTTP_'):].replace('_', ' ').title().split(' ')) headers[header_name] = v set_current_session(headers) try: response = self.get_response(request) return response finally: reset_current_session() def _ensure_log_handler(self): """ If there's no log configuration, set up a default handler. """ if log.handlers: return handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s') handler.setFormatter(formatter) log.addHandler(handler) def _get_setting(self, name, default=None): try: return self.settings[name] except KeyError: if name in DEFAULTS: default_val = DEFAULTS[name] if hasattr(default_val, '__call__'): return default_val() return default_val return default def get_extra_data(self, request, exc): return def get_payload_data(self, request, exc): return def process_response(self, request, response): return response def process_exception(self, request, exc): if isinstance(exc, Http404) and _should_ignore_404(request.get_full_path()): return _apply_sensitive_post_params(request) rollbar.report_exc_info( sys.exc_info(), request, extra_data=self.get_extra_data(request, exc), payload_data=self.get_payload_data(request, exc), ) class RollbarNotifierMiddlewareOnly404(MiddlewareMixin): def get_extra_data(self, request, exc): return def get_payload_data(self, request, exc): return def process_response(self, request, response): if response.status_code != 404: return response if _should_ignore_404(request.get_full_path()): return response try: if hasattr(request, '_rollbar_notifier_original_http404_exc_info'): exc_type, exc_value, exc_traceback = request._rollbar_notifier_original_http404_exc_info if exc_value is None: exc_value = Http404() raise exc_value.with_traceback(exc_traceback) else: raise Http404() except Exception as exc: _apply_sensitive_post_params(request) rollbar.report_exc_info( sys.exc_info(), request, extra_data=self.get_extra_data(request, exc), payload_data=self.get_payload_data(request, exc), ) return response class RollbarNotifierMiddlewareExcluding404(RollbarNotifierMiddleware): def process_exception(self, request, exc): if isinstance(exc, Http404): request._rollbar_notifier_original_http404_exc_info = sys.exc_info() else: super(RollbarNotifierMiddlewareExcluding404, self).process_exception(request, exc) ================================================ FILE: rollbar/contrib/django/models.py ================================================ """ No models - this file is here so django recognizes this as an application for running unit tests. """ ================================================ FILE: rollbar/contrib/django/tests.py ================================================ """ Unit tests """ from django.test import TestCase from django.conf import settings class BasicTests(TestCase): def test_configuration(self): """ Test that the configuration is sane. """ self.assertTrue('ROLLBAR' in dir(settings), msg='The ROLLBAR setting is not present.') self.assertTrue(settings.ROLLBAR.get('access_token'), msg='The ROLLBAR["access_token"] setting is blank.') ================================================ FILE: rollbar/contrib/django/utils.py ================================================ class MiddlewareMixin(object): def __init__(self, get_response=None): super(MiddlewareMixin, self).__init__() ================================================ FILE: rollbar/contrib/django_rest_framework/__init__.py ================================================ try: from django.core.exceptions import ImproperlyConfigured except ImportError: ImproperlyConfigured = RuntimeError try: from rest_framework.views import exception_handler as _exception_handler except (ImportError, ImproperlyConfigured): _exception_handler = None def post_exception_handler(exc, context): # This is to be used with the Django REST Framework (DRF) as its # global exception handler. It replaces the POST data of the Django # request with the parsed data from the DRF. This is necessary # because we cannot read the request data/stream more than once. # This will allow us to see the parsed POST params in the rollbar # exception log. if _exception_handler is None: raise ImproperlyConfigured( 'Could not import rest_framework.views.exception_handler') try: context['request']._request.POST = context['request'].data except Exception: pass return _exception_handler(exc, context) ================================================ FILE: rollbar/contrib/fastapi/__init__.py ================================================ __all__ = ['add_to', 'ReporterMiddleware', 'LoggerMiddleware', 'get_current_request'] # Optional requirements: # # - FastAPI requires `python-multipart` package to support requests body parsing # - `LoggerMiddleware` and `get_current_request` require `aiocontextvars` package # to be installed when running in Python 3.6 from .middleware import ReporterMiddleware from .logger import LoggerMiddleware from .routing import add_to # Do not modify the returned request object from rollbar.contrib.starlette import get_current_request ================================================ FILE: rollbar/contrib/fastapi/logger.py ================================================ __all__ = ['LoggerMiddleware'] from fastapi import __version__ from rollbar.contrib.asgi.integration import integrate from rollbar.contrib.starlette import LoggerMiddleware as StarletteLoggerMiddleware @integrate(framework_name=f'fastapi {__version__}') class LoggerMiddleware(StarletteLoggerMiddleware): ... ================================================ FILE: rollbar/contrib/fastapi/middleware.py ================================================ __all__ = ['ReporterMiddleware'] from fastapi import __version__ from rollbar.contrib.asgi.integration import integrate from rollbar.contrib.starlette import ReporterMiddleware as StarletteReporterMiddleware @integrate(framework_name=f'fastapi {__version__}') class ReporterMiddleware(StarletteReporterMiddleware): ... ================================================ FILE: rollbar/contrib/fastapi/routing.py ================================================ __all__ = ['add_to'] import logging import sys from typing import Callable, Optional, Type, Union from fastapi import APIRouter, FastAPI, __version__ from fastapi.routing import APIRoute from rollbar.lib.session import reset_current_session, set_current_session try: from fastapi import Request, Response except ImportError: # Added in FastAPI v0.51.0 from starlette.requests import Request from starlette.responses import Response import rollbar from .utils import fastapi_min_version, get_installed_middlewares, has_bare_routing from rollbar.contrib.asgi.integration import integrate from rollbar.contrib.starlette.requests import store_current_request from rollbar.lib._async import RollbarAsyncError, try_report log = logging.getLogger(__name__) @fastapi_min_version('0.41.0') @integrate(framework_name=f'fastapi {__version__}') def add_to(app_or_router: Union[FastAPI, APIRouter]) -> Optional[Type[APIRoute]]: """ Adds RollbarLoggingRoute handler to the router app. This is the recommended way for integration with FastAPI. Alternatively to using middleware, the handler may fill more data in the payload (e.g. request body). app_or_router: FastAPI app or router Note: The route handler must be added before adding user routes Requirements: FastAPI v0.41.0+ Example usage: from fastapi import FastAPI from rollbar.contrib.fastapi import add_to as rollbar_add_to app = FastAPI() rollbar_add_to(app) """ if not has_bare_routing(app_or_router): log.error( 'RollbarLoggingRoute must to be added to a bare router' ' (before adding routes). See docs for more details.' ) return None installed_middlewares = get_installed_middlewares(app_or_router) if installed_middlewares: log.warning( f'Detected middleware installed {installed_middlewares}' ' while loading Rollbar route handler.' ' This can cause in duplicate occurrences.' ) if isinstance(app_or_router, FastAPI): _add_to_app(app_or_router) elif isinstance(app_or_router, APIRouter): _add_to_router(app_or_router) else: log.error('Error adding RollbarLoggingRoute to application.') return None return RollbarLoggingRoute class RollbarLoggingRoute(APIRoute): def get_route_handler(self) -> Callable: router_handler = super().get_route_handler() async def rollbar_route_handler(request: Request) -> Response: set_current_session(dict(request.headers)) try: store_current_request(request) return await router_handler(request) except Exception: # FastAPI requires the `python-multipart` package to parse the content if not request._stream_consumed: await request.body() await request.form() exc_info = sys.exc_info() try: await try_report(exc_info, request) except RollbarAsyncError: log.warning( 'Failed to report asynchronously. Trying to report synchronously.' ) rollbar.report_exc_info(exc_info, request) raise finally: reset_current_session() return rollbar_route_handler def _add_to_app(app): app.router.route_class = RollbarLoggingRoute def _add_to_router(router): router.route_class = RollbarLoggingRoute ================================================ FILE: rollbar/contrib/fastapi/utils.py ================================================ import functools import logging from typing import Union import fastapi from fastapi import APIRouter, FastAPI from . import ReporterMiddleware as FastAPIReporterMiddleware from rollbar.contrib.starlette import ReporterMiddleware as StarletteReporterMiddleware from rollbar.contrib.asgi import ReporterMiddleware as ASGIReporterMiddleware log = logging.getLogger(__name__) class FastAPIVersionError(Exception): def __init__(self, version, reason=''): err_msg = f'FastAPI {version}+ is required' if reason: err_msg += f' {reason}' log.error(err_msg) return super().__init__(err_msg) def is_current_version_higher_or_equal(current_version, min_version): """ Compare two version strings and return True if the current version is higher or equal to the minimum version. Note: This function only compares the release segment of the version string. """ def parse_version(version): """Parse the release segment of a version string into a list of strings.""" parsed = [''] current_segment = 0 for c in version: if c.isdigit(): parsed[current_segment] += c elif c == '.': current_segment += 1 parsed.append('') else: break if parsed[-1] == '': parsed.pop() return parsed current = tuple(map(int, parse_version(current_version))) minimum = tuple(map(int, parse_version(min_version))) return current >= minimum class fastapi_min_version: def __init__(self, min_version): self.min_version = min_version def __call__(self, func): @functools.wraps(func) def wrapper(*args, **kwargs): if not is_current_version_higher_or_equal( fastapi.__version__, self.min_version, ): raise FastAPIVersionError( self.min_version, reason=f'to use {func.__name__}() function' ) return func(*args, **kwargs) return wrapper def get_installed_middlewares(app): candidates = ( FastAPIReporterMiddleware, StarletteReporterMiddleware, ASGIReporterMiddleware, ) middlewares = [] if hasattr(app, 'user_middleware'): # FastAPI v0.51.0+ middlewares = [ middleware.cls for middleware in app.user_middleware if middleware.cls in candidates ] elif hasattr(app, 'error_middleware'): middleware = app.error_middleware while hasattr(middleware, 'app'): if isinstance(middleware, candidates): middlewares.append(middleware) middleware = middleware.app middlewares = [middleware.__class__ for middleware in middlewares] return middlewares def has_bare_routing(app_or_router: Union[FastAPI, APIRouter]): if not isinstance(app_or_router, (FastAPI, APIRouter)): return False urls = [ getattr(app_or_router, 'openapi_url', None), getattr(app_or_router, 'docs_url', None), getattr(app_or_router, 'redoc_url', None), getattr(app_or_router, 'swagger_ui_oauth2_redirect_url', None), ] for route in app_or_router.routes: if route is None or route.path in urls: continue return False return True ================================================ FILE: rollbar/contrib/flask/__init__.py ================================================ """ Integration with Flask """ from flask import request, got_request_exception, Flask import rollbar from rollbar.lib.session import reset_current_session, set_current_session def report_exception(app, exception): rollbar.report_exc_info(request=request) def _hook(request, data): data['framework'] = 'flask' if request: data['context'] = str(request.url_rule) rollbar.BASE_DATA_HOOK = _hook def init(app: Flask, access_token, environment='production', scrub_fields=None, url_fields=None, **kw): """ Initializes the Rollbar Flask integration. """ with app.app_context(): rollbar.init( access_token=access_token, environment=environment, scrub_fields=scrub_fields, url_fields=url_fields, **kw, ) # send exceptions from `app` to rollbar, using flask's signal system. got_request_exception.connect(report_exception, app) @app.before_request def before_request(): set_current_session(dict(request.headers)) @app.teardown_request def teardown_request(exception): reset_current_session() ================================================ FILE: rollbar/contrib/pyramid/__init__.py ================================================ """ Plugin for Pyramid apps to submit errors to Rollbar """ import logging import sys from pyramid.httpexceptions import WSGIHTTPException from pyramid.tweens import EXCVIEW from pyramid.util import DottedNameResolver from pyramid.settings import asbool import rollbar from rollbar import set_current_session from rollbar.lib.session import reset_current_session DEFAULT_WEB_BASE = 'https://rollbar.com' BOOLEAN_SETTINGS = [ 'rollbar.enabled', 'rollbar.allow_logging_basic_config', 'rollbar.verify_https' ] log = logging.getLogger(__name__) EXCEPTION_BLOCKLIST = (WSGIHTTPException,) EXCEPTION_SAFELIST = tuple() def handle_error(request, exception, exc_info): if( isinstance(exception, EXCEPTION_BLOCKLIST) and not isinstance(exception, EXCEPTION_SAFELIST) ): return rollbar.report_exc_info(exc_info, request) def parse_settings(settings): prefix = 'rollbar.' out = {} for k, v in settings.items(): if k.startswith(prefix): if k in BOOLEAN_SETTINGS: v = asbool(v) out[k[len(prefix):]] = v return out def rollbar_tween_factory(pyramid_handler, registry): settings = parse_settings(registry.settings) def rollbar_tween(request): set_current_session(dict(request.headers)) # for testing out the integration try: if (settings.get('allow_test', 'true') == 'true' and request.GET.get('pyramid_rollbar_test') == 'true'): try: raise Exception("pyramid_rollbar test exception") except Exception as exc: handle_error(request, exc, sys.exc_info()) except: log.exception("Error in pyramid_rollbar_test block") try: response = pyramid_handler(request) except Exception as exc: handle_error(request, exc, sys.exc_info()) raise finally: reset_current_session() if request.exception is not None: handle_error(request, request.exception, request.exc_info) return response return rollbar_tween def patch_debugtoolbar(settings): """ Patches the pyramid_debugtoolbar (if installed) to display a link to the related rollbar item. """ try: from pyramid_debugtoolbar import tbtools except ImportError: return rollbar_web_base = settings.get('rollbar.web_base', DEFAULT_WEB_BASE) if rollbar_web_base.endswith('/'): rollbar_web_base = rollbar_web_base[:-1] def insert_rollbar_console(request, html): # insert after the closing item_uuid = request.environ.get('rollbar.uuid') if not item_uuid: return html url = '%s/item/uuid/?uuid=%s' % (rollbar_web_base, item_uuid) link = 'View in Rollbar' % url new_data = "

Rollbar: %s

" % link insertion_marker = "" replacement = insertion_marker + new_data return html.replace(insertion_marker, replacement, 1) # patch tbtools.Traceback.render_full old_render_full = tbtools.Traceback.render_full def new_render_full(self, request, *args, **kw): html = old_render_full(self, request, *args, **kw) return insert_rollbar_console(request, html) tbtools.Traceback.render_full = new_render_full def includeme(config): """ Pyramid entry point """ settings = config.registry.settings config.add_tween('rollbar.contrib.pyramid.rollbar_tween_factory', over=EXCVIEW) # run patch_debugtoolbar, unless they disabled it if asbool(settings.get('rollbar.patch_debugtoolbar', True)): patch_debugtoolbar(settings) def hook(request, data): data['framework'] = 'pyramid' if request: request.environ['rollbar.uuid'] = data['uuid'] if request.matched_route: data['context'] = request.matched_route.name rollbar.BASE_DATA_HOOK = hook kw = parse_settings(settings) access_token = kw.pop('access_token') environment = kw.pop('environment', 'production') if kw.get('scrub_fields'): kw['scrub_fields'] = {str.strip(x) for x in kw.get('scrub_fields').split('\n') if x} if kw.get('exception_level_filters'): r = DottedNameResolver() exception_level_filters = [] for line in kw.get('exception_level_filters').split('\n'): if line: dotted_path, level = line.split() try: cls = r.resolve(dotted_path) exception_level_filters.append((cls, level)) except ImportError: log.error('Could not import %r' % dotted_path) kw['exception_level_filters'] = exception_level_filters kw['enabled'] = asbool(kw.get('enabled', True)) rollbar.init(access_token, environment, **kw) def create_rollbar_middleware(app, global_config=None, **kw): access_token = kw.pop('access_token') environment = kw.pop('environment', 'production') rollbar.init(access_token, environment, **kw) return RollbarMiddleware(global_config or {}, app) class RollbarMiddleware(object): def __init__(self, settings, app): self.settings = settings self.app = app def __call__(self, environ, start_resp): try: return self.app(environ, start_resp) except Exception as exc: from pyramid.request import Request handle_error(Request(environ), exc, sys.exc_info()) raise ================================================ FILE: rollbar/contrib/quart/__init__.py ================================================ """ Integration with Quart """ from quart import request import rollbar def report_exception(app, exception): rollbar.report_exc_info(request=request) def _hook(request, data): data['framework'] = 'quart' if request: data['context'] = str(request.url_rule) rollbar.BASE_DATA_HOOK = _hook ================================================ FILE: rollbar/contrib/rq/__init__.py ================================================ """ Exception handler hook for RQ (http://python-rq.org/) How to use: 1. Instead of using the default "rqworker" script to run the worker, write your own short script as shown in this example: https://github.com/nvie/rq/blob/master/examples/run_worker.py 2. In this script, initialize rollbar with `handler='blocking'`, for example: rollbar.init('your access token', 'production', handler='blocking') 3. After constructing the worker but before calling `.work()`, add `rollbar.contrib.rq.exception_handler` as an exception handler. Full example: ``` import rollbar from rq import Connection, Queue, Worker if __name__ == '__main__': rollbar.init('your_access_token', 'production', handler='blocking') with Connection(): q = Queue() worker = Worker(q) worker.push_exc_handler(rollbar.contrib.rq.exception_handler) worker.work() ``` """ import rollbar def exception_handler(job, *exc_info): """ Called by RQ when there is a failure in a worker. NOTE: Make sure that in your RQ worker process, rollbar.init() has been called with handler='blocking'. The default handler, 'thread', does not work from inside an RQ worker. """ # Report data about the job with the exception. job_info = job.to_dict() # job_info['data'] is the pickled representation of the job, and doesn't json-serialize well. # repr() works nicely. job_info['data'] = repr(job_info['data']) extra_data = {'job': job_info} payload_data = {'framework': 'rq'} rollbar.report_exc_info(exc_info, extra_data=extra_data, payload_data=payload_data) # continue to the next handler return True ================================================ FILE: rollbar/contrib/starlette/__init__.py ================================================ __all__ = ['ReporterMiddleware', 'LoggerMiddleware', 'get_current_request'] # Optional requirements: # # - Starlette requires `python-multipart` package to support requests body parsing # - `LoggerMiddleware` and `get_current_request` require `aiocontextvars` package # to be installed when running in Python 3.6 from .middleware import ReporterMiddleware from .logger import LoggerMiddleware # Do not modify the returned request object from .requests import get_current_request ================================================ FILE: rollbar/contrib/starlette/logger.py ================================================ __all__ = ['LoggerMiddleware'] import logging import sys from starlette import __version__ from starlette.types import ASGIApp, Receive, Scope, Send from rollbar.contrib.asgi import ReporterMiddleware as ASGIReporterMiddleware from rollbar.contrib.asgi.integration import integrate from rollbar.contrib.starlette.requests import store_current_request log = logging.getLogger(__name__) @integrate(framework_name=f'starlette {__version__}') class LoggerMiddleware(ASGIReporterMiddleware): def __init__(self, app: ASGIApp) -> None: if sys.version_info < (3, 6): log.error( 'LoggerMiddleware requires Python 3.7+ (or 3.6 with `aiocontextvars` package)' ) raise RuntimeError( 'LoggerMiddleware requires Python 3.7+ (or 3.6 with `aiocontextvars` package)' ) super().__init__(app) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: store_current_request(scope, receive) await self.app(scope, receive, send) ================================================ FILE: rollbar/contrib/starlette/middleware.py ================================================ import logging import sys from starlette import __version__ from starlette.requests import Request from starlette.types import Receive, Scope, Send import rollbar from .requests import store_current_request from rollbar.contrib.asgi import ReporterMiddleware as ASGIReporterMiddleware from rollbar.contrib.asgi.integration import integrate from rollbar.lib._async import RollbarAsyncError, try_report from rollbar.lib.session import set_current_session, reset_current_session log = logging.getLogger(__name__) @integrate(framework_name=f'starlette {__version__}') class ReporterMiddleware(ASGIReporterMiddleware): async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope['type'] == 'http': set_current_session(self._format_headers(scope['headers'])) try: store_current_request(scope, receive) await self.app(scope, receive, send) except Exception: if scope['type'] == 'http': request = Request(scope, receive) # Consuming the request body in Starlette middleware is problematic. # See: https://github.com/encode/starlette/issues/495#issuecomment-494008175 # # Uncomment lines below if you know the risks. # # Starlette requires the `python-multipart` package to parse the content # await request.body() # await request.form() exc_info = sys.exc_info() try: await try_report(exc_info, request) except RollbarAsyncError: log.warning( 'Failed to report asynchronously. Trying to report synchronously.' ) rollbar.report_exc_info(exc_info, request) raise finally: if scope['type'] == 'http': reset_current_session() ================================================ FILE: rollbar/contrib/starlette/requests.py ================================================ __all__ = ['get_current_request'] import logging import sys from typing import Optional, Union from starlette.requests import Request from starlette.types import Receive, Scope log = logging.getLogger(__name__) if sys.version_info[:2] == (3, 6): # Backport PEP 567 try: import aiocontextvars except ImportError: # Do not raise an exception as the module is exported to package API # but is still optional log.error( 'Python 3.6 requires `aiocontextvars` package to be installed' ' to support global access to request objects' ) try: from contextvars import ContextVar except ImportError: ContextVar = None if ContextVar: _current_request: ContextVar[Optional[Request]] = ContextVar( 'rollbar-request-object', default=None ) def get_current_request() -> Optional[Request]: """ Return current request. Do NOT modify the returned request object. """ if ContextVar is None: log.error( 'Python 3.7+ (or aiocontextvars package)' ' is required to receive current request.' ) return None return _current_request.get() def store_current_request( request_or_scope: Union[Request, Scope], receive: Optional[Receive] = None ) -> Optional[Request]: if ContextVar is None: return None if receive is None: request = request_or_scope elif request_or_scope['type'] == 'http': request = Request(request_or_scope, receive) else: request = None _current_request.set(request) return request def hasuser(request: Request) -> bool: try: return hasattr(request, 'user') except AssertionError: return False ================================================ FILE: rollbar/examples/asgi/app.py ================================================ #!/usr/bin/env python # ASGI middleware is ASGI v3 compliant. It can integrate with any # ASGIv3-compliant applications. # This example demonstrates Rollbar integration with the Starlette app. # # NOTE: Rollbar provides dedicated framework integrations that are more # feature-rich. Currently available: Starlette and FastAPI. # # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # Optional asynchronous reporting requires HTTPX package to be installed. # # Run: python app.py import rollbar import uvicorn from rollbar.contrib.asgi import ReporterMiddleware as RollbarMiddleware from starlette.applications import Starlette from starlette.responses import PlainTextResponse # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx ) # Integrate Rollbar with Starlette application app = Starlette() app.add_middleware(RollbarMiddleware) # should be added as the first middleware # Verify application runs correctly # # $ curl http://localhost:8888 @app.route('/') async def root(request): return PlainTextResponse('hello world') # Cause an uncaught exception to be sent to Rollbar # GET query params will be sent to Rollbar and available in the UI # # $ curl http://localhost:8888/error?param1=hello¶m2=world async def localfunc(arg1, arg2, arg3): # Both local variables and function arguments will be sent to Rollbar # and available in the UI localvar = 'local variable' cause_error_with_local_variables @app.route('/error') async def error(request): await localfunc('func_arg1', 'func_arg2', 1) return PlainTextResponse("You shouldn't be seeing this") if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/django/app.py ================================================ import os import sys import django from django.conf import settings # use settings compatible with the installed Django version # v1.10+ requires MIDDLEWARE keyname # older versions require MIDDLEWARE_CLASSES keyname ROLLBAR_CONFIG = { 'access_token': 'POST_SERVER_ITEM_ACCESS_TOKEN', 'environment': 'development', 'branch': 'master', 'root': os.getcwd() } MIDDLEWARE_CONFIG = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # Rollbar middleware 'rollbar.contrib.django.middleware.RollbarNotifierMiddleware', ) if django.VERSION >= (1, 10): settings.configure( DEBUG=True, SECRET_KEY='thisisthesecretkey', ROOT_URLCONF=__name__, ROLLBAR = ROLLBAR_CONFIG, MIDDLEWARE = MIDDLEWARE_CONFIG, ) else: settings.configure( DEBUG=True, SECRET_KEY='thisisthesecretkey', ROOT_URLCONF=__name__, ROLLBAR = ROLLBAR_CONFIG, MIDDLEWARE_CLASSES = MIDDLEWARE_CONFIG, ) from django.conf.urls import url from django.http import HttpResponse def index(request): return HttpResponse('Hello World') def error(request): foo() return HttpResponse('You shouldn\'t be seeing this') urlpatterns = ( url(r'^$', index), url(r'^error$', error), ) if __name__ == "__main__": from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) ================================================ FILE: rollbar/examples/fastapi/app.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # Optional asynchronous reporting requires HTTPX package to be installed. # # NOTE: This example requires FastAPI v0.41.0+ (see app_middleware.py for alternative). # # Run: python app.py import fastapi import rollbar import uvicorn from rollbar.contrib.fastapi import add_to as rollbar_add_to # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx include_request_body=True, ) # Integrate Rollbar with FastAPI application before adding routes to the app app = fastapi.FastAPI() rollbar_add_to(app) # Verify application runs correctly # # $ curl http://localhost:8888 @app.get('/') async def read_root(): return {'hello': 'world'} # Cause an uncaught exception to be sent to Rollbar # GET query params will be sent to Rollbar and available in the UI # # $ curl http://localhost:8888/error?param1=hello¶m2=world async def localfunc(arg1, arg2, arg3): # Both local variables and function arguments will be sent to Rollbar # and available in the UI localvar = 'local variable' cause_error_with_local_variables @app.get('/error') async def read_error(): await localfunc('func_arg1', 'func_arg2', 1) return {'result': "You shouldn't be seeing this"} # Cause an uncaught exception to be sent to Rollbar # POST request body will be sent to Rollbar and available in the UI # # curl http://localhost:8888/body -d '{"param1": "hello", "param2": "world"}' @app.post('/body') async def read_body(): cause_error_with_body return {'result': "You shouldn't be seeing this"} # Cause an uncaught exception to be sent to Rollbar # POST form data will be sent to Rollbar and available in the UI # # curl http://localhost:8888/form -F 'param1=hello' -F 'param2=world' @app.post('/form') async def read_form(): cause_error_with_form return {'result': "You shouldn't be seeing this"} if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/fastapi/app_global_request.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # NOTE: Python 3.6 requires aiocontextvars package to be installed. # # Run: python app_global_request.py import fastapi import rollbar import uvicorn from rollbar.contrib.fastapi import LoggerMiddleware # Integrate Rollbar with FastAPI application app = fastapi.FastAPI() app.add_middleware(LoggerMiddleware) # should be added as the last middleware async def get_user_agent(): # Global access to the current request object request = rollbar.get_request() user_agent = request.headers['User-Agent'] return user_agent # $ curl -i http://localhost:8888 @app.get('/') async def read_root(): user_agent = await get_user_agent() return {'user-agent': user_agent} if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/fastapi/app_logger.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # NOTE: Python 3.6 requires aiocontextvars package to be installed. # Optional asynchronous reporting requires HTTPX package to be installed. # # Run: python app_logger.py import logging import fastapi import rollbar import uvicorn from rollbar.contrib.fastapi import LoggerMiddleware from rollbar.logger import RollbarHandler # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx ) # Set root logger to log DEBUG and above logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Report ERROR and above to Rollbar rollbar_handler = RollbarHandler() rollbar_handler.setLevel(logging.ERROR) # Attach Rollbar handler to the root logger logger.addHandler(rollbar_handler) # Integrate Rollbar with FastAPI application app = fastapi.FastAPI() app.add_middleware(LoggerMiddleware) # should be added as the last middleware # GET query params will be sent to Rollbar and available in the UI # $ curl http://localhost:8888?param1=hello¶m2=world @app.get('/') async def read_root(): # Report log entries logger.critical('Critical message sent to Rollbar') logger.error('Error message sent to Rollbar') # Ignore log entries logger.warning('Warning message is not sent to Rollbar') logger.info('Info message is not sent to Rollbar') logger.debug('Debug message is not sent to Rollbar') return {'hello': 'world'} if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/fastapi/app_middleware.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # Optional asynchronous reporting requires HTTPX package to be installed. # # NOTE: FastAPI middlewares don't allow to collect streamed content like a request body. # You may consider to use routing integration instead (see app.py example). # # Run: python app_middleware.py import fastapi import rollbar import uvicorn from rollbar.contrib.fastapi import ReporterMiddleware as RollbarMiddleware # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx ) # Integrate Rollbar with FastAPI application app = fastapi.FastAPI() app.add_middleware(RollbarMiddleware) # should be added as the first middleware # Verify application runs correctly # # $ curl http://localhost:8888 @app.get('/') async def read_root(): return {'hello': 'world'} # Cause an uncaught exception to be sent to Rollbar # GET query params will be sent to Rollbar and available in the UI # # $ curl http://localhost:8888/error?param1=hello¶m2=world async def localfunc(arg1, arg2, arg3): # Both local variables and function arguments will be sent to Rollbar # and available in the UI localvar = 'local variable' cause_error_with_local_variables @app.get('/error') async def read_error(): await localfunc('func_arg1', 'func_arg2', 1) return {'result': "You shouldn't be seeing this"} if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/flask/app.py ================================================ # NOTE: pyrollbar requires both `Flask` and `blinker` packages to be installed first from flask import Flask from flask import got_request_exception import rollbar import rollbar.contrib.flask app = Flask(__name__) with app.app_context(): rollbar.init('ACCESS_TOKEN', environment='development') # send exceptions from `app` to rollbar, using flask's signal system. got_request_exception.connect(rollbar.contrib.flask.report_exception, app) @app.route('/') def root(): foo() return 'Hello World' if __name__ == '__main__': app.run() ================================================ FILE: rollbar/examples/starlette/app.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # Optional asynchronous reporting requires HTTPX package to be installed. # # NOTE: Starlette middlewares don't allow to collect streamed content like a request body. # # Run: python app.py import rollbar import uvicorn from rollbar.contrib.starlette import ReporterMiddleware as RollbarMiddleware from starlette.applications import Starlette from starlette.responses import PlainTextResponse # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx ) # Integrate Rollbar with Starlette application app = Starlette() app.add_middleware(RollbarMiddleware) # should be added as the first middleware # Verify application runs correctly # # $ curl http://localhost:8888 @app.route('/') async def root(request): return PlainTextResponse('hello world') # Cause an uncaught exception to be sent to Rollbar # GET query params will be sent to Rollbar and available in the UI # # $ curl http://localhost:8888/error?param1=hello¶m2=world async def localfunc(arg1, arg2, arg3): # Both local variables and function arguments will be sent to Rollbar # and available in the UI localvar = 'local variable' cause_error_with_local_variables @app.route('/error') async def error(request): await localfunc('func_arg1', 'func_arg2', 1) return PlainTextResponse("You shouldn't be seeing this") if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/starlette/app_global_request.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # NOTE: Python 3.6 requires aiocontextvars package to be installed. # # Run: python app_global_request.py import rollbar import uvicorn from rollbar.contrib.starlette import LoggerMiddleware from starlette.applications import Starlette from starlette.responses import JSONResponse # Integrate Rollbar with Starlette application app = Starlette() app.add_middleware(LoggerMiddleware) # should be added as the last middleware async def get_user_agent(): # Global access to the current request object request = rollbar.get_request() user_agent = request.headers['User-Agent'] return user_agent # $ curl -i http://localhost:8888 @app.route('/') async def root(request): user_agent = await get_user_agent() return JSONResponse({'user-agent': user_agent}) if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/starlette/app_logger.py ================================================ #!/usr/bin/env python # This example uses Uvicorn package that must be installed. However, it can be # replaced with any other ASGI-compliant server. # # NOTE: Python 3.6 requires aiocontextvars package to be installed. # Optional asynchronous reporting requires HTTPX package to be installed. # # Run: python app_logger.py import logging import rollbar import uvicorn from rollbar.contrib.starlette import LoggerMiddleware from rollbar.logger import RollbarHandler from starlette.applications import Starlette from starlette.responses import PlainTextResponse # Initialize Rollbar SDK with your server-side ACCESS_TOKEN rollbar.init( 'ACCESS_TOKEN', environment='staging', handler='async', # For asynchronous reporting use: default, async or httpx ) # Set root logger to log DEBUG and above logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Report ERROR and above to Rollbar rollbar_handler = RollbarHandler() rollbar_handler.setLevel(logging.ERROR) # Attach Rollbar handler to the root logger logger.addHandler(rollbar_handler) # Integrate Rollbar with Starlette application app = Starlette() app.add_middleware(LoggerMiddleware) # should be added as the last middleware # GET query params will be sent to Rollbar and available in the UI # $ curl http://localhost:8888?param1=hello¶m2=world @app.route('/') async def root(request): # Report log entries logger.critical('Critical message sent to Rollbar') logger.error('Error message sent to Rollbar') # Ignore log entries logger.warning('Warning message is not sent to Rollbar') logger.info('Info message is not sent to Rollbar') logger.debug('Debug message is not sent to Rollbar') return PlainTextResponse('hello world') if __name__ == '__main__': uvicorn.run(app, host='localhost', port=8888) ================================================ FILE: rollbar/examples/twisted/simpleserv.py ================================================ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. # # From https://twistedmatrix.com/documents/current/_downloads/simpleserv.py # NOTE: pyrollbar requires both `Twisted` and `treq` packages to be installed from twisted.internet import reactor, protocol import rollbar def bar(p): # These local variables will be sent to Rollbar and available in the UI a = 33 b = a * 5 baz() def foo(): hello = 'world' bar(hello) class Echo(protocol.Protocol): """This is just about the simplest possible protocol""" def dataReceived(self, data): "As soon as any data is received, write it back." # Cause an uncaught exception to be sent to Rollbar foo() self.transport.write(data) def main(): rollbar.init('ACCESS_TOKEN', environment='test', handler='twisted') """This runs the protocol on port 8000""" factory = protocol.ServerFactory() factory.protocol = Echo reactor.listenTCP(8000, factory) reactor.run() # this only runs if the module was *not* imported if __name__ == '__main__': main() ================================================ FILE: rollbar/lib/__init__.py ================================================ import base64 import collections import copy from array import array from collections.abc import Mapping binary_type = bytes integer_types = int number_types = (float, int) string_types = str sequence_types = (Mapping, list, tuple, set, frozenset, array, collections.deque) def force_lower(val): try: return val.lower() except: return str(val).lower() def prefix_match(key, prefixes): if not key: return False for prefix in prefixes: if len(prefix) > len(key): continue if prefix == key[:len(prefix)]: return True return False def key_in(key, canonicals): if not key: return False for c in canonicals: if key_match(key, c): return True return False def key_depth(key, canonicals) -> int: if not key: return 0 for c in canonicals: if key_match(key, c): return len(c) return 0 def key_match(key, canonical): if len(key) < len(canonical): return False for k, c in zip(key, canonical): if '*' == c: continue if c == k: continue return False return True def reverse_list_of_lists(l, apply_each_fn=None): apply_each_fn = apply_each_fn or (lambda x: x) return [reversed([apply_each_fn(x) for x in inner]) for inner in l or []] def build_key_matcher(prefixes_or_suffixes, type='prefix', case_sensitive=False): _prefixes = [] if type == 'prefix': _iter = iter elif type == 'suffix': _iter = reversed else: raise ValueError('type must be either "prefix" or "suffix"') prefixes_or_suffixes = prefixes_or_suffixes or [] for prefix in prefixes_or_suffixes: if case_sensitive: # Copy the list of lists _prefix = list(_iter(prefix)) else: # Lowercase all of the elements _prefix = [force_lower(x) for x in _iter(prefix)] _prefixes.append(_prefix) def matcher(prefix_or_suffix): if case_sensitive: prefix = list(_iter(prefix_or_suffix)) else: prefix = [force_lower(x) for x in _iter(prefix_or_suffix)] return prefix_match(prefix, _prefixes) return matcher def is_builtin_type(obj): return obj.__class__.__module__ in ('__builtin__', 'builtins') # http://www.xormedia.com/recursively-merge-dictionaries-in-python.html def dict_merge(a, b, silence_errors=False): """ Recursively merges dict's. not just simple a['key'] = b['key'], if both a and bhave a key who's value is a dict then dict_merge is called on both values and the result stored in the returned dictionary. """ if not isinstance(b, dict): return b result = a for k, v in b.items(): if k in result and isinstance(result[k], dict): result[k] = dict_merge(result[k], v, silence_errors=silence_errors) else: try: result[k] = copy.deepcopy(v) except Exception as e: if not silence_errors: raise e result[k] = '' % (v,) return result def circular_reference_label(data, ref_key=None): ref = '.'.join([str(x) for x in ref_key]) return '' % (type(data).__name__, ref) def float_nan_label(data): return '' def float_infinity_label(data): if data > 1: return '' else: return '' def unencodable_object_label(data): return '' % (type(data).__name__, base64.b64encode(data).decode('ascii')) def undecodable_object_label(data): return '' % (type(data).__name__, base64.b64encode(data).decode('ascii')) try: from django.utils.functional import SimpleLazyObject except ImportError: SimpleLazyObject = None def defaultJSONEncode(o): if SimpleLazyObject and isinstance(o, SimpleLazyObject): if not o._wrapped: o._setup() return o._wrapped return repr(o) + " is not JSON serializable" ================================================ FILE: rollbar/lib/_async.py ================================================ import asyncio import contextlib import inspect import logging import sys from unittest import mock from urllib.parse import urljoin try: import httpx except ImportError: httpx = None import rollbar from rollbar import DEFAULT_TIMEOUT from rollbar.lib import transport log = logging.getLogger(__name__) ALLOWED_HANDLERS = ( 'async', 'httpx', ) if sys.version_info[:2] == (3, 6): # Backport PEP 567 try: import aiocontextvars except ImportError: log.warning( 'Python3.6 does not provide the `contextvars` module.' ' Some advanced features may not work as expected.' ' Please upgrade Python or install `aiocontextvars`.' ) try: from contextvars import ContextVar except ImportError: ContextVar = None if ContextVar: _ctx_handler = ContextVar('rollbar-handler', default=None) else: _ctx_handler = None class RollbarAsyncError(Exception): ... async def report_exc_info( exc_info=None, request=None, extra_data=None, payload_data=None, level=None, **kw ): """ Asynchronously reports an exception to Rollbar, using exc_info (from calling sys.exc_info()) exc_info: optional, should be the result of calling sys.exc_info(). If omitted, sys.exc_info() will be called here. request: optional, a Starlette, WebOb, Werkzeug-based or Sanic request object. extra_data: optional, will be included in the 'custom' section of the payload payload_data: optional, dict that will override values in the final payload (e.g. 'level' or 'fingerprint') kw: provided for legacy purposes; unused. Example usage: rollbar.init(access_token='YOUR_PROJECT_ACCESS_TOKEN') async def func(): try: do_something() except: await report_exc_info(sys.exc_info(), request, {'foo': 'bar'}, {'level': 'warning'}) """ with AsyncHandler(): try: return await call_later( _report_exc_info( exc_info, request, extra_data, payload_data, level, **kw ) ) except Exception as e: log.exception('Exception while reporting exc_info to Rollbar. %r', e) async def report_message( message, level='error', request=None, extra_data=None, payload_data=None, **kw ): """ Asynchronously reports an arbitrary string message to Rollbar. message: the string body of the message level: level to report at. One of: 'critical', 'error', 'warning', 'info', 'debug' request: the request object for the context of the message extra_data: dictionary of params to include with the message. 'body' is reserved. payload_data: param names to pass in the 'data' level of the payload; overrides defaults. """ with AsyncHandler(): try: return await call_later( _report_message(message, level, request, extra_data, payload_data) ) except Exception as e: log.exception('Exception while reporting message to Rollbar. %r', e) async def _report_exc_info( exc_info=None, request=None, extra_data=None, payload_data=None, level=None, **kw ): return rollbar.report_exc_info( exc_info, request, extra_data, payload_data, level, **kw ) async def _report_message( message, level='error', request=None, extra_data=None, payload_data=None, **kw ): return rollbar.report_message(message, level, request, extra_data, payload_data) async def _post_api_httpx(path, payload_str, access_token=None): headers = {'Content-Type': 'application/json'} if access_token is not None: headers['X-Rollbar-Access-Token'] = access_token else: headers['X-Rollbar-Access-Token'] = rollbar.SETTINGS.get('access_token') proxy_cfg = { 'proxy': rollbar.SETTINGS.get('http_proxy'), 'proxy_user': rollbar.SETTINGS.get('http_proxy_user'), 'proxy_password': rollbar.SETTINGS.get('http_proxy_password'), } proxies = transport._get_proxy_cfg(proxy_cfg) mounts = None if proxies: mounts = { 'http://': httpx.HTTPTransport(proxy=proxies['http']), 'https://': httpx.HTTPTransport(proxy=proxies['https']), } url = urljoin(rollbar.SETTINGS['endpoint'], path) async with httpx.AsyncClient( mounts=mounts, verify=rollbar.SETTINGS.get('verify_https', True) ) as client: resp = await client.post( url, content=payload_str, headers=headers, timeout=rollbar.SETTINGS.get('timeout', DEFAULT_TIMEOUT), ) try: return rollbar._parse_response(path, access_token, payload_str, resp) except Exception as e: log.exception('Exception while posting item %r', e) async def try_report( exc_info=None, request=None, extra_data=None, payload_data=None, level=None, **kw ): current_handler = rollbar.SETTINGS.get('handler') if not (current_handler in ALLOWED_HANDLERS or current_handler == 'default'): raise RollbarAsyncError('No async handler set.') if httpx is None: raise RollbarAsyncError('HTTPX is required') return await report_exc_info( exc_info, request, extra_data, payload_data, level, **kw ) class AsyncHandler: def __init__(self): self.global_handler = None self.token = None def with_ctx_handler(self): if self.global_handler in ALLOWED_HANDLERS: self.token = _ctx_handler.set(self.global_handler) else: log.warning( 'Running coroutines requires async compatible handler. Switching to default async handler.' ) self.token = _ctx_handler.set('async') return _ctx_handler.get() def with_global_handler(self): return self.global_handler def __enter__(self): self.global_handler = rollbar.SETTINGS.get('handler') if _ctx_handler: return self.with_ctx_handler() else: return self.with_global_handler() def __exit__(self, exc_type, exc_value, traceback): if _ctx_handler and self.token: _ctx_handler.reset(self.token) def get_current_handler(): if _ctx_handler is None: return rollbar.SETTINGS.get('handler') handler = _ctx_handler.get() if handler is None: return rollbar.SETTINGS.get('handler') return handler def call_later(coro): if sys.version_info < (3, 7): return asyncio.ensure_future(coro) return asyncio.create_task(coro) # test helpers # TODO: move to rollbar.test.async_helper after migrating from unittest def run(coro): if sys.version_info >= (3, 7): return asyncio.run(coro) assert inspect.iscoroutine(coro) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: return loop.run_until_complete(coro) finally: loop.close() asyncio.set_event_loop(None) def async_receive(message): async def receive(): return message assert message['type'] == 'http.request' return receive async def coroutine(): ... class BareMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): await self.app(scope, receive, send) class FailingTestASGIApp: async def __call__(self, scope, receive, send): await self.app(scope, receive, send) async def app(self, scope, receive, send): raise RuntimeError('Invoked only for testing') # for Python 3.7- compatibility class AsyncMock(mock.MagicMock): async def __call__(self, *args, **kwargs): return super().__call__(*args, **kwargs) ================================================ FILE: rollbar/lib/events.py ================================================ EXCEPTION_INFO = 'exception_info' MESSAGE = 'message' PAYLOAD = 'payload' _event_handlers = { EXCEPTION_INFO: [], MESSAGE: [], PAYLOAD: [] } def _check_type(typ): if typ not in _event_handlers: raise ValueError('Unknown type: %s. Must be one of %s' % (typ, _event_handlers.keys())) def _add_handler(typ, handler_fn, pos): _check_type(typ) pos = pos if pos is not None else -1 handlers = _event_handlers[typ] try: handlers.index(handler_fn) except ValueError: handlers.insert(pos, handler_fn) def _remove_handler(typ, handler_fn): _check_type(typ) handlers = _event_handlers[typ] try: index = handlers.index(handler_fn) handlers.pop(index) except ValueError: pass def _on_event(typ, target, **kw): _check_type(typ) ref = target for handler in _event_handlers[typ]: result = handler(ref, **kw) if result is False: return False ref = result return ref # Add/remove event handlers def add_exception_info_handler(handler_fn, pos=None): _add_handler(EXCEPTION_INFO, handler_fn, pos) def remove_exception_info_handler(handler_fn): _remove_handler(EXCEPTION_INFO, handler_fn) def add_message_handler(handler_fn, pos=None): _add_handler(MESSAGE, handler_fn, pos) def remove_message_handler(handler_fn): _remove_handler(MESSAGE, handler_fn) def add_payload_handler(handler_fn, pos=None): _add_handler(PAYLOAD, handler_fn, pos) def remove_payload_handler(handler_fn): _remove_handler(PAYLOAD, handler_fn) # Event handler processing def on_exception_info(exc_info, **kw): return _on_event(EXCEPTION_INFO, exc_info, **kw) def on_message(message, **kw): return _on_event(MESSAGE, message, **kw) def on_payload(payload, **kw): return _on_event(PAYLOAD, payload, **kw) # Misc def reset(): for handlers in _event_handlers.values(): del handlers[:] ================================================ FILE: rollbar/lib/filters/__init__.py ================================================ from rollbar.lib import events from rollbar.lib.filters.basic import filter_rollbar_ignored_exceptions, filter_by_level def add_builtin_filters(settings): # exc_info filters events.add_exception_info_handler(filter_rollbar_ignored_exceptions) events.add_exception_info_handler(filter_by_level) # message filters events.add_message_handler(filter_by_level) ================================================ FILE: rollbar/lib/filters/basic.py ================================================ def filter_rollbar_ignored_exceptions(exc_info, **kw): _, exc, _ = exc_info if getattr(exc, '_rollbar_ignore', False): return False return exc_info def filter_by_level(target, **kw): if 'level' in kw and kw['level'] == 'ignored': return False return target ================================================ FILE: rollbar/lib/payload.py ================================================ from typing import TypedDict class Attribute(TypedDict): """ Represents the `data.attributes` field in the payload, which is used to store session, execution scope information, and other key-value pairs. """ key: str value: str ================================================ FILE: rollbar/lib/session.py ================================================ from __future__ import annotations import random import threading from contextvars import ContextVar from rollbar.lib.payload import Attribute _context_session: ContextVar[list[Attribute]|None] = ContextVar('rollbar-session', default=None) _thread_session: threading.local = threading.local() def set_current_session(headers: dict[str, str]) -> None: """ Set current session data. The session data should be a dictionary with string keys and string values. """ session_data = parse_session_request_baggage_headers(headers, generate_missing=True) _context_session.set(session_data) _thread_session.data = session_data def get_current_session() -> list[Attribute]: """ Return current session data. Do NOT modify the returned session data. """ session_data = _context_session.get() if session_data is not None: return session_data # Fallback to thread local storage for non-async contexts. return getattr(_thread_session, 'data', None) or [] def reset_current_session() -> None: """ Reset current session data. """ _context_session.set(None) _thread_session.data = None def parse_session_request_baggage_headers(headers: dict, generate_missing: bool = False) -> list[Attribute]: """ Parse the 'baggage' header from the request headers to extract session information. If the 'baggage' header is not present or does not contain the expected keys, a new execution scope ID will be generated and returned as part of the session attributes. :param headers: The request headers as a dictionary. :param generate_missing: If True, generates a new execution scope ID if it's missing from the baggage header. If False, it is not generated. This should only be set to True when the session data is being stored for the first time in the request context. Generally, `set_current_session()`, and `get_current_session()` should be used instead. """ if not headers: if generate_missing: return _build_new_scope_attributes() return [] baggage_header = None # Make sure to handle case-insensitive header keys. for key in headers.keys(): if key.lower() == 'baggage': baggage_header = headers[key] break if not baggage_header: if generate_missing: return _build_new_scope_attributes() return [] baggage_items = baggage_header.split(',') baggage_data = [] has_scope_id = False for item in baggage_items: if '=' not in item: continue key, value = item.split('=', 1) key = key.strip() if key == 'rollbar.session.id': baggage_data.append({'key': 'session_id', 'value': value.strip()}) if key == 'rollbar.execution.scope.id': has_scope_id = True baggage_data.append({'key': 'execution_scope_id', 'value': value.strip()}) if not baggage_data: if generate_missing: return _build_new_scope_attributes() return [] # Always ensure we have an execution scope ID, even if the baggage header is present but doesn't contain it. if not has_scope_id and generate_missing: baggage_data.extend(_build_new_scope_attributes()) return baggage_data def _build_new_scope_attributes() -> list[Attribute]: """ Generates a new value for the `rollbar.execution.scope.id` attribute. """ new_id = _new_scope_id() if new_id is None: return [] return [{'key': 'execution_scope_id', 'value': new_id}] def _new_scope_id() -> str | None: """ Generate a new random ID with 128 bits of randomness, formatted as a 32-character hexadecimal string. To be used as an execution scope ID. """ try: # Generate a random integer with exactly 128 random bits num = random.getrandbits(128) except Exception as e: return None return format(num, "032x") ================================================ FILE: rollbar/lib/thread_pool.py ================================================ import logging import os import sys from concurrent.futures import ThreadPoolExecutor _pool = None # type: ThreadPoolExecutor|None log = logging.getLogger(__name__) def init_pool(max_workers): """ Creates the thread pool with the max workers. :type max_workers: int|None :param max_workers: If max_workers is None it will use the logic from the standard library to calculate the number of threads. However, we ported the logic from Python 3.5 to earlier versions. """ if max_workers is None and sys.version_info < (3, 5): max_workers = (os.cpu_count() or 1) * 5 global _pool _pool = ThreadPoolExecutor(max_workers) def submit(worker, payload_str, access_token): """ Submit a new task to the thread pool. :type worker: function :type payload_str: str :type access_token: str """ global _pool if _pool is None: log.warning('pyrollbar: Thead pool not initialized. Please ensure init_pool() is called prior to submit().') return _pool.submit(worker, payload_str, access_token) ================================================ FILE: rollbar/lib/transform.py ================================================ from typing import Optional class Transform(object): depth_first = True priority = 100 def default(self, o, key=None): return o def transform_circular_reference(self, o, key=None, ref_key=None): # By default, we just perform a no-op for circular references. # Subclasses should implement this method to return whatever representation # for the circular reference they need. return self.default(o, key=key) def transform_tuple(self, o, key=None): return self.default(o, key=key) def transform_namedtuple(self, o, key=None): return self.default(o, key=key) def transform_list(self, o, key=None): return self.default(o, key=key) def transform_dict(self, o, key=None): return self.default(o, key=key) def transform_number(self, o, key=None): return self.default(o, key=key) def transform_bytes(self, o, key=None): return self.default(o, key=key) def transform_unicode(self, o, key=None): return self.default(o, key=key) def transform_boolean(self, o, key=None): return self.default(o, key=key) def transform_path(self, o, key=None): return self.default(str(o), key=key) def transform_custom(self, o, key=None): return self.default(o, key=key) @staticmethod def rollbar_repr(obj: object) -> Optional[str]: r = None if hasattr(obj, '__rollbar_repr__'): r = obj.__rollbar_repr__() if not isinstance(r, str): raise TypeError(f'__rollbar_repr__ returned non-string (type {type(r)})') return r ================================================ FILE: rollbar/lib/transforms/__init__.py ================================================ from collections.abc import Iterable from rollbar.lib import ( binary_type, string_types, number_types, traverse, ) # NOTE: Don't remove this import, it would cause a breaking change to the library's API. # The `Transform` class was moved out of this file to prevent a cyclical dependency issue. from rollbar.lib.transform import Transform from rollbar.lib.transforms.batched import BatchedTransform _ALLOWED_CIRCULAR_REFERENCE_TYPES = [binary_type, bool, type(None)] if isinstance(string_types, tuple): _ALLOWED_CIRCULAR_REFERENCE_TYPES.extend(string_types) else: _ALLOWED_CIRCULAR_REFERENCE_TYPES.append(string_types) if isinstance(number_types, tuple): _ALLOWED_CIRCULAR_REFERENCE_TYPES.extend(number_types) else: _ALLOWED_CIRCULAR_REFERENCE_TYPES.append(number_types) _ALLOWED_CIRCULAR_REFERENCE_TYPES = tuple(_ALLOWED_CIRCULAR_REFERENCE_TYPES) def transform(obj, transforms, key=None, batch_transforms=False): if isinstance(transforms, Transform): transforms = [transforms] if batch_transforms: transforms = [BatchedTransform(transforms)] for transform in transforms: if not isinstance(transform, Transform): continue obj = _transform(obj, transform, key=key) return obj def _transform(obj, transform, key=None): key = key or () def do_transform(type_name, val, key=None, **kw): fn = getattr(transform, "transform_%s" % type_name, transform.transform_custom) val = fn(val, key=key, **kw) return val def string_handler(s, key=None): if isinstance(s, bytes): return do_transform("bytes", s, key=key) elif isinstance(s, str): return do_transform("unicode", s, key=key) def default_handler(o, key=None): if isinstance(o, bool): return do_transform("boolean", o, key=key) # There is a quirk in the current version (1.1.6) of the enum # backport enum34 which causes it to not have the same # behavior as Python 3.4+. One way to identify IntEnums is that # they are instances of numbers but not number types. if isinstance(o, number_types): if type(o) not in number_types: return do_transform("custom", o, key=key) else: return do_transform("number", o, key=key) return do_transform("custom", o, key=key) handlers = { "string_handler": string_handler, "tuple_handler": lambda o, key=None: do_transform("tuple", o, key=key), "namedtuple_handler": lambda o, key=None: do_transform( "namedtuple", o, key=key ), "list_handler": lambda o, key=None: do_transform("list", o, key=key), "set_handler": lambda o, key=None: do_transform("set", o, key=key), "mapping_handler": lambda o, key=None: do_transform("dict", o, key=key), "path_handler": lambda o, key=None: do_transform("path", o, key=key), "circular_reference_handler": lambda o, key=None, ref_key=None: do_transform( "circular_reference", o, key=key, ref_key=ref_key ), "default_handler": default_handler, "allowed_circular_reference_types": _ALLOWED_CIRCULAR_REFERENCE_TYPES, } return traverse.traverse(obj, key=key, depth_first=transform.depth_first, **handlers) __all__ = ["transform", "Transform"] ================================================ FILE: rollbar/lib/transforms/batched.py ================================================ from rollbar.lib.transform import Transform from rollbar.lib import ( number_types, type_info, ) def do_transform(transform, type_name, val, key=None, **kw): fn = getattr(transform, "transform_%s" % type_name, transform.transform_custom) val = fn(val, key=key, **kw) return val def string_handler(transform, s, key=None): if isinstance(s, bytes): return do_transform(transform, "bytes", s, key=key) elif isinstance(s, str): return do_transform(transform, "unicode", s, key=key) def default_handler(transform, o, key=None): if isinstance(o, bool): return do_transform(transform, "boolean", o, key=key) # There is a quirk in the current version (1.1.6) of the enum # backport enum34 which causes it to not have the same # behavior as Python 3.4+. One way to identify IntEnums is that # they are instances of numbers but not number types. if isinstance(o, number_types): if type(o) not in number_types: return do_transform(transform, "custom", o, key=key) else: return do_transform(transform, "number", o, key=key) return do_transform(transform, "custom", o, key=key) handlers = { type_info.STRING: string_handler, type_info.TUPLE: lambda transform, o, key=None: do_transform( transform, "tuple", o, key=key ), type_info.NAMEDTUPLE: lambda transform, o, key=None: do_transform( transform, "namedtuple", o, key=key ), type_info.LIST: lambda transform, o, key=None: do_transform( transform, "list", o, key=key ), type_info.SET: lambda transform, o, key=None: do_transform( transform, "set", o, key=key ), type_info.MAPPING: lambda transform, o, key=None: do_transform( transform, "dict", o, key=key ), type_info.CIRCULAR: lambda transform, o, key=None, ref_key=None: do_transform( transform, "circular_reference", o, key=key, ref_key=ref_key ), type_info.DEFAULT: default_handler, } class BatchedTransform(Transform): def __init__(self, transforms): super(BatchedTransform, self).__init__() self._transforms = transforms def default(self, o, key=None): for transform in self._transforms: node_type = type_info.get_type(o) handler = handlers.get(node_type, handlers.get(type_info.DEFAULT)) o = handler(transform, o, key=key) return o __all__ = ["BatchedTransform"] ================================================ FILE: rollbar/lib/transforms/scrub.py ================================================ import random from rollbar.lib import build_key_matcher from rollbar.lib.transform import Transform class ScrubTransform(Transform): suffix_matcher = None priority = 40 def __init__(self, suffixes=None, redact_char='*', randomize_len=True): super(ScrubTransform, self).__init__() if suffixes is not None and len(suffixes) > 0: self.suffix_matcher = build_key_matcher(suffixes, type='suffix') self.redact_char = redact_char self.randomize_len = randomize_len def in_scrub_fields(self, key): if self.suffix_matcher is None: return False return self.suffix_matcher(key) def redact(self, val): if self.randomize_len: _len = random.randint(3, 20) else: try: _len = len(val) except: _len = len(str(val)) return self.redact_char * _len def default(self, o, key=None): if self.in_scrub_fields(key): return self.redact(o) return o __all__ = ['ScrubTransform'] ================================================ FILE: rollbar/lib/transforms/scrub_redact.py ================================================ from rollbar.lib.transforms.scrub import ScrubTransform class RedactRef(object): pass REDACT_REF = RedactRef() class ScrubRedactTransform(ScrubTransform): priority = 20 def default(self, o, key=None): if o is REDACT_REF: return self.redact(o) return super(ScrubRedactTransform, self).default(o, key=key) __all__ = ['ScrubRedactTransform'] ================================================ FILE: rollbar/lib/transforms/scruburl.py ================================================ import re from urllib.parse import urlsplit, urlencode, urlunsplit, parse_qs from rollbar.lib import string_types, binary_type from rollbar.lib.transforms.scrub import ScrubTransform _starts_with_auth_re = re.compile(r'^[a-zA-Z0-9-_]*(:[^@/]+)?@') class ScrubUrlTransform(ScrubTransform): priority = 50 def __init__(self, suffixes=None, scrub_username=False, scrub_password=True, params_to_scrub=None, redact_char='-', randomize_len=True): super(ScrubUrlTransform, self).__init__(suffixes=suffixes, redact_char=redact_char, randomize_len=randomize_len) self.scrub_username = scrub_username self.scrub_password = scrub_password self.params_to_scrub = {x.lower() for x in params_to_scrub or []} def in_scrub_fields(self, key): # Returning True here because we want to scrub URLs out of # every string, not just ones that we know the key for. return True def redact(self, url_string): _redact = super(ScrubUrlTransform, self).redact missing_colon_double_slash = False if _starts_with_auth_re.match(url_string): missing_colon_double_slash = True url_string = '//%s' % url_string try: url_parts = urlsplit(url_string) qs_params = parse_qs(url_parts.query, keep_blank_values=True) except: # This isn't a URL, return url_string which is a no-op # for this transform return url_string netloc = url_parts.netloc # If there's no netloc, give up if not netloc: return url_string for qs_param, vals in qs_params.items(): if qs_param.lower() in self.params_to_scrub: vals2 = [_redact(x) for x in vals] qs_params[qs_param] = vals2 scrubbed_qs = urlencode(qs_params, doseq=True) if self.scrub_username and url_parts.username: redacted_username = _redact(url_parts.username) netloc = netloc.replace(url_parts.username, redacted_username) if self.scrub_password and url_parts.password: redacted_pw = _redact(url_parts.password) netloc = netloc.replace(url_parts.password, redacted_pw) scrubbed_url = (url_parts.scheme, netloc, url_parts.path, scrubbed_qs, url_parts.fragment) scrubbed_url_string = urlunsplit(scrubbed_url) if missing_colon_double_slash: scrubbed_url_string = scrubbed_url_string.lstrip('://') return scrubbed_url_string def default(self, o, key=None): # Change the default behavior because we are only interested # in scrubbing strings. if isinstance(o, string_types) or isinstance(o, binary_type): return super(ScrubUrlTransform, self).default(o, key=key) return o __all__ = ['ScrubUrlTransform'] ================================================ FILE: rollbar/lib/transforms/serializable.py ================================================ import math from rollbar.lib import binary_type, string_types from rollbar.lib import ( circular_reference_label, float_infinity_label, float_nan_label, undecodable_object_label, unencodable_object_label) from rollbar.lib.transform import Transform class SerializableTransform(Transform): priority = 30 def __init__(self, safe_repr=True, safelist_types=None): super(SerializableTransform, self).__init__() self.safe_repr = safe_repr self.safelist = set(safelist_types or []) def transform_circular_reference(self, o, key=None, ref_key=None): return circular_reference_label(o, ref_key) def transform_namedtuple(self, o, key=None): tuple_dict = o._asdict() transformed_dict = self.transform_dict(tuple_dict, key=key) new_vals = [] for field in tuple_dict: new_vals.append(transformed_dict[field]) return '<%s>' % str(o._make(new_vals)) def transform_number(self, o, key=None): if math.isnan(o): return float_nan_label(o) elif math.isinf(o): return float_infinity_label(o) else: return o def transform_bytes(self, o, key=None): try: o.decode('utf8') except UnicodeDecodeError: return undecodable_object_label(o) else: return repr(o) def transform_unicode(self, o, key=None): try: o.encode('utf8') except UnicodeEncodeError: return unencodable_object_label(o) else: return o def transform_dict(self, o, key=None): ret = {} for k, v in o.items(): if isinstance(k, string_types) or isinstance(k, binary_type): if isinstance(k, bytes): new_k = self.transform_bytes(k) else: new_k = self.transform_unicode(k) else: new_k = str(k) ret[new_k] = v return super(SerializableTransform, self).transform_dict(ret, key=key) def transform_custom(self, o, key=None): if o is None: return None # Best to be very careful when we call user code in the middle of # preparing a stack trace. So we put a try/except around it all. try: # If the object has a __rollbar_repr__() method, use it. custom = Transform.rollbar_repr(o) if custom is not None: return custom if any(filter(lambda x: isinstance(o, x), self.safelist)): try: return repr(o) except TypeError: pass # If self.safe_repr is False, use repr() to serialize the object if not self.safe_repr: try: return repr(o) except TypeError: pass # Otherwise, just use the type name return str(type(o)) except Exception as e: exc_str = '' try: exc_str = str(e) except Exception as e2: exc_str = '[%s while calling str(%s)]' % ( e2.__class__.__name__, e.__class__.__name__) return '<%s in %s.__repr__: %s>' % ( e.__class__.__name__, o.__class__.__name__, exc_str) __all__ = ['SerializableTransform'] ================================================ FILE: rollbar/lib/transforms/shortener.py ================================================ from array import array import collections import itertools import reprlib from collections.abc import Mapping from typing import Union, Tuple from rollbar.lib import ( integer_types, key_in, key_depth, sequence_types, string_types) from rollbar.lib.transform import Transform _type_name_mapping = { 'string': string_types, 'long': integer_types, 'mapping': Mapping, 'list': list, 'tuple': tuple, 'set': set, 'frozenset': frozenset, 'array': array, 'deque': collections.deque, 'other': None } def _max_left_right(max_len: int, seperator_len: int) -> Tuple[int, int]: left = max(0, (max_len-seperator_len)//2) right = max(0, max_len-seperator_len-left) return left, right def shorten_array(obj: array, max_len: int) -> array: if len(obj) <= max_len: return obj return obj[:max_len] def shorten_bytes(obj: bytes, max_len: int) -> bytes: if len(obj) <= max_len: return obj return obj[:max_len] def shorten_deque(obj: collections.deque, max_len: int) -> collections.deque: if len(obj) <= max_len: return obj return collections.deque(itertools.islice(obj, max_len)) def shorten_frozenset(obj: frozenset, max_len: int) -> frozenset: if len(obj) <= max_len: return obj return frozenset([elem for i, elem in enumerate(obj) if i < max_len] + ['...']) def shorten_int(obj: int, max_len: int) -> Union[int, str]: s = repr(obj) if len(s) <= max_len: return obj left, right = _max_left_right(max_len, 3) return s[:left] + '...' + s[len(s)-right:] def shorten_list(obj: list, max_len: int) -> list: if len(obj) <= max_len: return obj return obj[:max_len] + ['...'] def shorten_mapping(obj: Union[dict, Mapping], max_keys: int) -> dict: if len(obj) <= max_keys: return obj return {k: obj[k] for k in itertools.islice(obj.keys(), max_keys)} def shorten_set(obj: set, max_len: int) -> set: if len(obj) <= max_len: return obj return set([elem for i, elem in enumerate(obj) if i < max_len] + ['...']) def shorten_string(obj: str, max_len: int) -> str: if len(obj) <= max_len: return obj left, right = _max_left_right(max_len, 3) return obj[:left] + '...' + obj[len(obj)-right:] def shorten_tuple(obj: tuple, max_len: int) -> tuple: if len(obj) <= max_len: return obj return obj[:max_len] + ('...',) class ShortenerTransform(Transform): depth_first = False priority = 10 def __init__(self, safe_repr=True, keys=None, **sizes): super(ShortenerTransform, self).__init__() self.safe_repr = safe_repr self.keys = keys self._repr = reprlib.Repr() for name, size in sizes.items(): setattr(self._repr, name, size) def _get_max_size(self, obj): for name, _type in _type_name_mapping.items(): # Special case for dicts since we are using collections.abc.Mapping # to provide better type checking for dict-like objects if name == 'mapping': name = 'dict' if _type and isinstance(obj, _type): return getattr(self._repr, 'max%s' % name) return self._repr.maxother def _shorten(self, val): max_size = self._get_max_size(val) if isinstance(val, array): return shorten_array(val, max_size) if isinstance(val, bytes): return shorten_bytes(val, max_size) if isinstance(val, collections.deque): return shorten_deque(val, max_size) if isinstance(val, (dict, Mapping)): return shorten_mapping(val, max_size) if isinstance(val, float): return val if isinstance(val, frozenset): return shorten_frozenset(val, max_size) if isinstance(val, int): return shorten_int(val, max_size) if isinstance(val, list): return shorten_list(val, max_size) if isinstance(val, set): return shorten_set(val, max_size) if isinstance(val, str): return shorten_string(val, max_size) if isinstance(val, tuple): return shorten_tuple(val, max_size) return self._shorten_other(val) def _shorten_other(self, obj): if obj is None: return None # If the object has a __rollbar_repr__() method, use it. custom = Transform.rollbar_repr(obj) if custom is not None: return custom if self.safe_repr: obj = str(obj) return self._repr.repr(obj) def _should_shorten(self, val, key): if not key: return False return key_in(key, self.keys) def _should_drop(self, val, key) -> bool: if not key: return False max_depth = key_depth(key, self.keys) if max_depth == 0: return False return (max_depth + self._repr.maxlevel) <= len(key) def default(self, o, key=None): if self._should_drop(o, key): if isinstance(o, (dict, Mapping)): return {'...': '...'} if isinstance(o, sequence_types): return ['...'] if self._should_shorten(o, key): return self._shorten(o) return super(ShortenerTransform, self).default(o, key=key) __all__ = ['ShortenerTransform'] ================================================ FILE: rollbar/lib/transport.py ================================================ from typing import Optional import requests import threading _local = threading.local() def _session(): if hasattr(_local, 'session'): return _local.session _local.session = requests.Session() return _local.session def _get_proxy_cfg(kw: dict) -> Optional[dict]: proxy = kw.pop('proxy', None) proxy_user = kw.pop('proxy_user', None) proxy_password = kw.pop('proxy_password', None) if proxy and proxy_user and proxy_password: return { 'http': f'http://{proxy_user}:{proxy_password}@{proxy}', 'https': f'http://{proxy_user}:{proxy_password}@{proxy}', } elif proxy: return { 'http': f'http://{proxy}', 'https': f'http://{proxy}', } def configure_pool(**kw): keys = ['pool_connections', 'pool_maxsize', 'max_retries'] args = {k: kw[k] for k in keys if kw.get(k, None) is not None} if len(args) == 0: return https_adapter = requests.adapters.HTTPAdapter(**args) http_adapter = requests.adapters.HTTPAdapter(**args) _session().mount('https://', https_adapter) _session().mount('http://', http_adapter) def post(*args, **kw): proxies = _get_proxy_cfg(kw) return _session().post(*args, proxies=proxies, **kw) def get(*args, **kw): proxies = _get_proxy_cfg(kw) return _session().get(*args, proxies=proxies, **kw) __all__ = ['post', 'get', 'configure_pool'] ================================================ FILE: rollbar/lib/traverse.py ================================================ import logging from pathlib import Path from rollbar.lib import binary_type, string_types, circular_reference_label # NOTE: Don't remove this line of code as it would cause a breaking change # to the library's API. The items imported here were originally in this file # but were moved to a new file for easier use elsewhere. from rollbar.lib.type_info import ( get_type, CIRCULAR, DEFAULT, MAPPING, TUPLE, NAMEDTUPLE, LIST, SET, STRING, PATH, ) log = logging.getLogger(__name__) def _noop_circular(a, **kw): return circular_reference_label(a, ref_key=kw.get("ref_key")) def _noop(a, **_): return a def _noop_tuple(a, **_): return tuple(a) def _noop_namedtuple(a, **_): return a._make(a) def _noop_list(a, **_): return list(a) def _noop_set(a, **_): return set(a) def _noop_mapping(a, **_): return dict(a) def _noop_path(a, **_): return Path(a) _default_handlers = { CIRCULAR: _noop_circular, DEFAULT: _noop, STRING: _noop, TUPLE: _noop_tuple, NAMEDTUPLE: _noop_namedtuple, LIST: _noop_list, SET: _noop_set, PATH: _noop_path, MAPPING: _noop_mapping, } def traverse( obj, key=(), string_handler=_default_handlers[STRING], tuple_handler=_default_handlers[TUPLE], namedtuple_handler=_default_handlers[NAMEDTUPLE], list_handler=_default_handlers[LIST], set_handler=_default_handlers[SET], mapping_handler=_default_handlers[MAPPING], path_handler=_default_handlers[PATH], default_handler=_default_handlers[DEFAULT], circular_reference_handler=_default_handlers[CIRCULAR], allowed_circular_reference_types=None, memo=None, depth_first=True, **custom_handlers ): memo = memo or {} obj_id = id(obj) obj_type = get_type(obj) ref_key = memo.get(obj_id) if ref_key: if not allowed_circular_reference_types or not isinstance( obj, allowed_circular_reference_types ): return circular_reference_handler(obj, key=key, ref_key=ref_key) memo[obj_id] = key kw = { "string_handler": string_handler, "tuple_handler": tuple_handler, "namedtuple_handler": namedtuple_handler, "list_handler": list_handler, "set_handler": set_handler, "mapping_handler": mapping_handler, "path_handler": path_handler, "default_handler": default_handler, "circular_reference_handler": circular_reference_handler, "allowed_circular_reference_types": allowed_circular_reference_types, "memo": memo, "depth_first": depth_first, } kw.update(custom_handlers) try: if obj_type is STRING: return string_handler(obj, key=key) elif obj_type is TUPLE: if depth_first: return tuple_handler( tuple( traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj) ), key=key, ) # Breadth first return tuple(traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(tuple_handler(obj, key=key))) elif obj_type is NAMEDTUPLE: if depth_first: return namedtuple_handler( obj._make( traverse(v, key=key + (k,), **kw) for k, v in obj._asdict().items() ), key=key, ) # Breadth first return obj._make(traverse(v, key=key + (k,), **kw) for k, v in namedtuple_handler(obj, key=key)._asdict().items()) elif obj_type is LIST: if depth_first: return list_handler( [traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)], key=key, ) # Breadth first return [traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(list_handler(obj, key=key))] elif obj_type is SET: if depth_first: return set_handler( {traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(obj)}, key=key, ) # Breadth first return {traverse(elem, key=key + (i,), **kw) for i, elem in enumerate(set_handler(obj, key=key))} elif obj_type is MAPPING: if depth_first: return mapping_handler( {k: traverse(v, key=key + (k,), **kw) for k, v in obj.items()}, key=key, ) # Breadth first return {k: traverse(v, key=key + (k,), **kw) for k, v in mapping_handler(obj, key=key).items()} elif obj_type is PATH: return path_handler(obj, key=key) elif obj_type is DEFAULT: for handler_type, handler in custom_handlers.items(): if isinstance(obj, handler_type): return handler(obj, key=key) except: # use the default handler for unknown object types log.debug( "Exception while traversing object using type-specific " "handler. Switching to default handler.", exc_info=True, ) return default_handler(obj, key=key) __all__ = ["traverse"] ================================================ FILE: rollbar/lib/type_info.py ================================================ from rollbar.lib import binary_type, string_types from collections.abc import Mapping, Sequence, Set from pathlib import Path CIRCULAR = -1 DEFAULT = 0 MAPPING = 1 TUPLE = 2 NAMEDTUPLE = 3 LIST = 4 SET = 5 STRING = 6 PATH = 7 def get_type(obj): if isinstance(obj, (string_types, binary_type)): return STRING if isinstance(obj, Mapping): return MAPPING if isinstance(obj, tuple): if hasattr(obj, "_fields"): return NAMEDTUPLE return TUPLE if isinstance(obj, set): return SET if isinstance(obj, Sequence): return LIST if isinstance(obj, Path): return PATH return DEFAULT __all__ = [ "CIRCULAR", "DEFAULT", "MAPPING", "TUPLE", "NAMEDTUPLE", "LIST", "SET", "STRING", "PATH", "get_type", ] ================================================ FILE: rollbar/logger.py ================================================ """ Hooks for integrating with the python logging framework. Usage: import logging from rollbar.logger import RollbarHandler rollbar.init('ACCESS_TOKEN', 'ENVIRONMENT') logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # report ERROR and above to Rollbar rollbar_handler = RollbarHandler() rollbar_handler.setLevel(logging.ERROR) # attach the handlers to the root logger logger.addHandler(rollbar_handler) """ import logging import threading from logging.config import ConvertingDict, ConvertingList, ConvertingTuple import rollbar # hack to fix backward compatibility in Python3 try: from logging import _checkLevel except ImportError: _checkLevel = lambda lvl: lvl EXCLUDE_RECORD_KEYS = { # Attributes that are disallowed in `logging.Logger.makeRecord` 'asctime', 'message', # Attributes that are used internally by pyrollbar 'extra_data', 'payload_data', 'request', *vars(logging.makeLogRecord({})).keys(), } def resolve_logging_types(obj): if isinstance(obj, (dict, ConvertingDict)): return {k: resolve_logging_types(v) for k, v in obj.items()} elif isinstance(obj, (list, ConvertingList)): return [resolve_logging_types(i) for i in obj] elif isinstance(obj, (tuple, ConvertingTuple)): return tuple(resolve_logging_types(i) for i in obj) return obj class RollbarHandler(logging.Handler): SUPPORTED_LEVELS = set(('debug', 'info', 'warning', 'error', 'critical')) _history = threading.local() def __init__(self, access_token=None, environment=None, level=logging.INFO, history_size=10, history_level=logging.DEBUG, **kw): logging.Handler.__init__(self) if access_token is not None: rollbar.init( access_token, environment, allow_logging_basic_config=False, # a handler shouldn't configure the root logger **resolve_logging_types(kw)) self.notify_level = _checkLevel(level) self.history_size = history_size if history_size > 0: self._history.records = [] self.setHistoryLevel(history_level) def setLevel(self, level): """ Override so we set the effective level for which log records we notify Rollbar about instead of which records we save to the history. """ self.notify_level = _checkLevel(level) def setHistoryLevel(self, level): """ Use this method to determine which records we record history for. Use setLevel() to determine which level we report records to Rollbar for. """ logging.Handler.setLevel(self, level) def emit(self, record): # If the record came from Rollbar's own logger don't report it # to Rollbar if record.name == rollbar.__log_name__: return level = record.levelname.lower() if level not in self.SUPPORTED_LEVELS: return exc_info = record.exc_info extra_data = { 'args': record.args, 'record': { 'created': record.created, 'funcName': record.funcName, 'lineno': record.lineno, 'module': record.module, 'name': record.name, 'pathname': record.pathname, 'process': record.process, 'processName': record.processName, 'relativeCreated': record.relativeCreated, 'thread': record.thread, 'threadName': record.threadName } } | { # include any extras k: v for k, v in vars(record).items() if k not in EXCLUDE_RECORD_KEYS } | getattr(record, 'extra_data', {}) # include historical extra_data payload_data = getattr(record, 'payload_data', {}) self._add_history(record, payload_data) # after we've added the history data, check to see if the # notify level is satisfied if record.levelno < self.notify_level: return # Wait until we know we're going to send a report before trying to # load the request request = getattr(record, "request", None) or rollbar.get_request() # Rather than copy the log record and disable exception and stack trace # formatting, this does the same steps to prepare the log record # as `logging.Formatter.format` does before calling # `logging.Formatter.formatMessage`. formatter = self.formatter or logging._defaultFormatter record.message = record.getMessage() if formatter.usesTime(): record.asctime = formatter.formatTime(record, formatter.datefmt) message = formatter.formatMessage(record) uuid = None try: # when not in an exception handler, exc_info == (None, None, None) if exc_info and exc_info[0]: if record.msg: message_template = { 'body': { 'trace': {'exception': {'description': message}} } } payload_data = rollbar.dict_merge( payload_data, message_template, silence_errors=True) uuid = rollbar.report_exc_info(exc_info, level=level, request=request, extra_data=extra_data, payload_data=payload_data) else: uuid = rollbar.report_message(message, level=level, request=request, extra_data=extra_data, payload_data=payload_data) except: self.handleError(record) else: if uuid: record.rollbar_uuid = uuid def _add_history(self, record, payload_data): if hasattr(self._history, 'records'): records = self._history.records history = list(records[-self.history_size:]) if history: history_data = [self._build_history_data(r) for r in history] payload_data.setdefault('server', {})['history'] = history_data records.append(record) # prune the messages if we have too many self._history.records = list(records[-self.history_size:]) def _build_history_data(self, record): data = {'timestamp': record.created, 'format': record.msg, 'args': record.args} if hasattr(record, 'rollbar_uuid'): data['uuid'] = record.rollbar_uuid return data ================================================ FILE: rollbar/test/__init__.py ================================================ import unittest SNOWMAN = b'\xe2\x98\x83' SNOWMAN_UNICODE = SNOWMAN.decode('utf8') class BaseTest(unittest.TestCase): pass def discover(): loader = unittest.TestLoader() suite = loader.discover(__name__) return suite ================================================ FILE: rollbar/test/asgi_tests/__init__.py ================================================ import sys import unittest def _load_tests(loader, tests, pattern): return unittest.TestSuite() if sys.version_info < (3, 5): load_tests = _load_tests ================================================ FILE: rollbar/test/asgi_tests/test_integration.py ================================================ import unittest import sys from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 5) @unittest.skipUnless(ALLOWED_PYTHON_VERSION, 'ASGI implementation requires Python3.5+') class IntegrationTest(BaseTest): def test_should_integrate_if__integrate_defined(self): from rollbar.contrib.asgi.integration import IntegrationBase called = False # cannot patch local objects class Integration(IntegrationBase): def _integrate(self): nonlocal called called = True self.assertTrue(hasattr(Integration, '_integrate')) obj = Integration() self.assertTrue(called) self.assertTrue(hasattr(obj, '_integrate')) def test_should_not_fail_if__integrate_not_exists(self): from rollbar.contrib.asgi.integration import IntegrationBase class WrongIntegration(IntegrationBase): ... self.assertFalse(hasattr(WrongIntegration, '_integrate')) obj = WrongIntegration() self.assertFalse(hasattr(obj, '_integrate')) ================================================ FILE: rollbar/test/asgi_tests/test_middleware.py ================================================ import copy import importlib import sys from unittest import mock import unittest import rollbar from rollbar.lib._async import AsyncMock from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 5) ASYNC_REPORT_ENABLED = sys.version_info >= (3, 6) @unittest.skipUnless(ALLOWED_PYTHON_VERSION, 'ASGI implementation requires Python3.5+') class ReporterMiddlewareTest(BaseTest): default_settings = copy.deepcopy(rollbar.SETTINGS) def setUp(self): importlib.reload(rollbar) rollbar.SETTINGS = copy.deepcopy(self.default_settings) rollbar.SETTINGS['handler'] = 'async' @mock.patch('rollbar.report_exc_info') def test_should_catch_and_report_errors(self, mock_report): from rollbar.contrib.asgi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run testapp = ReporterMiddleware(FailingTestASGIApp()) with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) self.assertTrue(mock_report.called) args, kwargs = mock_report.call_args self.assertEqual(kwargs, {}) exc_type, exc_value, exc_tb = args[0] self.assertEqual(exc_type, RuntimeError) self.assertIsInstance(exc_value, RuntimeError) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_name_to_payload(self, mock_send_payload, *mocks): import rollbar from rollbar.contrib.asgi.middleware import ReporterMiddleware self.assertIsNone(rollbar.BASE_DATA_HOOK) ReporterMiddleware(None) # invoke integration rollbar.report_exc_info() self.assertTrue(mock_send_payload.called) payload = mock_send_payload.call_args[0][0] self.assertIn('asgi', payload['data']['framework']) @unittest.skipUnless(ASYNC_REPORT_ENABLED, 'Requires Python 3.6+') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_default_handler( self, sync_report_exc_info, async_report_exc_info ): import rollbar from rollbar.contrib.asgi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run rollbar.SETTINGS['handler'] = 'default' testapp = ReporterMiddleware(FailingTestASGIApp()) with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) self.assertTrue(async_report_exc_info.called) self.assertFalse(sync_report_exc_info.called) @unittest.skipUnless(ASYNC_REPORT_ENABLED, 'Requires Python 3.6+') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_any_async_handler( self, sync_report_exc_info, async_report_exc_info ): import rollbar from rollbar.contrib.asgi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run rollbar.SETTINGS['handler'] = 'httpx' testapp = ReporterMiddleware(FailingTestASGIApp()) with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) self.assertTrue(async_report_exc_info.called) self.assertFalse(sync_report_exc_info.called) @unittest.skipUnless(ASYNC_REPORT_ENABLED, 'Requires Python 3.6+') @mock.patch('logging.Logger.warning') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_sync_report_exc_info_if_non_async_handlers( self, sync_report_exc_info, async_report_exc_info, mock_log ): import rollbar from rollbar.contrib.asgi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run rollbar.SETTINGS['handler'] = 'threading' testapp = ReporterMiddleware(FailingTestASGIApp()) with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) self.assertFalse(async_report_exc_info.called) self.assertTrue(sync_report_exc_info.called) mock_log.assert_called_once_with( 'Failed to report asynchronously. Trying to report synchronously.' ) def test_should_support_http_only(self): from rollbar.contrib.asgi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run testapp = ReporterMiddleware(FailingTestASGIApp()) with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) self.assertTrue(mock_report.called) with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'websocket'}, None, None)) self.assertFalse(mock_report.called) def test_should_support_type_hints(self): from rollbar.contrib.asgi.types import Receive, Scope, Send self.assertDictEqual( rollbar.contrib.asgi.ReporterMiddleware.__call__.__annotations__, {'scope': Scope, 'receive': Receive, 'send': Send, 'return': None}, ) ================================================ FILE: rollbar/test/asgi_tests/test_spec.py ================================================ import inspect import sys import unittest from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 5) @unittest.skipUnless(ALLOWED_PYTHON_VERSION, 'ASGI implementation requires Python3.5+') class ASGISpecTest(BaseTest): def test_asgi_v3_middleware_is_single_callable_coroutine(self): from rollbar.contrib.asgi import ReporterMiddleware app = ReporterMiddleware(object) self.assertFalse(inspect.isclass(app)) self.assertTrue(hasattr(app, '__call__')) self.assertTrue(inspect.iscoroutinefunction(app.__call__)) def test_asgi_v3_app_signature(self): from rollbar.contrib.asgi import ReporterMiddleware app = ReporterMiddleware(object) app_args = inspect.getfullargspec(app).args self.assertListEqual(app_args, ['self', 'scope', 'receive', 'send']) ================================================ FILE: rollbar/test/async_tests/__init__.py ================================================ import sys import unittest def _load_tests(loader, tests, pattern): return unittest.TestSuite() if sys.version_info < (3, 6): load_tests = _load_tests ================================================ FILE: rollbar/test/async_tests/test_async.py ================================================ import copy import sys from unittest import mock import unittest import rollbar from rollbar.lib._async import AsyncMock from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless(ALLOWED_PYTHON_VERSION, 'Async support requires Python3.6+') class AsyncLibTest(BaseTest): default_settings = copy.deepcopy(rollbar.SETTINGS) def setUp(self): self.access_token = 'aaaabbbbccccddddeeeeffff00001111' rollbar.SETTINGS = copy.deepcopy(self.default_settings) rollbar._initialized = False rollbar.init(self.access_token, handler='async') @mock.patch('rollbar.send_payload') def test_report_exception(self, send_payload): from rollbar.lib._async import report_exc_info, run def _raise(): try: raise Exception('foo') except: return run(report_exc_info()) uuid = _raise() send_payload.assert_called_once() payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['uuid'], uuid) self.assertEqual(payload['access_token'], self.access_token) self.assertIn('body', payload['data']) self.assertIn('trace', payload['data']['body']) self.assertNotIn('trace_chain', payload['data']['body']) self.assertIn('exception', payload['data']['body']['trace']) self.assertEqual( payload['data']['body']['trace']['exception']['message'], 'foo' ) self.assertEqual( payload['data']['body']['trace']['exception']['class'], 'Exception' ) self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('locals', payload['data']['body']['trace']['frames'][-1]) @mock.patch('rollbar.send_payload') def test_report_messsage(self, send_payload): from rollbar.lib._async import report_message, run uuid = run(report_message('foo')) send_payload.assert_called_once() payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['uuid'], uuid) self.assertEqual(payload['access_token'], self.access_token) self.assertIn('body', payload['data']) self.assertIn('message', payload['data']['body']) self.assertIn('body', payload['data']['body']['message']) self.assertEqual(payload['data']['body']['message']['body'], 'foo') @mock.patch('rollbar.report_exc_info') def test_should_run_rollbar_report_exc_info(self, rollbar_report_exc_info): from rollbar.lib._async import report_exc_info, run try: raise Exception() except Exception: run( report_exc_info( 'exc_info', 'request', 'extra_data', 'payload_data', 'level_data', foo='bar', ) ) rollbar_report_exc_info.assert_called_once_with( 'exc_info', 'request', 'extra_data', 'payload_data', 'level_data', foo='bar' ) @mock.patch('rollbar.report_message') def test_should_run_rollbar_report_message(self, rollbar_report_message): from rollbar.lib._async import report_message, run run(report_message('message', 'level', 'request', 'extra_data', 'payload_data')) rollbar_report_message.assert_called_once_with( 'message', 'level', 'request', 'extra_data', 'payload_data' ) @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_async') def test_report_exc_info_should_use_async_handler_regardless_of_settings( self, mock__send_payload_async, mock_log ): import rollbar from rollbar.lib._async import report_exc_info, run rollbar.SETTINGS['handler'] = 'thread' self.assertEqual(rollbar.SETTINGS['handler'], 'thread') run(report_exc_info()) self.assertEqual(rollbar.SETTINGS['handler'], 'thread') mock__send_payload_async.assert_called_once() mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler. Switching to default async handler.' ) @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_async') def test_report_message_should_use_async_handler_regardless_of_settings( self, mock__send_payload_async, mock_log ): import rollbar from rollbar.lib._async import report_message, run rollbar.SETTINGS['handler'] = 'thread' self.assertEqual(rollbar.SETTINGS['handler'], 'thread') run(report_message('foo')) self.assertEqual(rollbar.SETTINGS['handler'], 'thread') mock__send_payload_async.assert_called_once() mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler. Switching to default async handler.' ) @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_async') def test_report_exc_info_should_allow_async_handler( self, mock__send_payload_async, mock_log ): import rollbar from rollbar.lib._async import report_exc_info, run rollbar.SETTINGS['handler'] = 'async' self.assertEqual(rollbar.SETTINGS['handler'], 'async') run(report_exc_info()) self.assertEqual(rollbar.SETTINGS['handler'], 'async') mock__send_payload_async.assert_called_once() mock_log.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_async') def test_report_message_should_allow_async_handler( self, mock__send_payload_async, mock_log ): import rollbar from rollbar.lib._async import report_message, run rollbar.SETTINGS['handler'] = 'async' self.assertEqual(rollbar.SETTINGS['handler'], 'async') run(report_message('foo')) self.assertEqual(rollbar.SETTINGS['handler'], 'async') mock__send_payload_async.assert_called_once() mock_log.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_httpx') def test_report_exc_info_should_allow_httpx_handler( self, mock__send_payload_httpx, mock_log ): import rollbar from rollbar.lib._async import report_exc_info, run rollbar.SETTINGS['handler'] = 'httpx' self.assertEqual(rollbar.SETTINGS['handler'], 'httpx') run(report_exc_info()) self.assertEqual(rollbar.SETTINGS['handler'], 'httpx') mock__send_payload_httpx.assert_called_once() mock_log.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_httpx') def test_report_message_should_allow_httpx_handler( self, mock__send_payload_httpx, mock_log ): import rollbar from rollbar.lib._async import report_message, run rollbar.SETTINGS['handler'] = 'httpx' self.assertEqual(rollbar.SETTINGS['handler'], 'httpx') run(report_message('foo', 'error')) self.assertEqual(rollbar.SETTINGS['handler'], 'httpx') mock__send_payload_httpx.assert_called_once() mock_log.assert_not_called() @mock.patch('logging.Logger.warning') def test_ctx_manager_should_use_async_handler(self, mock_log): import rollbar from rollbar.lib._async import AsyncHandler rollbar.SETTINGS['handler'] = 'thread' with AsyncHandler() as handler: self.assertEqual(handler, 'async') mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler.' ' Switching to default async handler.' ) rollbar.SETTINGS['handler'] = 'thread' @mock.patch('logging.Logger.warning') def test_ctx_manager_should_use_global_handler_if_contextvar_is_not_supported( self, mock_log ): import rollbar import rollbar.lib._async from rollbar.lib._async import AsyncHandler try: # simulate missing `contextvars` module _ctx_handler = rollbar.lib._async._ctx_handler rollbar.lib._async._ctx_handler = None rollbar.SETTINGS['handler'] = 'thread' self.assertEqual(rollbar.SETTINGS['handler'], 'thread') with AsyncHandler() as handler: self.assertEqual(handler, 'thread') mock_log.assert_not_called() self.assertEqual(rollbar.SETTINGS['handler'], 'thread') finally: # restore original _ctx_handler rollbar.lib._async._ctx_handler = _ctx_handler @mock.patch('logging.Logger.warning') def test_ctx_manager_should_not_substitute_global_handler(self, mock_log): import rollbar from rollbar.lib._async import AsyncHandler rollbar.SETTINGS['handler'] = 'thread' self.assertEqual(rollbar.SETTINGS['handler'], 'thread') with AsyncHandler() as handler: self.assertEqual(handler, 'async') self.assertEqual(rollbar.SETTINGS['handler'], 'thread') self.assertEqual(rollbar.SETTINGS['handler'], 'thread') mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler.' ' Switching to default async handler.' ) @mock.patch('rollbar._send_payload_httpx') @mock.patch('rollbar._send_payload_async') def test_report_exc_info_message_should_allow_multiple_async_handlers( self, mock__send_payload_async, mock__send_payload_httpx ): import asyncio import rollbar from rollbar.lib._async import report_exc_info, run async def report(handler): rollbar.SETTINGS['handler'] = handler try: raise Exception('foo') except: await report_exc_info() async def send_reports(): await asyncio.gather(report('async'), report('httpx')) run(send_reports()) mock__send_payload_async.assert_called_once() mock__send_payload_httpx.assert_called_once() @mock.patch('rollbar._send_payload_httpx') @mock.patch('rollbar._send_payload_async') def test_report_message_should_allow_multiple_async_handlers( self, mock__send_payload_async, mock__send_payload_httpx ): import asyncio import rollbar from rollbar.lib._async import report_message, run async def report(handler): rollbar.SETTINGS['handler'] = handler await report_message('foo') async def send_reports(): await asyncio.gather(report('async'), report('httpx')) run(send_reports()) mock__send_payload_async.assert_called_once() mock__send_payload_httpx.assert_called_once() @mock.patch('rollbar._send_payload') @mock.patch('rollbar._send_payload_thread') @mock.patch('rollbar._send_payload_httpx') @mock.patch('rollbar._send_payload_async') def test_report_exc_info_should_allow_multiple_handlers( self, mock__send_payload_async, mock__send_payload_httpx, mock__send_payload_thread, mock__send_payload, ): import asyncio import rollbar from rollbar.lib._async import report_exc_info, run async def async_report(handler): rollbar.SETTINGS['handler'] = handler try: raise Exception('foo') except: await report_exc_info() async def sync_report(handler): rollbar.SETTINGS['handler'] = handler try: raise Exception('foo') except: rollbar.report_exc_info() async def send_reports(): await asyncio.gather( sync_report('thread'), async_report('httpx'), sync_report('blocking'), async_report('async'), ) run(send_reports()) mock__send_payload_async.assert_called_once() mock__send_payload_httpx.assert_called_once() mock__send_payload_thread.assert_called_once() mock__send_payload.assert_called_once() @mock.patch('rollbar._send_payload') @mock.patch('rollbar._send_payload_thread') @mock.patch('rollbar._send_payload_httpx') @mock.patch('rollbar._send_payload_async') def test_report_message_should_allow_multiple_handlers( self, mock__send_payload_async, mock__send_payload_httpx, mock__send_payload_thread, mock__send_payload, ): import asyncio import rollbar from rollbar.lib._async import report_message, run async def async_report(handler): rollbar.SETTINGS['handler'] = handler await report_message('foo') async def sync_report(handler): rollbar.SETTINGS['handler'] = handler rollbar.report_message('foo') async def send_reports(): await asyncio.gather( sync_report('thread'), async_report('httpx'), sync_report('blocking'), async_report('async'), ) run(send_reports()) mock__send_payload_async.assert_called_once() mock__send_payload_httpx.assert_called_once() mock__send_payload_thread.assert_called_once() mock__send_payload.assert_called_once() @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_thread') @mock.patch('rollbar._send_payload_async') def test_report_exc_info_should_allow_multiple_handlers_with_threads( self, mock__send_payload_async, mock__send_payload_thread, mock_log, ): import time import threading import rollbar from rollbar.lib._async import report_exc_info, run async def async_report(): try: raise Exception('foo') except: await report_exc_info() def sync_report(): # give a chance to execute async_report() first time.sleep(0.1) try: raise Exception('foo') except: rollbar.report_exc_info() rollbar.SETTINGS['handler'] = 'thread' t1 = threading.Thread(target=run, args=(async_report(),)) t2 = threading.Thread(target=sync_report) t1.start() t2.start() t1.join() t2.join() mock__send_payload_async.assert_called_once() mock__send_payload_thread.assert_called_once() mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler. Switching to default async handler.' ) @mock.patch('logging.Logger.warning') @mock.patch('rollbar._send_payload_thread') @mock.patch('rollbar._send_payload_async') def test_report_message_should_allow_multiple_handlers_with_threads( self, mock__send_payload_async, mock__send_payload_thread, mock_log, ): import time import threading import rollbar from rollbar.lib._async import report_message, run async def async_report(): await report_message('foo') def sync_report(): # give a chance to execute async_report() first time.sleep(0.1) rollbar.report_message('foo') rollbar.SETTINGS['handler'] = 'thread' t1 = threading.Thread(target=run, args=(async_report(),)) t2 = threading.Thread(target=sync_report) t1.start() t2.start() t1.join() t2.join() mock__send_payload_async.assert_called_once() mock__send_payload_thread.assert_called_once() mock_log.assert_called_once_with( 'Running coroutines requires async compatible handler. Switching to default async handler.' ) @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) def test_should_try_report_with_async_handler(self, async_report_exc_info): import rollbar from rollbar.lib._async import run, try_report rollbar.SETTINGS['handler'] = 'async' self.assertEqual(rollbar.SETTINGS['handler'], 'async') run(try_report()) async_report_exc_info.assert_called_once() @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) def test_should_try_report_if_default_handler(self, async_report_exc_info): import rollbar from rollbar.lib._async import run, try_report rollbar.SETTINGS['handler'] = 'default' self.assertEqual(rollbar.SETTINGS['handler'], 'default') run(try_report()) async_report_exc_info.assert_called_once() @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) def test_should_not_try_report_with_async_handler_if_non_async_handler( self, async_report_exc_info ): import rollbar from rollbar.lib._async import RollbarAsyncError, run, try_report rollbar.SETTINGS['handler'] = 'thread' self.assertEqual(rollbar.SETTINGS['handler'], 'thread') with self.assertRaises(RollbarAsyncError): run(try_report()) async_report_exc_info.assert_not_called() @mock.patch('rollbar.lib._async.httpx', None) def test_try_report_should_raise_exc_if_httpx_package_is_missing(self): import rollbar from rollbar.lib._async import RollbarAsyncError, run, try_report rollbar.SETTINGS['handler'] = 'httpx' self.assertEqual(rollbar.SETTINGS['handler'], 'httpx') with self.assertRaises(RollbarAsyncError): run(try_report()) @mock.patch('asyncio.ensure_future') def test_should_schedule_task_in_event_loop(self, ensure_future): from rollbar.lib._async import call_later, coroutine try: if sys.version_info >= (3, 7): with mock.patch('asyncio.create_task') as create_task: coro = coroutine() call_later(coro) create_task.assert_called_once_with(coro) ensure_future.assert_not_called() else: coro = coroutine() call_later(coro) ensure_future.assert_called_once_with(coro) finally: # make sure the coroutine is closed to avoid RuntimeWarning by calling # coroutine without awaiting it later coro.close() ================================================ FILE: rollbar/test/fastapi_tests/__init__.py ================================================ import sys import unittest def _load_tests(loader, tests, pattern): return unittest.TestSuite() if sys.version_info < (3, 6): load_tests = _load_tests ================================================ FILE: rollbar/test/fastapi_tests/test_logger.py ================================================ import importlib import sys from unittest import mock try: import fastapi FASTAPI_INSTALLED = True except ImportError: FASTAPI_INSTALLED = False import unittest import rollbar from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI LoggerMiddleware requires Python3.6+', ) class LoggerMiddlewareTest(BaseTest): def setUp(self): importlib.reload(rollbar) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_version_to_payload(self, mock_send_payload, *mocks): import fastapi from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.logger import LoggerMiddleware self.assertIsNone(rollbar.BASE_DATA_HOOK) app = FastAPI() app.add_middleware(LoggerMiddleware) app.build_middleware_stack() rollbar.report_exc_info() mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] self.assertIn('fastapi', payload['data']['framework']) self.assertIn(fastapi.__version__, payload['data']['framework']) def test_should_support_type_hints(self): from starlette.types import Receive, Scope, Send import rollbar.contrib.fastapi.logger self.assertDictEqual( rollbar.contrib.fastapi.logger.LoggerMiddleware.__call__.__annotations__, {'scope': Scope, 'receive': Receive, 'send': Send, 'return': None}, ) @mock.patch('rollbar.contrib.starlette.logger.store_current_request') def test_should_store_current_request(self, store_current_request): from fastapi import FastAPI from rollbar.contrib.fastapi.logger import LoggerMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient expected_scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } app = FastAPI() app.add_middleware(LoggerMiddleware) @app.get('/') async def read_root(): return 'ok' client = TestClient(app) client.get('/') store_current_request.assert_called_once() scope = store_current_request.call_args[0][0] self.assertEqual(scope, {**expected_scope, **scope}) def test_should_return_current_request(self): from fastapi import FastAPI from starlette.requests import Request from rollbar.contrib.fastapi.logger import LoggerMiddleware from rollbar.contrib.fastapi import get_current_request try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient app = FastAPI() app.add_middleware(LoggerMiddleware) @app.get('/') async def read_root(): request = get_current_request() self.assertIsNotNone(request) self.assertIsInstance(request, Request) client = TestClient(app) client.get('/') @mock.patch('rollbar.contrib.starlette.requests.ContextVar', None) @mock.patch('logging.Logger.error') def test_should_not_return_current_request_for_older_python(self, mock_log): from fastapi import FastAPI from rollbar.contrib.fastapi.logger import LoggerMiddleware from rollbar.contrib.fastapi import get_current_request try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient app = FastAPI() app.add_middleware(LoggerMiddleware) @app.get('/') async def read_root(): self.assertIsNone(get_current_request()) mock_log.assert_called_once_with( 'Python 3.7+ (or aiocontextvars package)' ' is required to receive current request.' ) client = TestClient(app) client.get('/') ================================================ FILE: rollbar/test/fastapi_tests/test_middleware.py ================================================ import copy import importlib import sys from unittest import mock try: import fastapi FASTAPI_INSTALLED = True except ImportError: FASTAPI_INSTALLED = False import unittest import rollbar from rollbar.lib._async import AsyncMock from rollbar.test import BaseTest from rollbar.test.utils import get_public_attrs ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) class ReporterMiddlewareTest(BaseTest): default_settings = copy.deepcopy(rollbar.SETTINGS) def setUp(self): importlib.reload(rollbar) rollbar.SETTINGS = copy.deepcopy(self.default_settings) rollbar.SETTINGS['handler'] = 'async' @mock.patch('rollbar.report_exc_info') def test_should_catch_and_report_errors(self, mock_report): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def read_root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() args, kwargs = mock_report.call_args self.assertEqual(kwargs, {}) exc_type, exc_value, exc_tb = args[0] self.assertEqual(exc_type, ZeroDivisionError) self.assertIsInstance(exc_value, ZeroDivisionError) @mock.patch('rollbar.report_exc_info') def test_should_report_with_request_data(self, mock_report): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') def read_root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() request = mock_report.call_args[0][1] self.assertIsInstance(request, Request) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar._serialize_frame_data') @mock.patch('rollbar.send_payload') def test_should_send_payload_with_request_data(self, mock_send_payload, *mocks): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/{path}') def read_root(path): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/test?param1=value1¶m2=value2') mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] payload_request = payload['data']['request'] self.assertEqual(payload_request['method'], 'GET') self.assertEqual(payload_request['user_ip'], 'testclient') self.assertEqual( payload_request['url'], 'http://testserver/test?param1=value1¶m2=value2', ) self.assertDictEqual(payload_request['params'], {'path': 'test'}) self.assertDictEqual( payload_request['GET'], {'param1': 'value1', 'param2': 'value2'} ) self.assertDictEqual( payload_request['headers'], { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'testserver', 'user-agent': 'testclient', }, ) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_version_to_payload(self, mock_send_payload, *mocks): import fastapi from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.middleware import ReporterMiddleware self.assertIsNone(rollbar.BASE_DATA_HOOK) app = FastAPI() app.add_middleware(ReporterMiddleware) app.build_middleware_stack() rollbar.report_exc_info() mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] self.assertIn('fastapi', payload['data']['framework']) self.assertIn(fastapi.__version__, payload['data']['framework']) @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_default_handler( self, sync_report_exc_info, async_report_exc_info ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi import ReporterMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'default' app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_any_async_handler( self, sync_report_exc_info, async_report_exc_info ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi import ReporterMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'httpx' app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_sync_report_exc_info_if_non_async_handlers( self, sync_report_exc_info, async_report_exc_info, mock_log ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi import ReporterMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'threading' app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') sync_report_exc_info.assert_called_once() async_report_exc_info.assert_not_called() mock_log.assert_called_once_with( 'Failed to report asynchronously. Trying to report synchronously.' ) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access requires Python 3.6+' ) @mock.patch('rollbar.contrib.starlette.middleware.store_current_request') def test_should_store_current_request(self, store_current_request): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient expected_scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def read_root(): ... client = TestClient(app) client.get('/') store_current_request.assert_called_once() scope = store_current_request.call_args[0][0] self.assertEqual(scope, {**expected_scope, **scope}) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access is supported in Python 3.6+' ) def test_should_return_current_request(self): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware from rollbar.contrib.fastapi import get_current_request try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def read_root(original_request: Request): request = get_current_request() self.assertEqual(get_public_attrs(request), get_public_attrs(original_request)) client = TestClient(app) client.get('/') @mock.patch('rollbar.contrib.starlette.requests.ContextVar', None) @mock.patch('logging.Logger.error') def test_should_not_return_current_request_for_older_python(self, mock_log): from fastapi import FastAPI from rollbar.contrib.fastapi.middleware import ReporterMiddleware from rollbar.contrib.fastapi import get_current_request try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/') async def read_root(original_request: Request): request = get_current_request() self.assertIsNone(request) self.assertNotEqual(request, original_request) mock_log.assert_called_once_with( 'Python 3.7+ (or aiocontextvars package)' ' is required to receive current request.' ) client = TestClient(app) client.get('/') def test_should_support_http_only(self): from rollbar.contrib.fastapi.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run testapp = ReporterMiddleware(FailingTestASGIApp()) with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) mock_report.assert_called_once() with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'websocket'}, None, None)) mock_report.assert_not_called() def test_should_support_type_hints(self): from starlette.types import Receive, Scope, Send import rollbar.contrib.fastapi.middleware self.assertDictEqual( rollbar.contrib.fastapi.middleware.ReporterMiddleware.__call__.__annotations__, {'scope': Scope, 'receive': Receive, 'send': Send, 'return': None}, ) ================================================ FILE: rollbar/test/fastapi_tests/test_routing.py ================================================ import copy import importlib import json import sys from unittest import mock try: import fastapi FASTAPI_INSTALLED = True ALLOWED_FASTAPI_VERSION = fastapi.__version__ >= '0.41.0' except ImportError: FASTAPI_INSTALLED = False ALLOWED_FASTAPI_VERSION = False import unittest import rollbar from rollbar.lib._async import AsyncMock from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) class LoggingRouteUnsupportedFastAPIVersionTest(BaseTest): def test_should_disable_loading_route_handler_if_fastapi_is_too_old(self): import logging import fastapi from fastapi import FastAPI from fastapi.routing import APIRoute from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.utils import FastAPIVersionError logging.disable(logging.ERROR) # silent logger for tests fastapi_version = fastapi.__version__ app = FastAPI() old_route_class = app.router.route_class self.assertEqual(old_route_class, APIRoute) fastapi.__version__ = '0' with self.assertRaises(FastAPIVersionError): rollbar_add_to(app) fastapi.__version__ = '0.30.3' with self.assertRaises(FastAPIVersionError): rollbar_add_to(app) fastapi.__version__ = '0.40.10' with self.assertRaises(FastAPIVersionError): rollbar_add_to(app) self.assertEqual(app.router.route_class, old_route_class) logging.disable(logging.NOTSET) # make sure logger is re-enabled fastapi.__version__ = fastapi_version @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) @unittest.skipUnless(ALLOWED_FASTAPI_VERSION, 'FastAPI v0.41.0+ is required') class LoggingRouteTest(BaseTest): default_settings = copy.deepcopy(rollbar.SETTINGS) def setUp(self): importlib.reload(rollbar) rollbar.SETTINGS = copy.deepcopy(self.default_settings) rollbar.SETTINGS['handler'] = 'async' @mock.patch('rollbar.report_exc_info') def test_should_catch_and_report_errors(self, mock_report): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient app = FastAPI() rollbar_add_to(app) @app.get('/') async def read_root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() args, kwargs = mock_report.call_args self.assertEqual(kwargs, {}) exc_type, exc_value, exc_tb = args[0] self.assertEqual(exc_type, ZeroDivisionError) self.assertIsInstance(exc_value, ZeroDivisionError) @mock.patch('rollbar.report_exc_info') def test_should_report_with_request_data(self, mock_report): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() rollbar_add_to(app) @app.get('/') def read_root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() request = mock_report.call_args[0][1] self.assertIsInstance(request, Request) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar._serialize_frame_data') @mock.patch('rollbar.send_payload') def test_should_send_payload_with_request_data(self, mock_send_payload, *mocks): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient app = FastAPI() rollbar_add_to(app) @app.get('/{path}') def read_root(path): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/test?param1=value1¶m2=value2') mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] payload_request = payload['data']['request'] self.assertEqual(payload_request['method'], 'GET') self.assertEqual(payload_request['user_ip'], 'testclient') self.assertEqual( payload_request['url'], 'http://testserver/test?param1=value1¶m2=value2', ) self.assertDictEqual(payload_request['params'], {'path': 'test'}) self.assertDictEqual( payload_request['GET'], {'param1': 'value1', 'param2': 'value2'} ) self.assertDictEqual( payload_request['headers'], { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'testserver', 'user-agent': 'testclient', }, ) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar._serialize_frame_data') @mock.patch('rollbar.send_payload') def test_should_send_payload_with_request_body(self, mock_send_payload, *mocks): from fastapi import Body, FastAPI from pydantic import BaseModel from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['include_request_body'] = True expected_body = {'param1': 'value1', 'param2': 'value2'} app = FastAPI() rollbar_add_to(app) class TestBody(BaseModel): param1: str param2: str @app.post('/') def read_root(body: TestBody = Body(...)): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.post('/', json=expected_body) mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] payload_request = payload['data']['request'] self.assertEqual(payload_request['method'], 'POST') self.assertEqual(payload_request['user_ip'], 'testclient') self.assertEqual(payload_request['url'], 'http://testserver/') self.assertEqual(payload_request['body'], json.dumps(expected_body)) self.assertDictEqual( payload_request['headers'], { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'content-length': str(len(json.dumps(expected_body))), 'content-type': 'application/json', 'host': 'testserver', 'user-agent': 'testclient', }, ) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar._serialize_frame_data') @mock.patch('rollbar.send_payload') def test_should_send_payload_with_form_data(self, mock_send_payload, *mocks): from fastapi import FastAPI, Form from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient expected_form = {'param1': 'value1', 'param2': 'value2'} expected_body = b'param1=value1¶m2=value2' app = FastAPI() rollbar_add_to(app) @app.post('/') def read_root(param1: str = Form(...), param2: str = Form(...)): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): r = client.post( '/', data=expected_body, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] payload_request = payload['data']['request'] self.assertEqual(payload_request['method'], 'POST') self.assertEqual(payload_request['user_ip'], 'testclient') self.assertEqual(payload_request['url'], 'http://testserver/') self.assertDictEqual(payload_request['POST'], expected_form) self.assertDictEqual( payload_request['headers'], { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'content-length': str(len(expected_body)), 'content-type': 'application/x-www-form-urlencoded', 'host': 'testserver', 'user-agent': 'testclient', }, ) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_version_to_payload(self, mock_send_payload, *mocks): import fastapi from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to self.assertIsNone(rollbar.BASE_DATA_HOOK) app = FastAPI() rollbar_add_to(app) rollbar.report_exc_info() mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] self.assertIn('fastapi', payload['data']['framework']) self.assertIn(fastapi.__version__, payload['data']['framework']) @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_default_handler( self, sync_report_exc_info, async_report_exc_info ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'default' app = FastAPI() rollbar_add_to(app) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_any_async_handler( self, sync_report_exc_info, async_report_exc_info ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'httpx' app = FastAPI() rollbar_add_to(app) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_sync_report_exc_info_if_non_async_handlers( self, sync_report_exc_info, async_report_exc_info, mock_log ): from fastapi import FastAPI import rollbar from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient rollbar.SETTINGS['handler'] = 'threading' app = FastAPI() rollbar_add_to(app) @app.get('/') async def root(): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') sync_report_exc_info.assert_called_once() async_report_exc_info.assert_not_called() mock_log.assert_called_once_with( 'Failed to report asynchronously. Trying to report synchronously.' ) def test_should_enable_loading_route_handler_if_fastapi_version_is_sufficient(self): from fastapi import FastAPI from fastapi.routing import APIRoute from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute self.assertTrue(ALLOWED_FASTAPI_VERSION) app = FastAPI() old_route_class = app.router.route_class self.assertEqual(old_route_class, APIRoute) new_route_class = rollbar_add_to(app) self.assertNotEqual(new_route_class, old_route_class) self.assertEqual(app.router.route_class, new_route_class) self.assertEqual(app.router.route_class, RollbarLoggingRoute) def test_should_enable_loading_route_handler_before_adding_routes_to_app(self): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute app = FastAPI() old_route_class = app.router.route_class self.assertEqual(len(app.routes), 4) new_route_class = rollbar_add_to(app) self.assertNotEqual(new_route_class, old_route_class) self.assertEqual(app.router.route_class, new_route_class) self.assertEqual(len(app.routes), 4) @app.get('/') async def read_root(): ... self.assertEqual(app.router.route_class, new_route_class) self.assertEqual(app.router.route_class, RollbarLoggingRoute) self.assertEqual(len(app.routes), 5) @mock.patch('logging.Logger.error') def test_should_disable_loading_route_handler_after_adding_routes_to_app( self, mock_log ): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to app = FastAPI() old_route_class = app.router.route_class self.assertEqual(len(app.routes), 4) @app.get('/') async def read_root(): ... self.assertEqual(len(app.routes), 5) new_route_class = rollbar_add_to(app) self.assertEqual(len(app.routes), 5) self.assertIsNone(new_route_class) self.assertEqual(app.router.route_class, old_route_class) mock_log.assert_called_once_with( 'RollbarLoggingRoute must to be added to a bare router' ' (before adding routes). See docs for more details.' ) def test_should_enable_loading_route_handler_before_adding_routes_to_router(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute app = FastAPI() router = APIRouter() old_app_route_class = app.router.route_class old_router_route_class = router.route_class self.assertEqual(len(app.routes), 4) self.assertEqual(len(router.routes), 0) new_route_class = rollbar_add_to(router) self.assertNotEqual(new_route_class, old_router_route_class) self.assertEqual(router.route_class, new_route_class) self.assertEqual(router.route_class, RollbarLoggingRoute) self.assertEqual(app.router.route_class, old_app_route_class) self.assertEqual(len(app.routes), 4) self.assertEqual(len(router.routes), 0) @router.get('/') async def read_root(): ... app.include_router(router) self.assertEqual(router.route_class, new_route_class) self.assertEqual(len(app.routes), 5) @mock.patch('logging.Logger.error') def test_should_disable_loading_route_handler_after_adding_routes_to_router( self, mock_log ): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to app = FastAPI() router = APIRouter() old_app_route_class = app.router.route_class old_router_route_class = router.route_class self.assertEqual(len(app.routes), 4) self.assertEqual(len(router.routes), 0) @router.get('/') async def read_root(): ... app.include_router(router) self.assertEqual(len(app.routes), 5) self.assertEqual(len(router.routes), 1) new_route_class = rollbar_add_to(app) self.assertEqual(len(app.routes), 5) self.assertEqual(len(router.routes), 1) self.assertIsNone(new_route_class) self.assertEqual(app.router.route_class, old_app_route_class) self.assertEqual(router.route_class, old_router_route_class) mock_log.assert_called_once_with( 'RollbarLoggingRoute must to be added to a bare router' ' (before adding routes). See docs for more details.' ) def test_should_enable_loading_route_handler_for_multiple_routers(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute app = FastAPI() router1 = APIRouter() router2 = APIRouter() router3 = APIRouter() old_app_route_class = app.router.route_class old_router1_route_class = router1.route_class old_router2_route_class = router2.route_class old_router3_route_class = router3.route_class self.assertEqual(len(app.routes), 4) self.assertEqual(len(router1.routes), 0) self.assertEqual(len(router2.routes), 0) self.assertEqual(len(router3.routes), 0) new_router1_route_class = rollbar_add_to(router1) new_router2_route_class = rollbar_add_to(router2) self.assertNotEqual(new_router1_route_class, old_router1_route_class) self.assertNotEqual(new_router2_route_class, old_router2_route_class) self.assertEqual(router1.route_class, RollbarLoggingRoute) self.assertEqual(router2.route_class, RollbarLoggingRoute) self.assertEqual(router1.route_class, new_router1_route_class) self.assertEqual(router2.route_class, new_router2_route_class) self.assertEqual(router3.route_class, old_router3_route_class) self.assertEqual(app.router.route_class, old_app_route_class) self.assertEqual(len(app.routes), 4) self.assertEqual(len(router1.routes), 0) self.assertEqual(len(router2.routes), 0) self.assertEqual(len(router3.routes), 0) @router1.get('/') async def read1(): ... @router2.get('/') async def read2(): ... @router3.get('/') async def read3(): ... app.include_router(router1) app.include_router(router2) app.include_router(router3) self.assertEqual(router1.route_class, new_router1_route_class) self.assertEqual(router2.route_class, new_router2_route_class) self.assertEqual(router3.route_class, old_router3_route_class) self.assertEqual(len(app.routes), 7) def test_should_enable_loading_route_handler_for_fastapi_app(self): from fastapi import FastAPI from fastapi.routing import APIRoute from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute app = FastAPI() old_route_class = app.router.route_class self.assertEqual(old_route_class, APIRoute) new_route_class = rollbar_add_to(app) self.assertNotEqual(new_route_class, old_route_class) self.assertEqual(app.router.route_class, RollbarLoggingRoute) self.assertEqual(app.router.route_class, new_route_class) def test_should_enable_loading_route_handler_for_fastapi_router(self): from fastapi import APIRouter from fastapi.routing import APIRoute from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi.routing import RollbarLoggingRoute router = APIRouter() old_route_class = router.route_class self.assertEqual(old_route_class, APIRoute) new_route_class = rollbar_add_to(router) self.assertNotEqual(new_route_class, old_route_class) self.assertEqual(router.route_class, RollbarLoggingRoute) self.assertEqual(router.route_class, new_route_class) @mock.patch('logging.Logger.error') def test_should_disable_loading_route_handler_for_unknown_app(self, mock_log): from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to class UnkownRouter: route_class = None class UnknownApp: routes = [] router = UnkownRouter() app = UnknownApp() old_route_class = app.router.route_class new_route_class = rollbar_add_to(app) self.assertIsNone(new_route_class) self.assertEqual(app.router.route_class, old_route_class) mock_log.assert_called_once_with( 'Error adding RollbarLoggingRoute to application.' ) @mock.patch('logging.Logger.error') def test_should_disable_loading_route_handler_for_unknown_router(self, mock_log): from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to class UnknownRouter: routes = [] route_class = None router = UnknownRouter() old_route_class = router.route_class new_route_class = rollbar_add_to(router) self.assertIsNone(new_route_class) self.assertEqual(router.route_class, old_route_class) mock_log.assert_called_once_with( 'Error adding RollbarLoggingRoute to application.' ) def test_should_warn_if_middleware_in_use(self): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to from rollbar.contrib.fastapi import ReporterMiddleware as FastAPIMiddleware from rollbar.contrib.starlette import ReporterMiddleware as StarletteMiddleware from rollbar.contrib.asgi import ReporterMiddleware as ASGIMiddleware for middleware in (FastAPIMiddleware, StarletteMiddleware, ASGIMiddleware): with mock.patch('logging.Logger.warning') as mock_log: app = FastAPI() app.add_middleware(middleware) rollbar_add_to(app) mock_log.assert_called_once_with( f'Detected middleware installed {[middleware]}' ' while loading Rollbar route handler.' ' This can cause in duplicate occurrences.' ) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access requires Python 3.6+' ) @mock.patch('rollbar.contrib.fastapi.routing.store_current_request') def test_should_store_current_request(self, store_current_request): from fastapi import FastAPI from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.testclient import TestClient expected_scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'user-agent', b'testclient'), (b'accept-encoding', b'gzip, deflate'), (b'accept', b'*/*'), (b'connection', b'keep-alive'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } app = FastAPI() rollbar_add_to(app) @app.get('/') async def read_root(): ... client = TestClient(app) client.get('/') store_current_request.assert_called_once() scope = store_current_request.call_args[0][0] self.assertEqual(scope, {**expected_scope, **scope}) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access is supported in Python 3.6+' ) def test_should_return_current_request(self): from fastapi import FastAPI from rollbar.contrib.fastapi import get_current_request from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() rollbar_add_to(app) @app.get('/') async def read_root(original_request: Request): request = get_current_request() self.assertEqual(request, original_request) client = TestClient(app) client.get('/') @mock.patch('rollbar.contrib.starlette.requests.ContextVar', None) @mock.patch('logging.Logger.error') def test_should_not_return_current_request_for_older_python(self, mock_log): from fastapi import FastAPI from rollbar.contrib.fastapi import get_current_request from rollbar.contrib.fastapi.routing import add_to as rollbar_add_to try: from fastapi import Request from fastapi.testclient import TestClient except ImportError: # Added in FastAPI v0.51.0+ from starlette.requests import Request from starlette.testclient import TestClient app = FastAPI() rollbar_add_to(app) @app.get('/') async def read_root(original_request: Request): request = get_current_request() self.assertIsNone(request) self.assertNotEqual(request, original_request) mock_log.assert_called_once_with( 'Python 3.7+ (or aiocontextvars package)' ' is required to receive current request.' ) client = TestClient(app) client.get('/') def test_should_support_type_hints(self): from typing import Optional, Type, Union from fastapi import APIRouter, FastAPI from fastapi.routing import APIRoute import rollbar.contrib.fastapi.routing self.assertDictEqual( rollbar.contrib.fastapi.routing.add_to.__annotations__, { 'app_or_router': Union[FastAPI, APIRouter], 'return': Optional[Type[APIRoute]], }, ) ================================================ FILE: rollbar/test/fastapi_tests/test_utils.py ================================================ import sys try: import fastapi FASTAPI_INSTALLED = True except ImportError: FASTAPI_INSTALLED = False import unittest from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) class UtilsMiddlewareTest(BaseTest): def test_should_return_installed_rollbar_middlewares(self): from fastapi import FastAPI from rollbar.contrib.fastapi.utils import get_installed_middlewares from rollbar.contrib.fastapi import ReporterMiddleware as FastAPIMiddleware from rollbar.contrib.starlette import ReporterMiddleware as StarletteMiddleware from rollbar.contrib.asgi import ReporterMiddleware as ASGIMiddleware # single middleware app = FastAPI() app.add_middleware(FastAPIMiddleware) middlewares = get_installed_middlewares(app) self.assertListEqual(middlewares, [FastAPIMiddleware]) # multiple middlewares app = FastAPI() app.add_middleware(FastAPIMiddleware) app.add_middleware(StarletteMiddleware) app.add_middleware(ASGIMiddleware) middlewares = get_installed_middlewares(app) self.assertListEqual( middlewares, ([ASGIMiddleware, StarletteMiddleware, FastAPIMiddleware]) ) def test_should_return_empty_list_if_rollbar_middlewares_not_installed(self): from fastapi import FastAPI from rollbar.contrib.fastapi.utils import get_installed_middlewares from rollbar.lib._async import BareMiddleware # no middlewares app = FastAPI() middlewares = get_installed_middlewares(app) self.assertListEqual(middlewares, []) # no Rollbar middlewares app = FastAPI() app.add_middleware(BareMiddleware) middlewares = get_installed_middlewares(app) self.assertListEqual(middlewares, []) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) class UtilsBareRoutingTest(BaseTest): def test_should_return_true_if_has_bare_routing(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.utils import has_bare_routing app = FastAPI() self.assertTrue(has_bare_routing(app)) router = APIRouter() self.assertTrue(has_bare_routing(router)) def test_should_return_false_if_user_routes_added_to_app(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.utils import has_bare_routing app = FastAPI() self.assertTrue(has_bare_routing(app)) @app.get('/') async def read_root(): ... self.assertFalse(has_bare_routing(app)) def test_should_return_false_if_user_routes_added_to_router(self): from fastapi import APIRouter from rollbar.contrib.fastapi.utils import has_bare_routing router = APIRouter() self.assertTrue(has_bare_routing(router)) @router.get('/') async def read_root(): ... self.assertFalse(has_bare_routing(router)) def test_should_return_false_if_router_added_to_app(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.utils import has_bare_routing app = FastAPI() router = APIRouter() self.assertTrue(has_bare_routing(app)) @router.get('/') async def read_root(): ... app.include_router(router) self.assertFalse(has_bare_routing(app)) def test_should_return_true_if_docs_disabled(self): from fastapi import APIRouter, FastAPI from rollbar.contrib.fastapi.utils import has_bare_routing app = FastAPI(docs_url=None, redoc_url=None) self.assertTrue(has_bare_routing(app)) app = FastAPI(docs_url=None) self.assertTrue(has_bare_routing(app)) app = FastAPI(redoc_url=None) self.assertTrue(has_bare_routing(app)) router = APIRouter() self.assertTrue(has_bare_routing(router)) @unittest.skipUnless( FASTAPI_INSTALLED and ALLOWED_PYTHON_VERSION, 'FastAPI requires Python3.6+' ) class UtilsVersionCompareTest(BaseTest): def test_is_current_version_higher_or_equal(self): # Copied from https://semver.org/#spec-item-11 versions = [ '1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-alpha.beta', '1.0.0-beta', '1.0.0-beta.2', '1.0.0-beta.11', '1.0.0-rc.1', '1.0.0', '1.1.1', '1.100.0-beta2', '1.100.0-beta3', ] from rollbar.contrib.fastapi.utils import is_current_version_higher_or_equal previous_version = None for version in versions: if previous_version is None: previous_version = version continue with self.subTest(f'{version} >= {previous_version}'): self.assertTrue(is_current_version_higher_or_equal(version, previous_version)) previous_version = version ================================================ FILE: rollbar/test/flask_tests/__init__.py ================================================ ================================================ FILE: rollbar/test/flask_tests/test_flask.py ================================================ """ Tests for Flask instrumentation """ import json import sys import os from unittest import mock import rollbar from rollbar.lib.session import reset_current_session from rollbar.test import BaseTest # access token for https://rollbar.com/rollbar/pyrollbar TOKEN = '92c10f5616944b81a2e6f3c6493a0ec2' # Flask doesn't work on python 3.2, so don't test there. ALLOWED_PYTHON_VERSION = not (sys.version_info[0] == 3 and sys.version_info[1] == 2) try: import flask FLASK_INSTALLED = True except ImportError: FLASK_INSTALLED = False def create_app(): from flask import Flask, Request, got_request_exception import rollbar.contrib.flask app = Flask(__name__) @app.route('/') def index(): return 'Index page' @app.route('/cause_error', methods=['GET', 'POST']) def cause_error(): raise Exception("Uh oh") class CustomRequest(Request): @property def rollbar_person(self): return {'id': '123', 'username': 'testuser', 'email': 'test@example.com'} app.request_class = CustomRequest return app def init_rollbar(app): from flask import got_request_exception rollbar._initialized = False rollbar.init(TOKEN, 'flasktest', root=os.path.dirname(os.path.realpath(__file__)), allow_logging_basic_config=True, capture_email=True, capture_username=True) got_request_exception.connect(rollbar.contrib.flask.report_exception, app) if ALLOWED_PYTHON_VERSION and FLASK_INSTALLED: class FlaskTest(BaseTest): def setUp(self): super(FlaskTest, self).setUp() self.app = create_app() init_rollbar(self.app) self.client = self.app.test_client() reset_current_session() def test_index(self): resp = self.client.get('/') self.assertEqual(resp.status_code, 200) def assertStringEqual(self, left, right): if sys.version_info[0] > 2: if hasattr(left, 'decode'): left = left.decode('ascii') if hasattr(right, 'decode'): right = right.decode('ascii') return self.assertEqual(left, right) else: return self.assertEqual(left, right) @mock.patch('rollbar.send_payload') def test_uncaught(self, send_payload): rollbar.SETTINGS['include_request_body'] = True resp = self.client.get('/cause_error?foo=bar', headers={'X-Real-Ip': '1.2.3.4', 'User-Agent': 'Flask Test'}) self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) self.assertEqual(data['body']['trace']['exception']['class'], 'Exception') self.assertStringEqual(data['body']['trace']['exception']['message'], 'Uh oh') self.assertIn('person', data) self.assertDictEqual(data['person'], {'id': '123', 'username': 'testuser', 'email': 'test@example.com'}) self.assertIn('request', data) self.assertEqual(data['request']['url'], 'http://localhost/cause_error?foo=bar') # The behavior of implicitly converting werkzeug.ImmutableMultiDict # using dict() changes starting in Python 3.6. # See _build_werkzeug_request_data() if sys.version_info >= (3, 6): self.assertDictEqual(data['request']['GET'], {'foo': 'bar'}) else: self.assertDictEqual(data['request']['GET'], {'foo': ['bar']}) self.assertEqual(data['request']['user_ip'], '1.2.3.4') self.assertEqual(data['request']['method'], 'GET') self.assertEqual(data['request']['headers']['User-Agent'], 'Flask Test') @mock.patch('rollbar.send_payload') def test_uncaught_baggage_header(self, send_payload): rollbar.SETTINGS['include_request_body'] = True resp = self.client.get('/cause_error?foo=bar', headers={ 'X-Real-Ip': '1.2.3.4', 'User-Agent': 'Flask Test', 'Baggage': 'rollbar.session.id=abcde, rollbar.execution.scope.id = fghij', }) self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] data = payload['data'] self.assertEqual(data['attributes'], [ {'key': 'session_id', 'value': 'abcde'}, {'key': 'execution_scope_id', 'value': 'fghij'}, ]) @mock.patch('rollbar.send_payload') def test_uncaught_json_request(self, send_payload): rollbar.SETTINGS['include_request_body'] = True json_body = {"hello": "world"} json_body_str = json.dumps(json_body) resp = self.client.post('/cause_error', data=json_body_str, headers={'Content-Type': 'application/json', 'X-Forwarded-For': '5.6.7.8'}) self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) self.assertEqual(data['body']['trace']['exception']['class'], 'Exception') self.assertStringEqual(data['body']['trace']['exception']['message'], 'Uh oh') self.assertIn('person', data) self.assertDictEqual(data['person'], {'id': '123', 'username': 'testuser', 'email': 'test@example.com'}) self.assertIn('request', data) self.assertEqual(data['request']['url'], 'http://localhost/cause_error') self.assertEqual(data['request']['body'], json_body) self.assertEqual(data['request']['user_ip'], '5.6.7.8') self.assertEqual(data['request']['method'], 'POST') @mock.patch('rollbar.send_payload') def test_uncaught_no_username_no_email(self, send_payload): rollbar.SETTINGS['capture_email'] = False rollbar.SETTINGS['capture_username'] = False resp = self.client.get('/cause_error?foo=bar', headers={'X-Real-Ip': '1.2.3.4', 'User-Agent': 'Flask Test'}) self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) self.assertEqual(data['body']['trace']['exception']['class'], 'Exception') self.assertStringEqual(data['body']['trace']['exception']['message'], 'Uh oh') self.assertIn('person', data) self.assertDictEqual(data['person'], {'id': '123', 'username': None, 'email': None}) self.assertIn('request', data) self.assertEqual(data['request']['url'], 'http://localhost/cause_error?foo=bar') # The behavior of implicitly converting werkzeug.ImmutableMultiDict # using dict() changes starting in Python 3.6. # See _build_werkzeug_request_data() if sys.version_info >= (3, 6): self.assertDictEqual(data['request']['GET'], {'foo': 'bar'}) else: self.assertDictEqual(data['request']['GET'], {'foo': ['bar']}) self.assertEqual(data['request']['user_ip'], '1.2.3.4') self.assertEqual(data['request']['method'], 'GET') self.assertEqual(data['request']['headers']['User-Agent'], 'Flask Test') rollbar.SETTINGS['capture_email'] = True rollbar.SETTINGS['capture_username'] = True @mock.patch('rollbar.send_payload') def test_uncaught_no_body(self, send_payload): rollbar.SETTINGS['include_request_body'] = False resp = self.client.get('/cause_error?foo=bar', headers={'X-Real-Ip': '1.2.3.4', 'User-Agent': 'Flask Test'}) self.assertEqual(resp.status_code, 500) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('request', data) self.assertNotIn('body', data['request']) rollbar.SETTINGS['include_request_body'] = True ================================================ FILE: rollbar/test/starlette_tests/__init__.py ================================================ import sys import unittest def _load_tests(loader, tests, pattern): return unittest.TestSuite() if sys.version_info < (3, 6): load_tests = _load_tests ================================================ FILE: rollbar/test/starlette_tests/test_logger.py ================================================ import importlib import sys from unittest import mock try: import starlette STARLETTE_INSTALLED = True except ImportError: STARLETTE_INSTALLED = False import unittest import rollbar from rollbar.test import BaseTest ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( STARLETTE_INSTALLED and ALLOWED_PYTHON_VERSION, 'Starlette LoggerMiddleware requires Python3.6+', ) class LoggerMiddlewareTest(BaseTest): def setUp(self): importlib.reload(rollbar) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_version_to_payload(self, mock_send_payload, *mocks): import starlette from starlette.applications import Starlette import rollbar from rollbar.contrib.starlette.logger import LoggerMiddleware self.assertIsNone(rollbar.BASE_DATA_HOOK) app = Starlette() app.add_middleware(LoggerMiddleware) app.build_middleware_stack() rollbar.report_exc_info() mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] self.assertIn('starlette', payload['data']['framework']) self.assertIn(starlette.__version__, payload['data']['framework']) def test_should_support_type_hints(self): from starlette.types import Receive, Scope, Send import rollbar.contrib.starlette.logger self.assertDictEqual( rollbar.contrib.starlette.logger.LoggerMiddleware.__call__.__annotations__, {'scope': Scope, 'receive': Receive, 'send': Send, 'return': None}, ) @mock.patch('rollbar.contrib.starlette.logger.store_current_request') def test_should_store_current_request(self, store_current_request): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette.logger import LoggerMiddleware expected_scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } app = Starlette() app.add_middleware(LoggerMiddleware) @app.route('/{param}') async def root(request): return PlainTextResponse('OK') client = TestClient(app) client.get('/') store_current_request.assert_called_once() scope = store_current_request.call_args[0][0] self.assertEqual(scope, {**expected_scope, **scope}) def test_should_return_current_request(self): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette import get_current_request from rollbar.contrib.starlette.logger import LoggerMiddleware app = Starlette() app.add_middleware(LoggerMiddleware) @app.route('/') async def root(request): self.assertIsNotNone(get_current_request()) return PlainTextResponse('OK') client = TestClient(app) client.get('/') @mock.patch('rollbar.contrib.starlette.requests.ContextVar', None) @mock.patch('logging.Logger.error') def test_should_not_return_current_request_for_older_python(self, mock_log): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette import get_current_request from rollbar.contrib.starlette.logger import LoggerMiddleware app = Starlette() app.add_middleware(LoggerMiddleware) @app.route('/') async def root(request): self.assertIsNone(get_current_request()) mock_log.assert_called_once_with( 'Python 3.7+ (or aiocontextvars package) is required to receive current request.' ) return PlainTextResponse('OK') client = TestClient(app) client.get('/') ================================================ FILE: rollbar/test/starlette_tests/test_middleware.py ================================================ import copy import importlib import sys from unittest import mock try: import starlette STARLETTE_INSTALLED = True except ImportError: STARLETTE_INSTALLED = False import unittest import rollbar from rollbar.lib._async import AsyncMock from rollbar.test import BaseTest from rollbar.test.utils import get_public_attrs ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( STARLETTE_INSTALLED and ALLOWED_PYTHON_VERSION, 'Starlette requires Python3.6+' ) class ReporterMiddlewareTest(BaseTest): default_settings = copy.deepcopy(rollbar.SETTINGS) def setUp(self): importlib.reload(rollbar) rollbar.SETTINGS = copy.deepcopy(self.default_settings) rollbar.SETTINGS['handler'] = 'async' @mock.patch('rollbar.report_exc_info') def test_should_catch_and_report_errors(self, mock_report): from starlette.applications import Starlette from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() args, kwargs = mock_report.call_args self.assertEqual(kwargs, {}) exc_type, exc_value, exc_tb = args[0] self.assertEqual(exc_type, ZeroDivisionError) self.assertIsInstance(exc_value, ZeroDivisionError) @mock.patch('rollbar.report_exc_info') def test_should_report_with_request_data(self, mock_report): from starlette.applications import Starlette from starlette.requests import Request from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') mock_report.assert_called_once() request = mock_report.call_args[0][1] self.assertIsInstance(request, Request) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar._serialize_frame_data') @mock.patch('rollbar.send_payload') def test_should_send_payload_with_request_data(self, mock_send_payload, *mocks): from starlette.applications import Starlette from starlette.requests import Request from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/{path}') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/test?param1=value1¶m2=value2') mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] payload_request = payload['data']['request'] self.assertEqual(payload_request['method'], 'GET') self.assertEqual(payload_request['user_ip'], 'testclient') self.assertEqual( payload_request['url'], 'http://testserver/test?param1=value1¶m2=value2', ) self.assertDictEqual(payload_request['params'], {'path': 'test'}) self.assertDictEqual( payload_request['GET'], {'param1': 'value1', 'param2': 'value2'} ) self.assertDictEqual( payload_request['headers'], { 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'host': 'testserver', 'user-agent': 'testclient', }, ) @mock.patch('rollbar._check_config', return_value=True) @mock.patch('rollbar.send_payload') def test_should_add_framework_version_to_payload(self, mock_send_payload, *mocks): import starlette from starlette.applications import Starlette import rollbar from rollbar.contrib.starlette.middleware import ReporterMiddleware self.assertIsNone(rollbar.BASE_DATA_HOOK) app = Starlette() app.add_middleware(ReporterMiddleware) app.build_middleware_stack() rollbar.report_exc_info() mock_send_payload.assert_called_once() payload = mock_send_payload.call_args[0][0] self.assertIn('starlette', payload['data']['framework']) self.assertIn(starlette.__version__, payload['data']['framework']) @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_default_handler( self, sync_report_exc_info, async_report_exc_info ): from starlette.applications import Starlette from starlette.testclient import TestClient import rollbar from rollbar.contrib.starlette.middleware import ReporterMiddleware rollbar.SETTINGS['handler'] = 'default' app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_async_report_exc_info_if_any_async_handler( self, sync_report_exc_info, async_report_exc_info ): from starlette.applications import Starlette from starlette.testclient import TestClient import rollbar from rollbar.contrib.starlette.middleware import ReporterMiddleware rollbar.SETTINGS['handler'] = 'httpx' app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') async_report_exc_info.assert_called_once() sync_report_exc_info.assert_not_called() @mock.patch('logging.Logger.warning') @mock.patch('rollbar.lib._async.report_exc_info', new_callable=AsyncMock) @mock.patch('rollbar.report_exc_info') def test_should_use_sync_report_exc_info_if_non_async_handlers( self, sync_report_exc_info, async_report_exc_info, mock_log ): from starlette.applications import Starlette from starlette.testclient import TestClient import rollbar from rollbar.contrib.starlette.middleware import ReporterMiddleware rollbar.SETTINGS['handler'] = 'threading' app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(request): 1 / 0 client = TestClient(app) with self.assertRaises(ZeroDivisionError): client.get('/') sync_report_exc_info.assert_called_once() async_report_exc_info.assert_not_called() mock_log.assert_called_once_with( 'Failed to report asynchronously. Trying to report synchronously.' ) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access requires Python 3.6+' ) @mock.patch('rollbar.contrib.starlette.middleware.store_current_request') def test_should_store_current_request(self, store_current_request): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware expected_scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/{param}') async def root(request): return PlainTextResponse('OK') client = TestClient(app) client.get('/') store_current_request.assert_called_once() scope = store_current_request.call_args[0][0] self.assertEqual(scope, {**expected_scope, **scope}) @unittest.skipUnless( sys.version_info >= (3, 6), 'Global request access is supported in Python 3.6+' ) def test_should_return_current_request(self): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware from rollbar.contrib.starlette import get_current_request app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(original_request): request = get_current_request() self.assertEqual(get_public_attrs(request), get_public_attrs(original_request)) return PlainTextResponse('OK') client = TestClient(app) client.get('/') @mock.patch('rollbar.contrib.starlette.requests.ContextVar', None) @mock.patch('logging.Logger.error') def test_should_not_return_current_request_for_older_python(self, mock_log): from starlette.applications import Starlette from starlette.responses import PlainTextResponse from starlette.testclient import TestClient from rollbar.contrib.starlette.middleware import ReporterMiddleware from rollbar.contrib.starlette import get_current_request app = Starlette() app.add_middleware(ReporterMiddleware) @app.route('/') async def root(original_request): request = get_current_request() self.assertIsNone(request) self.assertNotEqual(request, original_request) mock_log.assert_called_once_with( 'Python 3.7+ (or aiocontextvars package)' ' is required to receive current request.' ) return PlainTextResponse('OK') client = TestClient(app) client.get('/') def test_should_support_http_only(self): from rollbar.contrib.starlette.middleware import ReporterMiddleware from rollbar.lib._async import FailingTestASGIApp, run testapp = ReporterMiddleware(FailingTestASGIApp()) with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'http', 'headers': []}, None, None)) mock_report.assert_called_once() with mock.patch('rollbar.report_exc_info') as mock_report: with self.assertRaises(RuntimeError): run(testapp({'type': 'websocket'}, None, None)) mock_report.assert_not_called() def test_should_support_type_hints(self): from starlette.types import Receive, Scope, Send self.assertDictEqual( rollbar.contrib.starlette.ReporterMiddleware.__call__.__annotations__, {'scope': Scope, 'receive': Receive, 'send': Send, 'return': None}, ) ================================================ FILE: rollbar/test/starlette_tests/test_requests.py ================================================ import sys try: import starlette STARLETTE_INSTALLED = True except ImportError: STARLETTE_INSTALLED = False import unittest from rollbar.test import BaseTest from rollbar.test.utils import get_public_attrs ALLOWED_PYTHON_VERSION = sys.version_info >= (3, 6) @unittest.skipUnless( STARLETTE_INSTALLED and ALLOWED_PYTHON_VERSION, 'Global request access requires Python3.6+', ) class RequestTest(BaseTest): def test_should_accept_request_param(self): from starlette.requests import Request from rollbar.contrib.starlette.requests import store_current_request from rollbar.lib._async import async_receive scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'user-agent', b'testclient'), (b'accept-encoding', b'gzip, deflate'), (b'accept', b'*/*'), (b'connection', b'keep-alive'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } receive = async_receive( {'type': 'http.request', 'body': b'body body', 'mode_body': False} ) request = Request(scope, receive) stored_request = store_current_request(request) self.assertEqual(get_public_attrs(request), get_public_attrs(stored_request)) def test_should_accept_scope_param_if_http_type(self): from starlette.requests import Request from rollbar.contrib.starlette.requests import store_current_request from rollbar.lib._async import async_receive scope = { 'client': ['testclient', 50000], 'headers': [ (b'host', b'testserver'), (b'user-agent', b'testclient'), (b'accept-encoding', b'gzip, deflate'), (b'accept', b'*/*'), (b'connection', b'keep-alive'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/', 'query_string': b'', 'root_path': '', 'scheme': 'http', 'server': ['testserver', 80], 'type': 'http', } receive = async_receive( {'type': 'http.request', 'body': b'body body', 'mode_body': False} ) expected_request = Request(scope, receive) request = store_current_request(scope, receive) self.assertEqual(get_public_attrs(request), get_public_attrs(expected_request)) def test_should_not_accept_scope_param_if_not_http_type(self): from rollbar.contrib.starlette.requests import store_current_request scope = {'asgi': {'spec_version': '2.0', 'version': '3.0'}, 'type': 'lifespan'} receive = {} request = store_current_request(scope, receive) self.assertIsNone(request) def test_hasuser(self): from starlette.requests import Request from rollbar.contrib.starlette.requests import hasuser request = Request({'type': 'http'}, {}) self.assertFalse(hasuser(request)) request = Request({'type': 'http', 'user': 'testuser'}, {}) self.assertTrue(hasuser(request)) self.assertEqual(request.user, 'testuser') ================================================ FILE: rollbar/test/test_basic_filters.py ================================================ from rollbar.lib import events, filters from rollbar.test import BaseTest class BasicFiltersTest(BaseTest): def setUp(self): events.reset() filters.add_builtin_filters({}) def test_rollbar_ignored_exception(self): class IgnoredException(Exception): _rollbar_ignore = True class NotIgnoredException(Exception): _rollbar_ignore = False self.assertFalse(events.on_exception_info((None, IgnoredException(), None))) self.assertIsNot(events.on_exception_info((None, NotIgnoredException(), None)), False) def test_filter_by_level(self): self.assertFalse(events.on_exception_info((None, 123, None), level='ignored')) self.assertIsNot(events.on_exception_info((None, 123, None), level='error'), False) self.assertFalse(events.on_message('hello world', level='ignored')) self.assertIsNot(events.on_message('hello world', level='error'), False) self.assertFalse(events.on_payload({}, level='ignored')) self.assertIsNot(events.on_message({}, level='error'), False) ================================================ FILE: rollbar/test/test_batched_transform.py ================================================ from rollbar.lib.transforms import transform from rollbar.lib.transform import Transform from rollbar.lib.traverse import traverse from rollbar.test import BaseTest class TrackingTransformer(Transform): def __init__(self): self.got = [] def default(self, o, key=None): self.got.append((o, key)) return o class BatchedTransformTest(BaseTest): def assertTrackingTransform(self, input): tracking_transformer = TrackingTransformer() transforms = [ tracking_transformer, tracking_transformer, ] transform(input, transforms, batch_transforms=True) want = [] def dup_watch_handler(o, key=None): want.append((o, key)) want.append((o, key)) return o traverse( input, string_handler=dup_watch_handler, tuple_handler=dup_watch_handler, namedtuple_handler=dup_watch_handler, list_handler=dup_watch_handler, set_handler=dup_watch_handler, mapping_handler=dup_watch_handler, default_handler=dup_watch_handler, circular_reference_handler=dup_watch_handler, ) self.assertEqual(want, tracking_transformer.got) def test_number(self): self.assertTrackingTransform(1) def test_flat_list(self): self.assertTrackingTransform([0, 1, 2, 3]) def test_flat_tuple(self): self.assertTrackingTransform((0, 1, 2, 3)) def test_nested_object(self): self.assertTrackingTransform((0, [1, 2], {"a": 3, "b": (4, 5)})) ================================================ FILE: rollbar/test/test_custom_filters.py ================================================ import re from rollbar.lib import events, filters from rollbar.test import BaseTest class CustomFiltersTest(BaseTest): def setUp(self): events.reset() filters.add_builtin_filters({}) def test_ignore_by_setting_rollbar_ignore(self): class NotIgnoredByDefault(Exception): pass def _ignore_if_cruel_world_filter(exc_info, **kw): cls, exc, trace = exc_info if 'cruel world' in str(exc): exc._rollbar_ignore = True return exc_info events.add_exception_info_handler(_ignore_if_cruel_world_filter, pos=0) self.assertIsNot(events.on_exception_info((None, NotIgnoredByDefault('hello world'), None)), False) self.assertFalse(events.on_exception_info((None, NotIgnoredByDefault('hello cruel world'), None))) def test_ignore_messages_by_regex(self): regex = re.compile(r'cruel') def _ignore_cruel_world_substring(message, **kw): if regex.search(message): return False return message events.add_message_handler(_ignore_cruel_world_substring) self.assertFalse(events.on_message('hello cruel world')) self.assertIsNot(events.on_message('hello world'), False) def test_modify_payload(self): def _add_test_key(payload, **kw): payload['test'] = 333 return payload events.add_payload_handler(_add_test_key) self.assertEqual(events.on_payload({'hello': 'world'}), {'hello': 'world', 'test': 333}) ================================================ FILE: rollbar/test/test_lib.py ================================================ from rollbar.lib import dict_merge, prefix_match, key_match, key_depth from rollbar.lib.transport import _get_proxy_cfg from rollbar.test import BaseTest class RollbarLibTest(BaseTest): def test_prefix_match(self): key = ['password', 'argspec', '0'] self.assertTrue(prefix_match(key, [['password']])) def test_prefix_match_dont_match(self): key = ['environ', 'argspec', '0'] self.assertFalse(prefix_match(key, [['password']])) def test_key_match(self): canonical = ['body', 'trace', 'frames', '*', 'locals', '*'] key = ['body', 'trace', 'frames', 5, 'locals', 'foo'] self.assertTrue(key_match(key, canonical)) def test_key_match_dont_match(self): canonical = ['body', 'trace', 'frames', '*', 'locals', '*'] key = ['body', 'trace', 'frames', 5, 'bar', 'foo'] self.assertFalse(key_match(key, canonical)) def test_key_match_wildcard_end(self): canonical = ['body', 'trace', 'frames', '*', 'locals', '*'] key = ['body', 'trace', 'frames', 5, 'locals', 'foo', 'bar'] self.assertTrue(key_match(key, canonical)) def test_key_match_too_short(self): canonical = ['body', 'trace', 'frames', '*', 'locals', '*'] key = ['body', 'trace', 'frames', 5, 'locals'] self.assertFalse(key_match(key, canonical)) def test_key_depth(self): canonicals = [['body', 'trace', 'frames', '*', 'locals', '*']] key = ['body', 'trace', 'frames', 5, 'locals', 'foo'] self.assertEqual(6, key_depth(key, canonicals)) def test_key_depth_dont_match(self): canonicals = [['body', 'trace', 'frames', '*', 'locals', '*']] key = ['body', 'trace', 'frames', 5, 'bar', 'foo'] self.assertEqual(0, key_depth(key, canonicals)) def test_key_depth_wildcard_end(self): canonicals = [['body', 'trace', 'frames', '*']] key = ['body', 'trace', 'frames', 5, 'locals', 'foo', 'bar'] self.assertEqual(4, key_depth(key, canonicals)) def test_dict_merge_not_dict(self): a = {'a': {'b': 42}} b = 99 result = dict_merge(a, b) self.assertEqual(99, result) def test_dict_merge_dicts_independent(self): a = {'a': {'b': 42}} b = {'x': {'y': 99}} result = dict_merge(a, b) self.assertIn('a', result) self.assertIn('b', result['a']) self.assertEqual(42, result['a']['b']) self.assertIn('x', result) self.assertIn('y', result['x']) self.assertEqual(99, result['x']['y']) def test_dict_merge_dicts(self): a = {'a': {'b': 42}} b = {'a': {'c': 99}} result = dict_merge(a, b) self.assertIn('a', result) self.assertIn('b', result['a']) self.assertIn('c', result['a']) self.assertEqual(42, result['a']['b']) self.assertEqual(99, result['a']['c']) def test_dict_merge_dicts_second_wins(self): a = {'a': {'b': 42}} b = {'a': {'b': 99}} result = dict_merge(a, b) self.assertIn('a', result) self.assertIn('b', result['a']) self.assertEqual(99, result['a']['b']) def test_dict_merge_dicts_select_poll(self): import select poll = getattr(select, 'poll', None) if poll is None: return p = poll() a = {'a': {'b': 42}} b = {'a': {'y': p}} result = dict_merge(a, b, silence_errors=True) self.assertIn('a', result) self.assertIn('b', result['a']) self.assertEqual(42, result['a']['b']) self.assertIn('y', result['a']) self.assertRegex(result['a']['y'], r'Uncopyable obj') def test_transport_get_proxy_cfg(self): result = _get_proxy_cfg({}) self.assertEqual(None, result) result = _get_proxy_cfg({'proxy': 'localhost'}) self.assertEqual({'http': 'http://localhost', 'https': 'http://localhost'}, result) result = _get_proxy_cfg({'proxy': 'localhost:8080'}) self.assertEqual({'http': 'http://localhost:8080', 'https': 'http://localhost:8080'}, result) result = _get_proxy_cfg({'proxy': 'localhost', 'proxy_user': 'username', 'proxy_password': 'password'}) self.assertEqual({ 'http': 'http://username:password@localhost', 'https': 'http://username:password@localhost', }, result) ================================================ FILE: rollbar/test/test_loghandler.py ================================================ """ Tests for the RollbarHandler logging handler """ import copy import json import logging import sys from unittest import mock import rollbar from rollbar.logger import RollbarHandler from rollbar.test import BaseTest _test_access_token = 'aaaabbbbccccddddeeeeffff00001111' _test_environment = 'test' _default_settings = copy.deepcopy(rollbar.SETTINGS) class CauseException(Exception): pass class LogHandlerTest(BaseTest): def setUp(self): rollbar._initialized = False rollbar.SETTINGS = copy.deepcopy(_default_settings) self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) self.rollbar_handler = RollbarHandler(_test_access_token, _test_environment) self.rollbar_handler.setLevel(logging.WARNING) self.logger.addHandler(self.rollbar_handler) def tearDown(self): self.logger.removeHandler(self.rollbar_handler) @mock.patch('rollbar.send_payload') def test_message_gets_formatted(self, send_payload): self.logger.warning("Hello %d %s", 1, 'world') payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['message']['body'], "Hello 1 world") self.assertEqual(payload['data']['body']['message']['args'], (1, 'world')) self.assertEqual(payload['data']['body']['message']['record']['name'], __name__) self.assertEqual(payload['data']['custom']['args'], (1, 'world')) self.assertEqual(payload['data']['custom']['record']['name'], __name__) @mock.patch('rollbar.send_payload') def test_string_or_int_level(self, send_payload): self.logger.setLevel(logging.ERROR) self.rollbar_handler.setLevel('WARNING') self.logger.error("I am an error") payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'error') self.rollbar_handler.setLevel(logging.WARNING) self.logger.error("I am an error") self.assertEqual(payload['data']['level'], 'error') def test_request_is_get_from_log_record_if_present(self): # Request objects vary depending on python frameworks or packages. # Using a dictionary for this test is enough. request = {"fake": "request", "for": "testing purporse"} # No need to test request parsing and payload sent, # just need to be sure that proper rollbar function is called # with passed request as argument. with mock.patch("rollbar.report_message") as report_message_mock: self.logger.warning("Warning message", extra={"request": request}) self.assertEqual(report_message_mock.call_args[1]["request"], request) # if you call logger.exception outside of an exception # handler, it shouldn't try to report exc_info, since it # won't have any with mock.patch("rollbar.report_exc_info") as report_exc_info: with mock.patch("rollbar.report_message") as report_message_mock: self.logger.exception("Exception message", extra={"request": request}) report_exc_info.assert_not_called() self.assertEqual(report_message_mock.call_args[1]["request"], request) with mock.patch("rollbar.report_exc_info") as report_exc_info: with mock.patch("rollbar.report_message") as report_message_mock: try: raise Exception() except: self.logger.exception("Exception message", extra={"request": request}) self.assertEqual(report_exc_info.call_args[1]["request"], request) report_message_mock.assert_not_called() @mock.patch('rollbar.send_payload') def test_nested_exception_trace_chain(self, send_payload): def _raise_context(): bar_local = 'bar' raise CauseException('bar') def _raise_ex(): try: _raise_context() except CauseException as context: # python2 won't automatically assign this traceback... exc_info = sys.exc_info() setattr(context, '__traceback__', exc_info[2]) try: foo_local = 'foo' # in python3 __context__ is automatically set when an exception is raised in an except block e = Exception('foo') setattr(e, '__context__', context) # PEP-3134 raise e except: self.logger.exception("Bad time") _raise_ex() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] body = payload['data']['body'] trace = body['trace'] if 'trace' in body else None trace_chain = body['trace_chain'] if 'trace_chain' in body else None has_only_trace_chain = trace is None and trace_chain is not None has_only_trace = trace is not None and trace_chain is None self.assertTrue(has_only_trace or has_only_trace_chain) if trace_chain is not None: self.assertEqual('Bad time', payload['data']['custom']['exception']['description']) if trace is not None: self.assertEqual('Bad time', trace['exception']['description']) @mock.patch('rollbar.send_payload') def test_not_nested_exception_trace_chain(self, send_payload): def _raise_context(): bar_local = 'bar' raise CauseException('bar') def _raise_ex(): try: _raise_context() except: self.logger.exception("Bad time") _raise_ex() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] body = payload['data']['body'] trace = body['trace'] if 'trace' in body else None trace_chain = body['trace_chain'] if 'trace_chain' in body else None has_only_trace_chain = trace is None and trace_chain is not None has_only_trace = trace is not None and trace_chain is None self.assertTrue(has_only_trace or has_only_trace_chain) if trace_chain is not None: self.assertEqual('Bad time', payload['data']['custom']['exception']['description']) if trace is not None: self.assertEqual('Bad time', trace['exception']['description']) @mock.patch('rollbar.send_payload') def test_logging_extra(self, send_payload): self.logger.error("Test error", extra=dict(test_attribute=1, test_other='test')) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['message'].get('test_attribute'), 1) self.assertEqual(payload['data']['body']['message'].get('test_other'), 'test') self.assertEqual(payload['data']['custom'].get('test_attribute'), 1) self.assertEqual(payload['data']['custom'].get('test_other'), 'test') @mock.patch('rollbar.send_payload') def test_logging_extra_data(self, send_payload): self.logger.error( "Test error", extra=dict(extra_data=dict(test_attribute=1, test_other='test'))) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['message'].get('test_attribute'), 1) self.assertEqual(payload['data']['body']['message'].get('test_other'), 'test') self.assertEqual(payload['data']['custom'].get('test_attribute'), 1) self.assertEqual(payload['data']['custom'].get('test_other'), 'test') @mock.patch('rollbar.send_payload') def test_log_formatting(self, send_payload): self.rollbar_handler.formatter = logging.Formatter( '%(test_other)s[%(test_attribute)s]: %(message)s' ) self.logger.error("Test error", extra=dict(test_attribute=1, test_other='test')) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['body']['message']['body'], 'test[1]: Test error') ================================================ FILE: rollbar/test/test_pyramid.py ================================================ from unittest import mock from rollbar.test import BaseTest try: from pyramid.request import Request PYRAMID_INSTALLED = True except ImportError: PYRAMID_INSTALLED = False if PYRAMID_INSTALLED: class PyramidMiddlewareTest(BaseTest): def test_catch_exception_in_the_wsgi_app(self): from rollbar.contrib.pyramid import RollbarMiddleware def wsgi_app(environ, start_resp): raise RuntimeError("oops") middleware = RollbarMiddleware({}, wsgi_app) with mock.patch("rollbar.report_exc_info") as mock_report: with self.assertRaises(RuntimeError): middleware(environ={}, start_resp=lambda: None) self.assertEqual(mock_report.call_count, 1) args, kwargs = mock_report.call_args self.assertEqual(kwargs, {}) exc_info, request = args exc_type, exc_value, exc_tb = exc_info self.assertEqual(exc_type, RuntimeError) self.assertIsInstance(exc_value, RuntimeError) self.assertIsInstance(request, Request) ================================================ FILE: rollbar/test/test_rollbar.py ================================================ import base64 import copy import json import socket import threading import uuid import sys from collections import namedtuple from rollbar.lib.session import reset_current_session try: from StringIO import StringIO except ImportError: from io import StringIO from pathlib import Path from unittest import mock import unittest import rollbar from rollbar.lib import string_types from rollbar.test import BaseTest from rollbar.test.utils import get_public_attrs try: eval(""" def _anonymous_tuple_func(x, (a, b), y): ret = x + a + b + y breakme() return ret """) except SyntaxError: _anonymous_tuple_func = None _test_access_token = 'aaaabbbbccccddddeeeeffff00001111' _default_settings = copy.deepcopy(rollbar.SETTINGS) class RollbarTest(BaseTest): def setUp(self): rollbar._initialized = False rollbar.SETTINGS = copy.deepcopy(_default_settings) rollbar.init(_test_access_token, locals={'enabled': True}, dummy_key='asdf', handler='blocking', timeout=12345) reset_current_session() def test_merged_settings(self): expected = {'enabled': True, 'sizes': rollbar.DEFAULT_LOCALS_SIZES, 'safe_repr': True, 'scrub_varargs': True, 'safelisted_types': [], 'whitelisted_types': []} self.assertDictEqual(rollbar.SETTINGS['locals'], expected) self.assertEqual(rollbar.SETTINGS['timeout'], 12345) self.assertEqual(rollbar.SETTINGS['dummy_key'], 'asdf') def test_default_configuration(self): self.assertEqual(rollbar.SETTINGS['access_token'], _test_access_token) self.assertEqual(rollbar.SETTINGS['environment'], 'production') @mock.patch('rollbar.send_payload') def test_disabled(self, send_payload): rollbar.SETTINGS['enabled'] = False rollbar.report_message('foo') try: raise Exception('foo') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, False) def test_server_data(self): server_data = rollbar._build_server_data() self.assertIn('host', server_data) self.assertIn('argv', server_data) self.assertNotIn('branch', server_data) self.assertNotIn('root', server_data) self.assertGreater(len(server_data['host']), 2) rollbar.SETTINGS['host'] = 'test-host' rollbar.SETTINGS['branch'] = 'master' rollbar.SETTINGS['root'] = '/home/test/' server_data = rollbar._build_server_data() self.assertIn('argv', server_data) self.assertEqual(server_data['host'], 'test-host') self.assertEqual(server_data['branch'], 'master') self.assertEqual(server_data['root'], '/home/test/') def test_wsgi_request_data(self): rollbar.SETTINGS['include_request_body'] = True request = { 'CONTENT_LENGTH': str(len('body body body')), 'CONTENT_TYPE': '', 'DOCUMENT_URI': '/api/test', 'GATEWAY_INTERFACE': 'CGI/1.1', 'HTTP_CONNECTION': 'close', 'HTTP_HOST': 'example.com', 'HTTP_USER_AGENT': 'Agent', 'PATH_INFO': '/api/test', 'QUERY_STRING': 'format=json¶m1=value1¶m2=value2', 'REMOTE_ADDR': '127.0.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_ADDR': '127.0.0.1', 'SERVER_NAME': 'example.com', 'SERVER_PORT': '80', 'SERVER_PROTOCOL': 'HTTP/1.1', 'wsgi.input': StringIO('body body body'), 'wsgi.multiprocess': True, 'wsgi.multithread': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0) } data = rollbar._build_wsgi_request_data(request) self.assertEqual(data['url'], 'http://example.com/api/test?format=json¶m1=value1¶m2=value2') self.assertEqual(data['user_ip'], '127.0.0.1') self.assertEqual(data['method'], 'GET') self.assertEqual(data['body'], 'body body body') self.assertDictEqual(data['GET'], {'format': 'json', 'param1': 'value1', 'param2': 'value2'}) self.assertDictEqual(data['headers'], {'Connection': 'close', 'Host': 'example.com', 'User-Agent': 'Agent'}) def test_wsgi_request_data_no_body(self): rollbar.SETTINGS['include_request_body'] = False request = { 'CONTENT_LENGTH': str(len('body body body')), 'REMOTE_ADDR': '127.0.0.1', 'SERVER_NAME': 'example.com', 'SERVER_PORT': '80', 'wsgi.input': StringIO('body body body'), 'wsgi.url_scheme': 'http', } data = rollbar._build_wsgi_request_data(request) self.assertNotIn('body', data) rollbar.SETTINGS['include_request_body'] = True def test_starlette_request_data(self): try: from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'accept', b'*/*'), (b'content-type', b'application/x-www-form-urlencoded'), (b'host', b'example.com'), (b'user-agent', b'Agent'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/api/test', 'path_params': {'param': 'test'}, 'query_params': { 'format': 'json', 'param1': 'value1', 'param2': 'value2', }, 'query_string': b'format=json¶m1=value1¶m2=value2', 'scheme': 'http', 'server': ('example.com', 80), 'url': {'path': 'example.com'}, } request = Request(scope) data = rollbar._build_starlette_request_data(request) self.assertEqual(data['url'], 'http://example.com/api/test?format=json¶m1=value1¶m2=value2') self.assertEqual(data['user_ip'], '127.0.0.1') self.assertEqual(data['method'], 'GET') self.assertDictEqual(data['params'], {'param': 'test'}) self.assertDictEqual(data['GET'], {'format': 'json', 'param1': 'value1', 'param2': 'value2'}) self.assertDictEqual( data['headers'], { 'accept': '*/*', 'content-type': 'application/x-www-form-urlencoded', 'host': 'example.com', 'user-agent': 'Agent', }, ) def test_starlette_request_baggage_headers(self): try: from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'accept', b'*/*'), (b'content-type', b'application/x-www-form-urlencoded'), (b'host', b'example.com'), (b'user-agent', b'Agent'), (b'baggage', b'rollbar.session.id=abcde, rollbar.execution.scope.id = fghij'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/api/test', 'path_params': {'param': 'test'}, 'query_params': { 'format': 'json', 'param1': 'value1', 'param2': 'value2', }, 'query_string': b'format=json¶m1=value1¶m2=value2', 'scheme': 'http', 'server': ('example.com', 80), 'url': {'path': 'example.com'}, } data = {} request = Request(scope) rollbar._add_request_data(data, request) rollbar._add_session_data(data) self.assertEqual(data['attributes'], [ {'key': 'session_id', 'value': 'abcde'}, {'key': 'execution_scope_id', 'value': 'fghij'}, ]) def test_starlette_request_data_with_consumed_body(self): try: from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') from rollbar.lib._async import async_receive, run rollbar.SETTINGS['include_request_body'] = True body = b'body body body' scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'content-type', b'text/html'), (b'content-length', str(len(body)).encode('latin-1')), ], 'method': 'GET', 'path': '/api/test', 'query_string': b'', } receive = async_receive( {'type': 'http.request', 'body': body, 'mode_body': False} ) request = Request(scope, receive) # Consuming body in Starlette middleware is currently disabled run(request.body()) # await request.body() data = rollbar._build_starlette_request_data(request) self.assertEqual(data['body'], body.decode('latin-1')) def test_starlette_request_data_empty_values(self): try: from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'content-type', b'text/html'), ], 'method': 'GET', 'query_string': b'', 'path': '', } request = Request(scope) data = rollbar._build_starlette_request_data(request) self.assertFalse('GET' in data) self.assertFalse('url' in data) self.assertFalse('params' in data) self.assertTrue('headers' in data) self.assertEqual(data['user_ip'], scope['client'][0]) self.assertEqual(data['method'], scope['method']) def test_fastapi_request_data(self): try: from fastapi.requests import Request except ImportError: self.skipTest('Requires FastAPI to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'accept', b'*/*'), (b'content-type', b'application/x-www-form-urlencoded'), (b'host', b'example.com'), (b'user-agent', b'Agent'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/api/test', 'path_params': {'param': 'test'}, 'query_params': { 'format': 'json', 'param1': 'value1', 'param2': 'value2', }, 'query_string': b'format=json¶m1=value1¶m2=value2', 'scheme': 'http', 'server': ('example.com', 80), 'url': {'path': 'example.com'}, } request = Request(scope) data = rollbar._build_fastapi_request_data(request) self.assertEqual(data['url'], 'http://example.com/api/test?format=json¶m1=value1¶m2=value2') self.assertEqual(data['user_ip'], '127.0.0.1') self.assertEqual(data['method'], 'GET') self.assertDictEqual(data['params'], {'param': 'test'}) self.assertDictEqual(data['GET'], {'format': 'json', 'param1': 'value1', 'param2': 'value2'}) self.assertDictEqual( data['headers'], { 'accept': '*/*', 'content-type': 'application/x-www-form-urlencoded', 'host': 'example.com', 'user-agent': 'Agent', }, ) def test_fastapi_request_baggage_headers(self): try: from fastapi.requests import Request except ImportError: self.skipTest('Requires FastAPI to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'accept', b'*/*'), (b'content-type', b'application/x-www-form-urlencoded'), (b'host', b'example.com'), (b'user-agent', b'Agent'), (b'baggage', b'rollbar.session.id=abcde, rollbar.execution.scope.id = fghij'), ], 'http_version': '1.1', 'method': 'GET', 'path': '/api/test', 'path_params': {'param': 'test'}, 'query_params': { 'format': 'json', 'param1': 'value1', 'param2': 'value2', }, 'query_string': b'format=json¶m1=value1¶m2=value2', 'scheme': 'http', 'server': ('example.com', 80), 'url': {'path': 'example.com'}, } data = {} request = Request(scope) rollbar._add_request_data(data, request) rollbar._add_session_data(data) self.assertEqual(data['attributes'], [ {'key': 'session_id', 'value': 'abcde'}, {'key': 'execution_scope_id', 'value': 'fghij'}, ]) def test_fastapi_request_data_with_consumed_body(self): try: from fastapi import Request except ImportError: self.skipTest('Requires FastAPI to be installed') from rollbar.lib._async import async_receive, run rollbar.SETTINGS['include_request_body'] = True body = b'body body body' scope = { 'type': 'http', 'headers': [ (b'content-type', b'text/html'), (b'content-length', str(len(body)).encode('latin-1')), ], 'method': 'GET', 'path': '/api/test', 'query_string': b'', } receive = async_receive( {'type': 'http.request', 'body': body, 'mode_body': False} ) request = Request(scope, receive) # Consuming body in FastAPI middlewares is currently disabled run(request.body()) # await request.body() data = rollbar._build_fastapi_request_data(request) self.assertEqual(data['body'], body.decode('latin-1')) def test_fastapi_request_data_empty_values(self): try: from fastapi import Request except ImportError: self.skipTest('Requires FastAPI to be installed') scope = { 'type': 'http', 'client': ('127.0.0.1', 1453), 'headers': [ (b'content-type', b'text/html'), ], 'method': 'GET', 'query_string': b'', 'path': '', } request = Request(scope) data = rollbar._build_fastapi_request_data(request) self.assertFalse('GET' in data) self.assertFalse('url' in data) self.assertFalse('params' in data) self.assertTrue('headers' in data) self.assertEqual(data['user_ip'], scope['client'][0]) self.assertEqual(data['method'], scope['method']) def test_django_build_person_data(self): try: import django from django.conf import settings except ImportError: self.skipTest('Requires Django to be installed') if not settings.configured: settings.configure( INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes'], SERVER_NAME = 'example.com', ) if django.VERSION >= (1, 7): django.setup() from django.contrib.auth.models import User from django.http.request import HttpRequest request = HttpRequest() request.user = User() request.user.id = 123 request.user.username = 'admin' request.user.email = 'admin@example.org' data = rollbar._build_person_data(request) self.assertDictEqual( data, {'id': '123', 'username': 'admin', 'email': 'admin@example.org'} ) def test_django_baggage_headers(self): try: import django from django.conf import settings except ImportError: self.skipTest('Requires Django to be installed') if not settings.configured: settings.configure( INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes'], DEFAULT_CHARSET='utf-8', ALLOWED_HOSTS = ['example.com'], ) django.setup() from django.http.request import HttpRequest request = HttpRequest() request.META['HTTP_BAGGAGE'] = 'rollbar.session.id=abcde, rollbar.execution.scope.id = fghij' request.META['SERVER_NAME'] = 'example.com' request.META['SERVER_PORT'] = 80 request.META['REMOTE_ADDR'] = '0.0.0.0' data = dict() rollbar._add_request_data(data, request) rollbar._add_session_data(data) self.assertEqual(data['attributes'], [ {'key': 'session_id', 'value': 'abcde'}, {'key': 'execution_scope_id', 'value': 'fghij'}, ]) def test_starlette_build_person_data_if_user_authenticated(self): try: from starlette.authentication import SimpleUser from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') # Implement interface with the id attribute class User(SimpleUser): counter = 0 def __init__(self, username, email): super().__init__(username) self.email = email User.counter += 1 self.id = User.counter scope = {'type': 'http'} request = Request(scope) # Make the user authenticated request.scope['user'] = User('admin', 'admin@example.org') data = rollbar._build_person_data(request) self.assertDictEqual( data, {'id': '1', 'username': 'admin', 'email': 'admin@example.org'} ) def test_starlette_failsafe_build_person_data_if_user_not_authenticated(self): try: from starlette.requests import Request except ImportError: self.skipTest('Requires Starlette to be installed') scope = {'type': 'http'} request = Request(scope) data = rollbar._build_person_data(request) self.assertIsNone(data) @unittest.skipUnless(sys.version_info >= (3, 6), 'Python3.6+ required') def test_get_request_starlette_middleware(self): try: from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.responses import PlainTextResponse from starlette.routing import Route from starlette.testclient import TestClient except ImportError: self.skipTest('Requires Starlette package') from rollbar.contrib.starlette import ReporterMiddleware def root(starlette_request): current_request = rollbar.get_request() self.assertEqual(get_public_attrs(current_request), get_public_attrs(starlette_request)) return PlainTextResponse("bye bye") routes = [Route('/{param}', root)] middleware = [Middleware(ReporterMiddleware)] app = Starlette(routes=routes, middleware=middleware) client = TestClient(app) response = client.get('/test?param1=value1¶m2=value2') self.assertEqual(response.status_code, 200) @unittest.skipUnless(sys.version_info >= (3, 6), 'Python3.6+ required') def test_get_request_starlette_logger(self): try: from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.responses import PlainTextResponse from starlette.routing import Route from starlette.testclient import TestClient except ImportError: self.skipTest('Requires Starlette package') from rollbar.contrib.starlette import ReporterMiddleware def root(starlette_request): current_request = rollbar.get_request() self.assertEqual(get_public_attrs(current_request), get_public_attrs(starlette_request)) return PlainTextResponse("bye bye") routes = [Route('/{param}', root)] middleware = [Middleware(ReporterMiddleware)] app = Starlette(routes=routes, middleware=middleware) client = TestClient(app) response = client.get('/test?param1=value1¶m2=value2') self.assertEqual(response.status_code, 200) @unittest.skipUnless(sys.version_info >= (3, 6), 'Python3.6+ required') def test_get_request_fastapi_middleware(self): try: from fastapi import FastAPI, Request from fastapi.testclient import TestClient except ImportError: self.skipTest('Requires FastaAPI package') from rollbar.contrib.fastapi import ReporterMiddleware app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/{param}') def root(param, fastapi_request: Request): current_request = rollbar.get_request() self.assertEqual(get_public_attrs(current_request), get_public_attrs(fastapi_request)) root = fastapi_add_route_with_request_param( app, root, '/{param}', 'fastapi_request' ) client = TestClient(app) response = client.get('/test?param1=value1¶m2=value2') self.assertEqual(response.status_code, 200) @unittest.skipUnless(sys.version_info >= (3, 6), 'Python3.6+ required') def test_get_request_fastapi_logger(self): try: from fastapi import FastAPI, Request from fastapi.testclient import TestClient except ImportError: self.skipTest('Requires FastaAPI package') from rollbar.contrib.fastapi import ReporterMiddleware app = FastAPI() app.add_middleware(ReporterMiddleware) @app.get('/{param}') def root(fastapi_request: Request): current_request = rollbar.get_request() self.assertEqual(get_public_attrs(current_request), get_public_attrs(fastapi_request)) root = fastapi_add_route_with_request_param( app, root, '/{param}', 'fastapi_request' ) client = TestClient(app) response = client.get('/test?param1=value1¶m2=value2') self.assertEqual(response.status_code, 200) @unittest.skipUnless(sys.version_info >= (3, 6), 'Python3.6+ required') def test_get_request_fastapi_router(self): try: import fastapi from fastapi import FastAPI, Request from fastapi.testclient import TestClient except ImportError: self.skipTest('Requires FastAPI package') from rollbar.contrib.fastapi import add_to as rollbar_add_to if fastapi.__version__ < '0.41.0': self.skipTest('Requires FastAPI 0.41.0+') app = FastAPI() rollbar_add_to(app) @app.get('/{param}') def root(fastapi_request: Request): current_request = rollbar.get_request() self.assertEqual(get_public_attrs(current_request), get_public_attrs(fastapi_request)) root = fastapi_add_route_with_request_param( app, root, '/{param}', 'fastapi_request' ) client = TestClient(app) response = client.get('/test?param1=value1¶m2=value2') self.assertEqual(response.status_code, 200) @mock.patch('rollbar.send_payload') def test_report_exception(self, send_payload): def _raise(): try: raise Exception('foo') except: rollbar.report_exc_info() _raise() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) self.assertIn('trace', payload['data']['body']) self.assertNotIn('trace_chain', payload['data']['body']) self.assertIn('exception', payload['data']['body']['trace']) self.assertEqual(payload['data']['body']['trace']['exception']['message'], 'foo') self.assertEqual(payload['data']['body']['trace']['exception']['class'], 'Exception') self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('locals', payload['data']['body']['trace']['frames'][-1]) @mock.patch('rollbar._post_api') def test_lambda_function_good(self, _post_api): rollbar.SETTINGS['handler'] = 'thread' fake_event = {'a': 42} fake_context = MockLambdaContext(99) @rollbar.lambda_function def my_lambda_func(event, context): return [event['a'], context.x] result = my_lambda_func(fake_event, fake_context) self.assertEqual(len(result), 2) self.assertEqual(result[0], 42) self.assertEqual(result[1], 99) self.assertEqual(_post_api.called, False) rollbar._CURRENT_LAMBDA_CONTEXT = None rollbar.SETTINGS['handler'] = 'blocking' @mock.patch('rollbar._post_api') def test_lambda_function_bad(self, _post_api): rollbar.SETTINGS['handler'] = 'thread' fake_event = {'a': 42} fake_context = MockLambdaContext(99) @rollbar.lambda_function def my_lambda_func(event, context): raise event['a'] result = None try: result = my_lambda_func(fake_event, fake_context) except: pass self.assertEqual(result, None) self.assertEqual(_post_api.called, True) rollbar._CURRENT_LAMBDA_CONTEXT = None rollbar.SETTINGS['handler'] = 'blocking' @mock.patch('rollbar._post_api') def test_lambda_function_method_good(self, _post_api): rollbar.SETTINGS['handler'] = 'thread' fake_event = {'a': 42} fake_context = MockLambdaContext(99) class LambdaClass(object): def __init__(self): self.a = 13 def my_lambda_func(self, event, context): return [event['a'], context.x, self.a] app = LambdaClass() app.my_lambda_func = rollbar.lambda_function(app.my_lambda_func) result = app.my_lambda_func(fake_event, fake_context) self.assertEqual(len(result), 3) self.assertEqual(result[0], 42) self.assertEqual(result[1], 99) self.assertEqual(result[2], 13) self.assertEqual(_post_api.called, False) rollbar._CURRENT_LAMBDA_CONTEXT = None rollbar.SETTINGS['handler'] = 'blocking' @mock.patch('rollbar._post_api') def test_lambda_function_method_bad(self, _post_api): rollbar.SETTINGS['handler'] = 'thread' fake_event = {'a': 42} fake_context = MockLambdaContext(99) class LambdaClass(object): def __init__(self): self.a = 13 def my_lambda_func(self, event, context): raise self.a app = LambdaClass() app.my_lambda_func = rollbar.lambda_function(app.my_lambda_func) result = None try: result = app.my_lambda_func(fake_event, fake_context) except: pass self.assertEqual(result, None) self.assertEqual(_post_api.called, True) rollbar._CURRENT_LAMBDA_CONTEXT = None rollbar.SETTINGS['handler'] = 'blocking' @mock.patch('rollbar.send_payload') def test_report_exception_with_cause(self, send_payload): def _raise_cause(): bar_local = 'bar' raise CauseException('bar') def _raise_ex(): try: _raise_cause() except CauseException as cause: # python2 won't automatically assign this traceback... exc_info = sys.exc_info() setattr(cause, '__traceback__', exc_info[2]) try: foo_local = 'foo' # in python3 this would normally be expressed as # raise Exception('foo') from cause e = Exception('foo') setattr(e, '__cause__', cause) # PEP-3134 raise e except: rollbar.report_exc_info() _raise_ex() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) self.assertNotIn('trace', payload['data']['body']) self.assertIn('trace_chain', payload['data']['body']) self.assertEqual(2, len(payload['data']['body']['trace_chain'])) self.assertIn('exception', payload['data']['body']['trace_chain'][0]) self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['message'], 'foo') self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['class'], 'Exception') self.assertEqual(payload['data']['body']['trace_chain'][0]['frames'][-1]['locals']['foo_local'], 'foo') self.assertIn('exception', payload['data']['body']['trace_chain'][1]) self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['message'], 'bar') self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['class'], 'CauseException') self.assertEqual(payload['data']['body']['trace_chain'][1]['frames'][-1]['locals']['bar_local'], 'bar') @mock.patch('rollbar.send_payload') def test_report_exception_with_same_exception_as_cause(self, send_payload): cause_exc = CauseException('bar') def _raise_cause(): bar_local = 'bar' raise cause_exc def _raise_ex(): try: _raise_cause() except CauseException as cause: # python2 won't automatically assign this traceback... exc_info = sys.exc_info() setattr(cause, '__traceback__', exc_info[2]) try: foo_local = 'foo' # in python3 this would normally be expressed as # raise cause from cause setattr(cause, '__cause__', cause) # PEP-3134 raise cause except: rollbar.report_exc_info() ex_raiser = threading.Thread(target=_raise_ex) ex_raiser.daemon = True ex_raiser.start() # 0.5 seconds ought be enough for any modern computer to get into the # cyclical parts of the code, but not so long as to collect a lot of # objects in memory ex_raiser.join(timeout=0.5) if ex_raiser.is_alive(): # This breaks the circular reference, allowing thread to exit and # to be joined cause_exc.__cause__ = None ex_raiser.join() self.fail('Cyclic reference in rollbar._walk_trace_chain()') self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) self.assertNotIn('trace', payload['data']['body']) self.assertIn('trace_chain', payload['data']['body']) self.assertEqual(2, len(payload['data']['body']['trace_chain'])) self.assertIn('exception', payload['data']['body']['trace_chain'][0]) self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['message'], 'bar') self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['class'], 'CauseException') frames = payload['data']['body']['trace_chain'][0]['frames'] self.assertEqual(payload['data']['body']['trace_chain'][0]['frames'][0]['locals']['foo_local'], 'foo') self.assertIn('exception', payload['data']['body']['trace_chain'][1]) self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['message'], 'bar') self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['class'], 'CauseException') self.assertEqual(payload['data']['body']['trace_chain'][1]['frames'][-1]['locals']['bar_local'], 'bar') @mock.patch('rollbar.send_payload') def test_report_exception_with_context(self, send_payload): def _raise_context(): bar_local = 'bar' raise CauseException('bar') def _raise_ex(): try: _raise_context() except CauseException as context: # python2 won't automatically assign this traceback... exc_info = sys.exc_info() setattr(context, '__traceback__', exc_info[2]) try: foo_local = 'foo' # in python3 __context__ is automatically set when an exception is raised in an except block e = Exception('foo') setattr(e, '__context__', context) # PEP-3134 raise e except: rollbar.report_exc_info() _raise_ex() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) self.assertNotIn('trace', payload['data']['body']) self.assertIn('trace_chain', payload['data']['body']) self.assertEqual(2, len(payload['data']['body']['trace_chain'])) self.assertIn('exception', payload['data']['body']['trace_chain'][0]) self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['message'], 'foo') self.assertEqual(payload['data']['body']['trace_chain'][0]['exception']['class'], 'Exception') self.assertEqual(payload['data']['body']['trace_chain'][0]['frames'][-1]['locals']['foo_local'], 'foo') self.assertIn('exception', payload['data']['body']['trace_chain'][1]) self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['message'], 'bar') self.assertEqual(payload['data']['body']['trace_chain'][1]['exception']['class'], 'CauseException') self.assertEqual(payload['data']['body']['trace_chain'][1]['frames'][-1]['locals']['bar_local'], 'bar') @mock.patch('rollbar.send_payload') def test_exception_filters(self, send_payload): rollbar.SETTINGS['exception_level_filters'] = [ (OSError, 'ignored'), ('rollbar.ApiException', 'ignored'), ('bogus.DoesntExist', 'ignored'), ] def _raise_exception(): try: raise Exception('foo') except: rollbar.report_exc_info() def _raise_os_error(): try: raise OSError('bar') except: rollbar.report_exc_info() def _raise_api_exception(): try: raise rollbar.ApiException('bar') except: rollbar.report_exc_info() _raise_exception() self.assertTrue(send_payload.called) _raise_os_error() self.assertEqual(1, send_payload.call_count) _raise_api_exception() self.assertEqual(1, send_payload.call_count) @mock.patch('rollbar.send_payload') def test_report_messsage(self, send_payload): rollbar.report_message('foo') self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['access_token'], _test_access_token) self.assertIn('body', payload['data']) self.assertIn('message', payload['data']['body']) self.assertIn('body', payload['data']['body']['message']) self.assertEqual(payload['data']['body']['message']['body'], 'foo') @mock.patch('rollbar.send_payload') def test_uuid(self, send_payload): uuid = rollbar.report_message('foo') payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['uuid'], uuid) @mock.patch('rollbar.send_payload') def test_report_exc_info_level(self, send_payload): try: raise Exception('level_error') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'error') try: raise Exception('level_info') except: rollbar.report_exc_info(level='info') self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'info') # payload takes precendence over 'level' try: raise Exception('payload_warn') except: rollbar.report_exc_info(level='info', payload_data={'level': 'warn'}) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'warn') @mock.patch('rollbar.send_payload') def test_report_exc_info_nones(self, send_payload): rollbar.report_exc_info(exc_info=(None, None, None)) self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual(payload['data']['level'], 'error') @mock.patch('rollbar._send_failsafe') @mock.patch('rollbar.lib.transport.post', side_effect=lambda *args, **kw: MockResponse({'status': 'Payload Too Large'}, 413)) def test_trigger_failsafe(self, post, _send_failsafe): rollbar.report_message('derp') self.assertEqual(_send_failsafe.call_count, 1) try: raise Exception('trigger_failsafe') except: rollbar.report_exc_info() self.assertEqual(_send_failsafe.call_count, 2) @mock.patch('rollbar._send_failsafe') @mock.patch('rollbar.lib.transport.post', side_effect=lambda *args, **kw: MockRawResponse('\r\n' \ '502 Bad Gateway\r\n' \ '\r\n' \ '

502 Bad Gateway

\r\n' \ '
nginx
\r\n' \ '\r\n' \ '\r\n', 502)) def test_502_failsafe(self, post, _send_failsafe): rollbar.report_message('derp') # self.assertEqual(_send_failsafe.call_count, 1) try: raise Exception('trigger_failsafe') except: rollbar._post_api('/api/1/item', {'derp'}) @mock.patch('rollbar.send_payload') def test_send_failsafe(self, send_payload): test_uuid = str(uuid.uuid4()) test_host = socket.gethostname() test_data = { 'access_token': _test_access_token, 'data': { 'body': { 'message': { 'body': 'Failsafe from pyrollbar: test message. ' 'Original payload may be found in your server ' 'logs by searching for the UUID.' } }, 'failsafe': True, 'level': 'error', 'custom': { 'orig_host': test_host, 'orig_uuid': test_uuid }, 'environment': rollbar.SETTINGS['environment'], 'internal': True, 'notifier': rollbar.SETTINGS['notifier'] } } rollbar._send_failsafe('test message', test_uuid, test_host) self.assertEqual(send_payload.call_count, 1) self.assertEqual(send_payload.call_args[0][0], test_data) @mock.patch('rollbar.log.exception') @mock.patch('rollbar.send_payload', side_effect=Exception('Monkey Business!')) def test_fail_to_send_failsafe(self, send_payload, mock_log): test_uuid = str(uuid.uuid4()) test_host = socket.gethostname() rollbar._send_failsafe('test message', test_uuid, test_host) self.assertEqual(mock_log.call_count, 1) @unittest.skipUnless(rollbar.AsyncHTTPClient, 'Requires async handler to be installed') @mock.patch('rollbar._send_payload_async') def test_async_handler(self, send_payload_async): def _raise(): try: raise Exception('foo') except: rollbar.report_exc_info() rollbar.SETTINGS['handler'] = 'async' _raise() send_payload_async.assert_called_once() @unittest.skipUnless(rollbar.httpx, 'Requires HTTPX to be installed') @mock.patch('rollbar._send_payload_httpx') def test_httpx_handler(self, send_payload_httpx): def _raise(): try: raise Exception('foo') except: rollbar.report_exc_info() rollbar.SETTINGS['handler'] = 'async' _raise() send_payload_httpx.assert_called_once() @unittest.skipUnless(sys.version_info >= (3, 6), 'assert_called_once support requires Python3.6+') @mock.patch('rollbar._send_payload_thread_pool') def test_thread_pool_handler(self, send_payload_thread_pool): def _raise(): try: raise Exception('foo') except: rollbar.report_exc_info() rollbar.SETTINGS['handler'] = 'thread_pool' _raise() send_payload_thread_pool.assert_called_once() @unittest.skipUnless(sys.version_info >= (3, 2), 'concurrent.futures support requires Python3.2+') def test_thread_pool_submit(self): from rollbar.lib.thread_pool import init_pool, submit init_pool(1) ran = {'nope': True} # dict used so it is not shadowed in run def run(payload_str, access_token): ran['nope'] = False submit(run, 'foo', 'bar') self.assertFalse(ran['nope']) @mock.patch('rollbar.send_payload') def test_args_constructor(self, send_payload): class tmp(object): def __init__(self, arg1): self.arg1 = arg1 foo() try: t = tmp(33) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual(33, payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) @mock.patch('rollbar.send_payload') def test_failed_locals_serialization(self, send_payload): class tmp(object): @property def __class__(self): foo() try: t = tmp() raise Exception('trigger_serialize') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) @mock.patch('rollbar.send_payload') def test_args_lambda_no_args(self, send_payload): _raise = lambda: foo() try: _raise() except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('locals', payload['data']['body']['trace']['frames'][-1]) @mock.patch('rollbar.send_payload') def test_args_lambda_with_args(self, send_payload): _raise = lambda arg1, arg2: foo(arg1, arg2) try: _raise('arg1-value', 'arg2-value') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('arg1-value', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) self.assertEqual('arg2', payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual('arg2-value', payload['data']['body']['trace']['frames'][-1]['locals']['arg2']) @mock.patch('rollbar.send_payload') def test_args_lambda_with_defaults(self, send_payload): _raise = lambda arg1='default': foo(arg1) try: _raise(arg1='arg1-value') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) # NOTE(cory): Lambdas are a bit strange. We treat default values for lambda args # as positional. self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('arg1-value', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) @mock.patch('rollbar.send_payload') def test_args_lambda_with_star_args(self, send_payload): _raise = lambda *args: foo(arg1) try: _raise('arg1-value') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) varargs = payload['data']['body']['trace']['frames'][-1]['varargspec'] self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['locals'][varargs])) self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals'][varargs][0], r'\*+') @mock.patch('rollbar.send_payload') def test_args_lambda_with_star_args_and_args(self, send_payload): _raise = lambda arg1, *args: foo(arg1) try: _raise('arg1-value', 1, 2) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) varargs = payload['data']['body']['trace']['frames'][-1]['varargspec'] self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('arg1-value', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['locals'][varargs])) self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals'][varargs][0], r'\*+') self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals'][varargs][1], r'\*+') @mock.patch('rollbar.send_payload') def test_args_lambda_with_kwargs(self, send_payload): _raise = lambda **kwargs: foo(arg1) try: _raise(arg1='arg1-value', arg2=2) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) keywords = payload['data']['body']['trace']['frames'][-1]['keywordspec'] self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['locals'][keywords])) self.assertEqual('arg1-value', payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['arg1']) self.assertEqual(2, payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['arg2']) @mock.patch('rollbar.send_payload') def test_args_lambda_with_kwargs_and_args(self, send_payload): _raise = lambda arg1, arg2, **kwargs: foo(arg1) try: _raise('a1', 'a2', arg3='arg3-value', arg4=2) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) keywords = payload['data']['body']['trace']['frames'][-1]['keywordspec'] self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('arg2', payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual('a1', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) self.assertEqual('a2', payload['data']['body']['trace']['frames'][-1]['locals']['arg2']) self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['locals'][keywords])) self.assertEqual('arg3-value', payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['arg3']) self.assertEqual(2, payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['arg4']) @mock.patch('rollbar.send_payload') def test_args_lambda_with_kwargs_and_args_and_defaults(self, send_payload): _raise = lambda arg1, arg2, arg3='default-value', **kwargs: foo(arg1) try: _raise('a1', 'a2', arg3='arg3-value', arg4=2) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) keywords = payload['data']['body']['trace']['frames'][-1]['keywordspec'] # NOTE(cory): again, default values are strange for lambdas and we include them as # positional args. self.assertEqual(3, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('arg2', payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual('arg3', payload['data']['body']['trace']['frames'][-1]['argspec'][2]) self.assertEqual('a1', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) self.assertEqual('a2', payload['data']['body']['trace']['frames'][-1]['locals']['arg2']) self.assertEqual('arg3-value', payload['data']['body']['trace']['frames'][-1]['locals']['arg3']) self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['locals'][keywords])) self.assertEqual(2, payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['arg4']) @mock.patch('rollbar.send_payload') def test_args_generators(self, send_payload): def _raise(arg1): for i in range(2): if i > 0: raise Exception() else: yield i try: l = list(_raise('hello world')) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertEqual(1, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('arg1', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual('hello world', payload['data']['body']['trace']['frames'][-1]['locals']['arg1']) @mock.patch('rollbar.send_payload') def test_anonymous_tuple_args(self, send_payload): # Only run this test on Python versions that support it if not _anonymous_tuple_func: return try: _anonymous_tuple_func((1, (2, 3), 4)) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertEqual(4, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual(1, payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertEqual(2, payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual(3, payload['data']['body']['trace']['frames'][-1]['argspec'][2]) self.assertEqual(4, payload['data']['body']['trace']['frames'][-1]['argspec'][3]) self.assertEqual(10, payload['data']['body']['trace']['frames'][-1]['locals']['ret']) @mock.patch('rollbar.send_payload') def test_scrub_defaults(self, send_payload): def _raise(password='sensitive', clear='text'): headers = { 'Authorization': 'bearer 123' } raise Exception() try: _raise() except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('kwargs', payload['data']['body']['trace']['frames'][-1]['locals']) self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['argspec'])) self.assertEqual('password', payload['data']['body']['trace']['frames'][-1]['argspec'][0]) self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['password'], r'\*+') self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['headers']['Authorization'], r'\*+') self.assertEqual('clear', payload['data']['body']['trace']['frames'][-1]['argspec'][1]) self.assertEqual('text', payload['data']['body']['trace']['frames'][-1]['locals']['clear']) @mock.patch('rollbar.send_payload') def test_dont_scrub_star_args(self, send_payload): rollbar.SETTINGS['locals']['scrub_varargs'] = False def _raise(*args): raise Exception() try: _raise('sensitive', 'text') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('locals', payload['data']['body']['trace']['frames'][-1]) varargspec = payload['data']['body']['trace']['frames'][-1]['varargspec'] self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['locals'][varargspec])) self.assertEqual(payload['data']['body']['trace']['frames'][-1]['locals'][varargspec][0], 'sensitive') self.assertEqual(payload['data']['body']['trace']['frames'][-1]['locals'][varargspec][1], 'text') @mock.patch('rollbar.send_payload') def test_scrub_kwargs(self, send_payload): def _raise(**kwargs): raise Exception() try: _raise(password='sensitive', clear='text') except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertNotIn('argspec', payload['data']['body']['trace']['frames'][-1]) self.assertNotIn('varargspec', payload['data']['body']['trace']['frames'][-1]) self.assertIn('keywordspec', payload['data']['body']['trace']['frames'][-1]) keywords = payload['data']['body']['trace']['frames'][-1]['keywordspec'] self.assertEqual(2, len(payload['data']['body']['trace']['frames'][-1]['locals'][keywords])) self.assertIn('password', payload['data']['body']['trace']['frames'][-1]['locals'][keywords]) self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['password'], r'\*+') self.assertIn('clear', payload['data']['body']['trace']['frames'][-1]['locals'][keywords]) self.assertEqual('text', payload['data']['body']['trace']['frames'][-1]['locals'][keywords]['clear']) @mock.patch('rollbar.send_payload') def test_scrub_locals(self, send_payload): invalid_b64 = b'CuX2JKuXuLVtJ6l1s7DeeQ==' invalid = base64.b64decode(invalid_b64) def _raise(): # Make sure that the _invalid local variable makes its # way into the payload even if its value cannot be serialized # properly. _invalid = invalid # Make sure the Password field gets scrubbed even though its # original value could not be serialized properly. Password = invalid password = 'sensitive' raise Exception((_invalid, Password, password)) try: _raise() except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['password'], r'\*+') self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['Password'], r'\*+') self.assertIn('_invalid', payload['data']['body']['trace']['frames'][-1]['locals']) undecodable_message = '' % ('bytes', base64.b64encode(invalid).decode('ascii')) self.assertEqual(undecodable_message, payload['data']['body']['trace']['frames'][-1]['locals']['_invalid']) @mock.patch('rollbar.send_payload') def test_scrub_namedtuple(self, send_payload): SomeTuple = namedtuple('SomeTuple', ['password', 'some_field']) def _raise(): Data = SomeTuple(password='clear_text', some_field='some_field') password = 'sensitive' raise Exception((Data, password)) try: _raise() except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['password'], r'\*+') self.assertRegex(payload['data']['body']['trace']['frames'][-1]['locals']['Data'], 'password=\'\*+\'') @mock.patch('rollbar.send_payload') def test_scrub_nans(self, send_payload): def _raise(): infinity = float('Inf') negative_infinity = float('-Inf') not_a_number = float('NaN') raise Exception() try: _raise() except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertEqual('', payload['data']['body']['trace']['frames'][-1]['locals']['infinity']) self.assertEqual('', payload['data']['body']['trace']['frames'][-1]['locals']['negative_infinity']) self.assertEqual('', payload['data']['body']['trace']['frames'][-1]['locals']['not_a_number']) @mock.patch('rollbar.send_payload') def test_scrub_self_referencing(self, send_payload): def _raise(obj): raise Exception() try: obj = {'x': 42.3} obj['child'] = { 'parent': obj } # NOTE(cory): We copy the dict here so that we don't produce a circular reference # from the _rase() args. _raise(dict(obj)) except: rollbar.report_exc_info() self.assertEqual(send_payload.called, True) payload = send_payload.call_args[0][0] self.assertTrue( (isinstance(payload['data']['body']['trace']['frames'][-1]['locals']['obj'], dict) and 'child' in payload['data']['body']['trace']['frames'][-1]['locals']['obj']) or (isinstance(payload['data']['body']['trace']['frames'][-1]['locals']['obj'], string_types) and payload['data']['body']['trace']['frames'][-1]['locals']['obj'].startswith('' class SerializableTransformTest(BaseTest): def _assertSerialized(self, start, expected, safe_repr=True, safelist=None, skip_id_check=False): serializable = SerializableTransform(safe_repr=safe_repr, safelist_types=safelist) result = transforms.transform(start, serializable) """ #print start print result print expected """ if not skip_id_check: self.assertNotEqual(id(result), id(expected)) self.assertEqual(type(expected), type(result)) if isinstance(result, Mapping): self.assertDictEqual(result, expected) elif isinstance(result, tuple): self.assertTupleEqual(result, expected) elif isinstance(result, (list, set)): self.assertListEqual(result, expected) else: self.assertEqual(result, expected) def test_simple_dict(self): start = { 'hello': 'world', '1': 2, } expected = copy.deepcopy(start) self._assertSerialized(start, expected) def test_enum(self): class EnumTest(enum.Enum): one = 1 two = 2 start = EnumTest.one expected = "" self._assertSerialized(start, expected) def test_enum_no_safe_repr(self): class EnumTest(enum.Enum): one = 1 two = 2 start = EnumTest.one expected = '' self._assertSerialized(start, expected, safe_repr=False) def test_int_enum(self): class EnumTest(enum.IntEnum): one = 1 two = 2 start = EnumTest.one expected = "" self._assertSerialized(start, expected) def test_int_enum_no_safe_repr(self): class EnumTest(enum.IntEnum): one = 1 two = 2 start = EnumTest.one expected = '' self._assertSerialized(start, expected, safe_repr=False) def test_encode_dict_with_invalid_utf8(self): start = { 'invalid': invalid } expected = copy.copy(start) expected['invalid'] = undecodable_repr self._assertSerialized(start, expected) def test_encode_utf8(self): start = invalid expected = undecodable_repr self._assertSerialized(start, expected) def test_encode_None(self): start = None expected = None self._assertSerialized(start, expected, skip_id_check=True) def test_encode_float(self): start = 3.14 expected = 3.14 self._assertSerialized(start, expected, skip_id_check=True) def test_encode_float_nan(self): start = float('nan') expected = '' self._assertSerialized(start, expected, skip_id_check=True) def test_encode_float_infinity(self): start = float('inf') expected = '' self._assertSerialized(start, expected, skip_id_check=True) def test_encode_float_neg_infinity(self): start = float('-inf') expected = '' self._assertSerialized(start, expected, skip_id_check=True) def test_encode_int(self): start = 33 expected = 33 self._assertSerialized(start, expected, skip_id_check=True) def test_encode_empty_tuple(self): start = () expected = () skip_id_check = False # different behavior in 3.11 if sys.version_info >= (3, 11): skip_id_check = True self._assertSerialized(start, expected, skip_id_check=skip_id_check) def test_encode_empty_list(self): start = [] expected = [] self._assertSerialized(start, expected) def test_encode_empty_dict(self): start = {} expected = {} self._assertSerialized(start, expected) def test_encode_namedtuple(self): MyType = collections.namedtuple('MyType', ('field_1', 'field_2')) nt = MyType(field_1='this is field 1', field_2=invalid) start = nt expected = "" % undecodable_repr self._assertSerialized(start, expected) def test_encode_tuple_with_bytes(self): start = ('hello', 'world', invalid) expected = list(start) expected[2] = undecodable_repr self._assertSerialized(start, tuple(expected)) def test_encode_list_with_bytes(self): start = ['hello', 'world', invalid] expected = list(start) expected[2] = undecodable_repr self._assertSerialized(start, expected) def test_encode_dict_with_bytes(self): start = {'hello': 'world', 'invalid': invalid} expected = copy.deepcopy(start) expected['invalid'] = undecodable_repr self._assertSerialized(start, expected) def test_encode_dict_with_bytes_key(self): start = {'hello': 'world', invalid: 'works?'} expected = copy.deepcopy(start) expected[undecodable_repr] = 'works?' del expected[invalid] self._assertSerialized(start, expected) def test_encode_with_rollbar_repr(self): class CustomRepr(object): def __rollbar_repr__(self): return 'hello' start = {'hello': 'world', 'custom': CustomRepr()} expected = copy.deepcopy(start) expected['custom'] = 'hello' self._assertSerialized(start, expected) def test_encode_with_custom_repr_no_safelist(self): class CustomRepr(object): def __repr__(self): return 'hello' start = {'hello': 'world', 'custom': CustomRepr()} expected = copy.deepcopy(start) expected['custom'] = str(CustomRepr) self._assertSerialized(start, expected) def test_encode_with_custom_repr_no_safelist_no_safe_repr(self): class CustomRepr(object): def __repr__(self): return 'hello' start = {'hello': 'world', 'custom': CustomRepr()} expected = copy.deepcopy(start) expected['custom'] = 'hello' self._assertSerialized(start, expected, safe_repr=False) def test_encode_with_custom_repr_safelist(self): class CustomRepr(object): def __repr__(self): return 'hello' start = {'hello': 'world', 'custom': CustomRepr()} expected = copy.deepcopy(start) expected['custom'] = 'hello' self._assertSerialized(start, expected, safelist=[CustomRepr]) def test_encode_with_custom_repr_returns_bytes(self): class CustomRepr(object): def __repr__(self): return b'hello' start = {'hello': 'world', 'custom': CustomRepr()} serializable = SerializableTransform(safelist_types=[CustomRepr]) result = transforms.transform(start, serializable) self.assertRegex(result['custom'], "") def test_encode_with_custom_repr_returns_object(self): class CustomRepr(object): def __repr__(self): return {'hi': 'there'} start = {'hello': 'world', 'custom': CustomRepr()} serializable = SerializableTransform(safelist_types=[CustomRepr]) result = transforms.transform(start, serializable) self.assertRegex(result['custom'], "") def test_encode_with_custom_repr_returns_unicode(self): class CustomRepr(object): def __repr__(self): return SNOWMAN_UNICODE start = {'hello': 'world', 'custom': CustomRepr()} expected = copy.deepcopy(start) expected['custom'] = SNOWMAN_UNICODE self._assertSerialized(start, expected, safelist=[CustomRepr]) def test_encode_with_bad_repr_doesnt_die(self): class CustomRepr(object): def __repr__(self): assert False start = {'hello': 'world', 'custom': CustomRepr()} serializable = SerializableTransform(safelist_types=[CustomRepr]) result = transforms.transform(start, serializable) self.assertRegex(result['custom'], "") def test_encode_with_bad_str_doesnt_die(self): class UnStringableException(Exception): def __str__(self): raise Exception('asdf') class CustomRepr(object): def __repr__(self): raise UnStringableException() start = {'hello': 'world', 'custom': CustomRepr()} serializable = SerializableTransform(safelist_types=[CustomRepr]) result = transforms.transform(start, serializable) self.assertRegex(result['custom'], "") ================================================ FILE: rollbar/test/test_session.py ================================================ import asyncio import threading import unittest from rollbar.test import BaseTest from rollbar.lib import session class SessionTest(BaseTest): def test_session_threading(self): results = [] def worker(headers): session.set_current_session(headers) results.append(session.get_current_session()) t1 = threading.Thread(target=worker, args=({ 'Baggage': 'rollbar.session.id=abc123,rollbar.execution.scope.id=123abc' },)) t2 = threading.Thread(target=worker, args=({ 'Baggage': 'rollbar.session.id=def456,rollbar.execution.scope.id=456def' },)) t3 = threading.Thread(target=worker, args=({},)) t1.start() t2.start() t3.start() t1.join() t2.join() t3.join() self.assertEqual(len(results), 3) self.assertEqual(results[0], [ {'key': 'session_id', 'value': 'abc123'}, {'key': 'execution_scope_id', 'value': '123abc'}, ]) self.assertEqual(results[1], [ {'key': 'session_id', 'value': 'def456'}, {'key': 'execution_scope_id', 'value': '456def'}, ]) # For the thread with empty headers, we should still get a session ID generated. self.assertEqual(results[2][0]['key'], 'execution_scope_id') self.assertEqual(len(results[2][0]['value']), 32) def test_parse_session_request_baggage_headers(self): headers = { 'Baggage': 'rollbar.session.id=abc123, rollbar.execution.scope.id=def456', } attributes = session.parse_session_request_baggage_headers(headers) self.assertEqual([ {'key': 'session_id', 'value': 'abc123'}, {'key': 'execution_scope_id', 'value': 'def456'}, ], attributes) def test_parse_session_request_baggage_headers_lower(self): headers = { 'baggage': 'rollbar.execution.scope.id=abc123', } attributes = session.parse_session_request_baggage_headers(headers) self.assertEqual([ {'key': 'execution_scope_id', 'value': 'abc123'}, ], attributes) def test_parse_session_request_baggage_headers_scope_only(self): headers = { 'baggage': 'rollbar.execution.scope.id=def456', } attributes = session.parse_session_request_baggage_headers(headers) self.assertEqual([ {'key': 'execution_scope_id', 'value': 'def456'}, ], attributes) def test_parse_session_request_baggage_headers_empty(self): headers = { 'baggage': '', } attributes = session.parse_session_request_baggage_headers(headers) self.assertEqual(len(attributes), 0) def test_parse_session_request_baggage_headers_empty_generate(self): headers = { 'baggage': '', } attributes = session.parse_session_request_baggage_headers(headers, generate_missing=True) # Ensure that we still generate an execution scope ID if the baggage header is empty. self.assertEqual(len(attributes), 1) self.assertEqual(attributes[0]['key'], 'execution_scope_id') self.assertEqual(len(attributes[0]['value']), 32) def test_parse_session_request_baggage_headers_other(self): headers = { 'baggage': 'some.id=xyz789', } attributes = session.parse_session_request_baggage_headers(headers) self.assertEqual(len(attributes), 0) def test_parse_session_request_baggage_headers_other_generate(self): headers = { 'baggage': 'some.id=xyz789', } attributes = session.parse_session_request_baggage_headers(headers, generate_missing=True) # Ensure that we still generate an execution scope ID if the baggage header doesn't contain the expected keys. self.assertEqual(len(attributes), 1) self.assertEqual(attributes[0]['key'], 'execution_scope_id') self.assertEqual(len(attributes[0]['value']), 32) def test_build_new_session_attributes(self): attributes = session._build_new_scope_attributes() self.assertEqual(len(attributes), 1) self.assertEqual(attributes[0]['key'], 'execution_scope_id') self.assertEqual(len(attributes[0]['value']), 32) def test_new_session_id(self): session_id = session._new_scope_id() self.assertEqual(len(session_id), 32) class TestSessionAsync(unittest.IsolatedAsyncioTestCase): """ Test that session data is properly isolated across async tasks. """ async def test_session_async(self): results = {} async def worker(headers, key): session.set_current_session(headers) await asyncio.sleep(0.1) # Force a context switch to test async isolation results[key] = session.get_current_session() await asyncio.gather( worker({'Baggage': 'rollbar.session.id=abc123,rollbar.execution.scope.id=123abc'}, 'task1'), worker({'Baggage': 'rollbar.session.id=def456,rollbar.execution.scope.id=456def'}, 'task2'), worker({}, 'task3'), ) self.assertEqual(results['task1'], [ {'key': 'session_id', 'value': 'abc123'}, {'key': 'execution_scope_id', 'value': '123abc'}, ]) self.assertEqual(results['task2'], [ {'key': 'session_id', 'value': 'def456'}, {'key': 'execution_scope_id', 'value': '456def'}, ]) # For the task with empty headers, we should still get a session ID generated. self.assertEqual(results['task3'][0]['key'], 'execution_scope_id') self.assertEqual(len(results['task3'][0]['value']), 32) ================================================ FILE: rollbar/test/test_shortener_transform.py ================================================ import sys from array import array from collections import deque from rollbar import DEFAULT_LOCALS_SIZES, SETTINGS from rollbar.lib import transforms from rollbar.lib.transforms.shortener import ShortenerTransform from rollbar.test import BaseTest class TestClassWithAVeryVeryVeryVeryVeryVeryVeryLongName: pass class KeyMemShortenerTransform(ShortenerTransform): """ A shortener that just stores the keys. """ keysUsed = [] def default(self, o, key=None): self.keysUsed.append((key, o)) return super(KeyMemShortenerTransform, self).default(o, key=key) class ShortenerTransformTest(BaseTest): def setUp(self): self.shortener = ShortenerTransform(keys=[('shorten',)], **DEFAULT_LOCALS_SIZES) def test_shorten_string(self): original = 'x' * 120 shortened = '{}...{}'.format('x'*48, 'x'*49) self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_long(self): original = 17955682733916468498414734863645002504519623752387 shortened = '179556827339164684...5002504519623752387' self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_mapping(self): original = { 'one': 'one', 'two': 'two', 'three': 'three', 'four': 'four', 'five': 'five', 'six': 'six', 'seven': 'seven', 'eight': 'eight', 'nine': 'nine', 'ten': 'ten', 'eleven': 'eleven' } shortened = { 'one': 'one', 'two': 'two', 'three': 'three', 'four': 'four', 'five': 'five', 'six': 'six', 'seven': 'seven', 'eight': 'eight', 'nine': 'nine', 'ten': 'ten', } self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_bytes(self): original = b'\x78' * 120 shortened = b'\x78' * 100 self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_list(self): original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] shortened = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '...'] self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_tuple(self): original = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) shortened = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '...') self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_set(self): original = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} shortened = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '...'} self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_frozenset(self): original = frozenset([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) shortened = frozenset([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '...']) self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_array(self): original = array('l', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) shortened = array('l', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_deque(self): original = deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 15) shortened = deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 15) self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_other(self): original = TestClassWithAVeryVeryVeryVeryVeryVeryVeryLongName() shortened = '' obj = CustomObj() original = obj shortened = '' self.assertEqual(shortened, self.shortener.default(original, ('shorten',))) self.assertEqual(original, self.shortener.default(original, ('nope',))) def test_shorten_frame(self): data = { 'body': { 'trace': { 'frames': [ { "filename": "/path/to/app.py", "lineno": 82, "method": "sub_func", "code": "extra(**kwargs)", "keywordspec": "kwargs", "locals": { "kwargs": { "app": ["foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud"], "extra": { "request": "" } }, "one": { "two": { "three": { "four": { "five": { "six": { "seven": 8, "eight": "nine" }, "ten": "Yep! this should still be here, but it is a little on the " "long side, so we might want to cut it down a bit." } } } }, "a": ["foo", "bar", "baz", "qux", 5, 6, 7, 8, 9, 10, 11, 12], "b": 14071504106566481658450568387453168916351054663, "app_id": 140715046161904, "bar": "im a bar", } } } ] } } } keys = [('body', 'trace', 'frames', '*', 'locals', '*')] shortener = ShortenerTransform(keys=keys, **DEFAULT_LOCALS_SIZES) result = transforms.transform(data, shortener) expected = { 'body': { 'trace': { 'frames': [ { "filename": "/path/to/app.py", "lineno": 82, "method": "sub_func", "code": "extra(**kwargs)", "keywordspec": "kwargs", "locals": { "kwargs": { # Shortened "app": ['foo', 'bar', 'baz', 'qux', 'quux', 'corge', 'grault', 'garply', 'waldo', 'fred', '...'], "extra": { "request": "" } }, "one": { "two": { "three": { "four": { "five": { "six": {'...': '...'}, # Dropped because it is past the maxlevel. # Shortened "ten": "Yep! this should still be here, but it is a litt...long " "side, so we might want to cut it down a bit." } } } }, "a": ['foo', 'bar', 'baz', 'qux', 5, 6, 7, 8, 9, 10, '...'], # Shortened "b": '140715041065664816...7453168916351054663', # Shortened "app_id": 140715046161904, "bar": "im a bar", } } } ] } } } self.assertEqual(result, expected) def test_breadth_first(self): obj = { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } } shortener_instance = KeyMemShortenerTransform( safe_repr=True, keys=[ ('request', 'POST'), ('request', 'json'), ('body', 'request', 'POST'), ('body', 'request', 'json'), ], **SETTINGS['locals']['sizes'] ) transforms.transform(obj, [shortener_instance], key=()) self.assertEqual( shortener_instance.keysUsed, [ ((), { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } }), (("one",), ["four", "five", 6, 7]), (("one", 0), "four"), (("one", 1), "five"), (("one", 2), 6), (("one", 3), 7), (("two",), ("eight", "nine", "ten")), (("two", 0), "eight"), (("two", 1), "nine"), (("two", 2), "ten"), (("three",), { "eleven": 12, "thirteen": 14 }), (("three", "eleven"), 12), (("three", "thirteen"), 14), ], ) ================================================ FILE: rollbar/test/test_transform.py ================================================ import copy import rollbar from rollbar.test import BaseTest from rollbar.lib.transforms import Transform from rollbar.lib.transforms.shortener import ShortenerTransform from rollbar.lib.transforms.scrub_redact import ScrubRedactTransform from rollbar.lib.transforms.serializable import SerializableTransform from rollbar.lib.transforms.scrub import ScrubTransform from rollbar.lib.transforms.scruburl import ScrubUrlTransform _test_access_token = 'aaaabbbbccccddddeeeeffff00001111' _default_settings = copy.deepcopy(rollbar.SETTINGS) class TransformTest(BaseTest): def setUp(self): rollbar._initialized = False rollbar.SETTINGS = copy.deepcopy(_default_settings) rollbar.init(_test_access_token, locals={'enabled': True}, handler='blocking', timeout=12345) def test_default_transforms(self): transforms = {transform.__class__ for transform in rollbar._transforms} self.assertEqual({ ShortenerTransform, ScrubRedactTransform, SerializableTransform, ScrubUrlTransform, }, transforms) def test_add_custom_transform(self): class CustomTransform(Transform): pass rollbar._initialized = False rollbar.SETTINGS = copy.deepcopy(_default_settings) rollbar.init(_test_access_token, locals={'enabled': True}, handler='blocking', timeout=12345, custom_transforms=[CustomTransform()]) transforms = {transform.__class__ for transform in rollbar._transforms} transforms_ordered = [transform.__class__ for transform in rollbar._transforms] self.assertEqual({ ShortenerTransform, ScrubRedactTransform, SerializableTransform, ScrubUrlTransform, CustomTransform, }, transforms) # CustomTransform should be last because it has the default priority of 100 self.assertEqual([ ShortenerTransform, ScrubRedactTransform, SerializableTransform, ScrubUrlTransform, CustomTransform, ], transforms_ordered) def test_add_custom_transform_first(self): class CustomTransform(Transform): priority = 1 class CustomTransformTwo(Transform): priority = 35 rollbar._initialized = False rollbar.SETTINGS = copy.deepcopy(_default_settings) rollbar.init(_test_access_token, locals={'enabled': True}, handler='blocking', timeout=12345, custom_transforms=[CustomTransform(), CustomTransformTwo()]) transforms = [transform.__class__ for transform in rollbar._transforms] # Custom transforms should be first and fifth. self.assertEqual([ CustomTransform, # priority 1 ShortenerTransform, # priority 10 ScrubRedactTransform, # priority 20 SerializableTransform, # priority 30 CustomTransformTwo, # priority 35 ScrubUrlTransform, # priority 50 ], transforms) ================================================ FILE: rollbar/test/test_traverse.py ================================================ from rollbar.lib.transform import Transform from rollbar.lib.traverse import traverse from rollbar.test import BaseTest class NamedTuple(tuple): """ Modeled after NamedTuple and KeyedTuple from SQLAlchemy 0.7 and 0.8. """ def __new__(cls, vals, labels=None): t = tuple.__new__(cls, vals) if labels: t.__dict__.update(zip(labels, vals)) t._labels = labels return t def keys(self): return [l for l in self._labels if l is not None] class KeyMemTransform(Transform): """ A transform that just stores the keys. """ keys = [] def default(self, o, key=None): self.keys.append((key, o)) return o class RollbarTraverseTest(BaseTest): """ Objects that appear to be a namedtuple, like SQLAlchemy's KeyedTuple, will cause an Exception while identifying them if they don't implement the _make method. """ def setUp(self): self.tuple = NamedTuple((1, 2, 3), labels=["one", "two", "three"]) def test_base_case(self): self.assertEqual(traverse(self.tuple), (1, 2, 3)) def test_bad_object(self): setattr(self.tuple, "_fields", "not quite a named tuple") self.assertEqual(traverse(self.tuple), (1, 2, 3)) def test_depth_first(self): obj = { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } } transform = KeyMemTransform() transform.keys = [] traverse( obj, key=(), string_handler=transform.default, tuple_handler=transform.default, namedtuple_handler=transform.default, list_handler=transform.default, set_handler=transform.default, mapping_handler=transform.default, path_handler=transform.default, default_handler=transform.default, circular_reference_handler=transform.default, allowed_circular_reference_types=transform.default, ) self.assertEqual( transform.keys, [ (("one", 0), "four"), (("one", 1), "five"), (("one", 2), 6), (("one", 3), 7), (("one",), ["four", "five", 6, 7]), (("two", 0), "eight"), (("two", 1), "nine"), (("two", 2), "ten"), (("two",), ("eight", "nine", "ten")), (("three", "eleven"), 12), (("three", "thirteen"), 14), (("three",), { "eleven": 12, "thirteen": 14 }), ((), { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } }), ], ) def test_breadth_first(self): obj = { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } } transform = KeyMemTransform() transform.keys = [] traverse( obj, key=(), string_handler=transform.default, tuple_handler=transform.default, namedtuple_handler=transform.default, list_handler=transform.default, set_handler=transform.default, mapping_handler=transform.default, path_handler=transform.default, default_handler=transform.default, circular_reference_handler=transform.default, allowed_circular_reference_types=transform.default, depth_first=False, ) self.assertEqual( transform.keys, [ ((), { "one": ["four", "five", 6, 7], "two": ("eight", "nine", "ten"), "three": { "eleven": 12, "thirteen": 14 } }), (("one",), ["four", "five", 6, 7]), (("one", 0), "four"), (("one", 1), "five"), (("one", 2), 6), (("one", 3), 7), (("two",), ("eight", "nine", "ten")), (("two", 0), "eight"), (("two", 1), "nine"), (("two", 2), "ten"), (("three",), { "eleven": 12, "thirteen": 14 }), (("three", "eleven"), 12), (("three", "thirteen"), 14), ], ) ================================================ FILE: rollbar/test/twisted_tests/__init__.py ================================================ ================================================ FILE: rollbar/test/twisted_tests/test_twisted.py ================================================ """ Tests for Twisted instrumentation """ import json import sys from unittest import mock import rollbar # access token for https://rollbar.com/rollbar/pyrollbar TOKEN = '92c10f5616944b81a2e6f3c6493a0ec2' # Twisted hasn't been fully ported to Python 3 yet, so don't test there. ALLOWED_PYTHON_VERSION = not sys.version_info[0] == 3 try: from twisted.test import proto_helpers from twisted.trial import unittest from twisted.internet import protocol from twisted.python import log TWISTED_INSTALLED = True except ImportError: TWISTED_INSTALLED = False if ALLOWED_PYTHON_VERSION and TWISTED_INSTALLED: class SquareProtocol(protocol.Protocol): def dataReceived(self, data): try: number = int(data) except ValueError: log.err() self.transport.write('error') else: self.transport.write(str(number**2)) class SquareFactory(protocol.Factory): protocol = SquareProtocol class TwistedTest(unittest.TestCase): def setUp(self): rollbar.init(TOKEN, 'twisted-test') factory = SquareFactory() self.proto = factory.buildProtocol(('127.0.0.1', 0)) self.tr = proto_helpers.StringTransport() self.proto.makeConnection(self.tr) @mock.patch('rollbar.send_payload') def test_base_case(self, send_payload): self.proto.dataReceived('8') self.assertEqual(int(self.tr.value()), 64) self.assertFalse(send_payload.called) @mock.patch('rollbar.send_payload') def test_caught_exception(self, send_payload): self.proto.dataReceived('rollbar') self.assertEqual(self.tr.value(), "error") errors = self.flushLoggedErrors(ValueError) self.assertEqual(len(errors), 1) self.assertTrue(send_payload.called) payload = send_payload.call_args[0][0] data = payload['data'] self.assertIn('body', data) self.assertEqual(data['body']['trace']['exception']['class'], 'ValueError') self.assertEqual(data['body']['trace']['exception']['message'], "invalid literal for int() with base 10: 'rollbar'") # XXX not able to test uncaught exceptions for some reason # @mock.patch('rollbar.send_payload') # def test_uncaught_exception(self, send_payload): # self.proto.dataReceived([8, 9]) # self.assertEqual(self.tr.value(), "error") # errors = self.flushLoggedErrors(TypeError) # self.assertEqual(len(errors), 1) # # self.assertEqual(send_payload.called, True) # payload = send_payload.call_args[0][0] # data = payload['data'] # # self.assertIn('body', data) # self.assertEqual(data['body']['trace']['exception']['class'], # 'TypeError') # self.assertEqual(data['body']['trace']['exception']['message'], # "int() argument must be a string or a number, not 'list'") ================================================ FILE: rollbar/test/utils.py ================================================ from collections.abc import Mapping def get_public_attrs(obj: Mapping) -> dict: return {k: obj[k] for k in obj if not k.startswith('_')}