Skip to content

Commit 01a3d5f

Browse files
authored
Merge pull request #110 from MindscapeHQ/js/fix-sending-exception
Version 4.4.0 bug fix and features
2 parents 8305dc5 + 521dbdd commit 01a3d5f

File tree

17 files changed

+504
-204
lines changed

17 files changed

+504
-204
lines changed

.travis.yml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
sudo: false
2-
31
language: python
42

53
cache: pip
64

7-
python: ["2.7", "3.2", "3.3", "3.4", "3.5", "3.6", "pypy"]
8-
9-
# Workaround to install Python 3.7
10-
# https://github.com/travis-ci/travis-ci/issues/9815
11-
matrix:
12-
include:
13-
- python: 3.7
14-
dist: xenial
15-
sudo: true
5+
python:
6+
- "2.7"
7+
- "3.6"
8+
- "3.7"
9+
- "3.8"
10+
- "3.9"
11+
- "3.10"
12+
- "3.11"
13+
- "pypy"
14+
- "pypy3"
1615

1716
install:
18-
- pip install .[dev]
17+
- python -m pip install coverage .[dev]
1918

2019
script:
21-
# Django 1.8 requires Python 2.7+
22-
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 && $TRAVIS_PYTHON_VERSION == 2* ]]; then coverage run -m unittest2 discover python2; fi
23-
# Coverage/Coveralls has dropped support for Python 3.2
24-
- if [[ $TRAVIS_PYTHON_VERSION != 3.2 && $TRAVIS_PYTHON_VERSION == 3* ]]; then coverage run -m unittest discover python3; fi
25-
- if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then coverage run -m unittest2 discover python2; fi
26-
# - if [[ $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then coverage run -m unittest discover python3; fi
20+
- if [[ $TRAVIS_PYTHON_VERSION == 2* || $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then coverage run --source=python2 -m unittest discover python2/tests; fi
21+
- if [[ $TRAVIS_PYTHON_VERSION == 3* || $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then coverage run --source=python3 -m unittest discover python3/tests; fi
2722

2823
after_success:
2924
coveralls

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1+
## 4.4.0 (11/08/2023):
2+
Features:
3+
- Added `RaygunHandler.from_sender()` factory to construct a `RaygunHandler` instance using an existing `RaygunSender`. This allows for additional configuration of the sender.
4+
- Added a `config` parameter the to Flask and WSGI middleware provider constructors. This also allows for additional configuration of the sender.
5+
- The `RaygunHandler` now adds tags corresponding to the logging level, which now defaults to `logging.ERROR`.
6+
- Errors/exceptions sent via the `RaygunHandler` now have their message overriden by the logged message.
7+
Bug fixes:
8+
- The `RaygunHandler` now attempts to capture `exc_info` from the `record`. This can be obtained if `logger.exception()` is used or if `exc_info=True` is set in the logger call.
9+
- If `exc_info` cannot be obtained by the `RaygunHandler`, it no longer attempts to construct a `RaygunErrorMessage` with `None` values. Instead, it generates a fallback error message using information gathered from the `record`. This is essentially an error with a single stack frame representing the call to the logger.
10+
Quality of life updates:
11+
- Updated `CONTRIBUTING.MD`.
12+
- Got unit tests running again (`django` upgrade).
13+
- Updated `python3/samples/sample.py` and `python3/samples/sampleWithLogging.py`.
14+
- Cleaned up `python3/raygun4py/cli.py`.
15+
116
## 4.3.0 (06/06/2019):
217
Features:
318
- Added a new config option, `transmit_environment_variables`, to control sending any environment variables at all
419
- Added support to `filter_keys` config option for ignoring keys with a simple wildcard approach. See README for more information
520

621
## 4.2.3 (28/03/2019):
722
Bugfixes
8-
- Add request rawData to the build_wsgi_compliant_request utilities to fix a bug where rawData is set manually then overwritten by an empty object.
23+
- Add request `rawData` to the `build_wsgi_compliant_request` utilities to fix a bug where `rawData` is set manually then overwritten by an empty object.
924

1025
## 4.2.2 (23/01/2019):
1126
Bugfixes

CONTRIBUTING.MD

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
1-
# Getting Started with Local Development
2-
The following will show you how to get started with local development
1+
## Getting Started with Local Development
2+
Before starting, you'll need to ensure you have the appropriate version of Python installed.
33

4-
## Python 2
5-
1. `cd <project folder> && virtualenv -p <PATH TO PYTHON2> venv2`
6-
1. activate the virtual env
7-
1. `pip install .[dev]`
8-
1. Run tests
9-
```
10-
python -m unittest2 discover python2
11-
```
4+
---
125

13-
## Python 3
14-
1. `cd <project folder> && virtualenv -p <PATH TO PYTHON3> venv3`
15-
1. activate the virtual env
16-
1. `pip install .[dev]`
17-
1. Run tests
6+
# Python 2
7+
1. Install the virtual environment manager: `python2 -m pip install virtualenv`
8+
2. Navigate to the project directory: `cd <project folder>`
9+
3. Create a new virtual environment using Python 2: `python2 -m virtualenv venv2`
10+
4. Activate the virtual environment. On Windows, use `venv2\Scripts\activate`. On Unix or MacOS, use `source venv2/bin/activate`.
11+
5. Install the development dependencies and prepare local package: `python2 -m pip install -e .[dev]`
12+
6. Run tests with the command:
13+
```bash
14+
python2 -m unittest discover python2/tests
1815
```
19-
python -m unittest discover python3
16+
- Remember to deactivate the virtual environment when you're done: `deactivate`
17+
18+
---
19+
20+
# Python 3
21+
1. Install the virtual environment manager: `python3 -m pip install virtualenv`
22+
2. Navigate to the project directory: `cd <project folder>`
23+
3. Create a new virtual environment using Python 3: `python3 -m virtualenv venv3`
24+
4. Activate the virtual environment. On Windows, use `venv3\Scripts\activate`. On Unix or MacOS, use `source venv3/bin/activate`.
25+
5. Install the development dependencies and prepare local package: `python3 -m pip install -e .[dev]`
26+
6. Run tests with the command:
27+
```bash
28+
python3 -m unittest discover python3/tests
2029
```
30+
- Remember to deactivate the virtual environment when you're done: `deactivate`
31+
32+
---

README.rst

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ raygun4py
88
:target: https://coveralls.io/r/MindscapeHQ/raygun4py?branch=master
99

1010

11-
Official Raygun provider for **Python 2.7**, **Python 3+** and **PyPy**
11+
Official Raygun provider for **Python 2.7**, **Python 3.1+** and **PyPy**
12+
13+
Please also refer to our `documentation site <https://raygun.com/documentation/language-guides/python/crash-reporting/installation/>`_, as this is maintained with higher priority.
1214

1315

1416
Installation
@@ -18,16 +20,8 @@ The easiest way to install this is as a pip package, as it is available from PyP
1820

1921
$ pip install raygun4py
2022

21-
Then import and instantiate the module:
22-
23-
.. code:: python
24-
25-
from raygun4py import raygunprovider
26-
27-
client = raygunprovider.RaygunSender('your_apikey')
28-
2923
Test the installation
30-
------------------------
24+
---------------------
3125

3226
From the command line, run::
3327

@@ -38,61 +32,80 @@ Replace :code:`your_apikey` with the one listed on your Raygun dashboard. This w
3832
Usage
3933
=====
4034

35+
Import and instantiate the module:
36+
37+
.. code:: python
38+
39+
from raygun4py import raygunprovider
40+
41+
sender = raygunprovider.RaygunSender("paste_your_api_key_here")
42+
4143
Automatically send the current exception like this:
4244

4345
.. code:: python
4446
4547
try:
4648
raise Exception("foo")
4749
except:
48-
client.send_exception()
50+
sender.send_exception()
4951
5052
See `sending functions`_ for more ways to send.
5153

52-
5354
Uncaught exception handler
5455
--------------------------
5556

56-
To automatically pick up unhandled exceptions with custom logic, you can provide a callback function to sys.excepthook:
57+
To automatically send unhandled exceptions, you can provide a callback function to :code:`sys.excepthook`:
5758

5859
.. code:: python
5960
6061
def handle_exception(exc_type, exc_value, exc_traceback):
6162
sender = raygunprovider.RaygunSender("your_apikey")
6263
sender.send_exception(exc_info=(exc_type, exc_value, exc_traceback))
64+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
6365
6466
sys.excepthook = handle_exception
6567
68+
Note that after sending the exception, we invoke the default :code:`sys.__excepthook__` to maintain the expected behavior for unhandled exceptions. This ensures the program terminates as it would without the custom exception handler in place.
69+
6670
Logging
6771
-------
6872

69-
You can also send exceptions using a logger:
73+
You can send errors/exceptions via a logger by attaching a :code:`RaygunHandler`:
7074

7175
.. code:: python
7276
73-
logger = logging.getLogger("mylogger")
74-
rgHandler = raygunprovider.RaygunHandler("your_apikey")
75-
logger.addHandler(rgHandler)
77+
logger = logging.getLogger()
78+
raygun_handler = raygunprovider.RaygunHandler("paste_your_api_key_here")
79+
logger.addHandler(raygun_handler)
7680
77-
def log_exception(exc_type, exc_value, exc_traceback):
78-
logger.error("An exception occurred", exc_info = (exc_type, exc_value, exc_traceback))
81+
A :code:`RaygunHandler` can also be instantiated from an existing :code:`RaygunSender`:
82+
83+
.. code:: python
7984
80-
sys.excepthook = log_exception
85+
raygun_handler = raygunprovider.RaygunHandler.from_sender(sender)
86+
87+
It is then recommended to use :code:`logger.exception()` or :code:`logger.error(exc_info=True)` in the scope of an :code:`except` block:
88+
89+
.. code:: python
8190
82-
This uses the built-in :code:`RaygunHandler`. You can provide your own handler implementation based on that class if you need custom sending behavior.
91+
try:
92+
raise Exception("Example exception")
93+
except:
94+
logger.exception("Example logger.exception log")
95+
# Or
96+
logger.error("Example logger.error log", exc_info=True)
8397
98+
Note that using a :code:`RaygunHandler` outside the scope of an :code:`except` block will not allow it to populate a full stack trace.
8499

85100
Web frameworks
86101
--------------
87102

88-
Raygun4py includes dedicated middleware implementations for Django and Flask, as well as generic WSGI frameworks (Tornado, Bottle, Ginkgo etc). These are available for both Python 2.6/2.7 and Python 3+.
103+
Raygun4py includes dedicated middleware implementations for Django and Flask, as well as generic WSGI frameworks (Tornado, Bottle, Ginkgo etc). These are available for both Python 2.7 and Python 3.1+.
89104

90105
Django
91106
++++++
92107

93-
To configure Django to automatically send all exceptions that are raised in views to Raygun:
94-
95-
settings.py
108+
To configure Django to automatically send all exceptions that are raised in views to Raygun, add the following to :code:`settings.py`:
96109

97110
.. code:: python
98111
@@ -127,6 +140,8 @@ The above configuration is the minimal required setup. The full set of options s
127140
Flask
128141
+++++
129142

143+
To attach a request exception handler that enhances reports with Flask-specific environment data, use our middleware :code:`flask.Provider`:
144+
130145
.. code:: python
131146
132147
from flask import Flask, current_app
@@ -136,6 +151,8 @@ Flask
136151
137152
flask.Provider(app, 'your_apikey').attach()
138153
154+
The :code:`flask.Provider` constructor can also take an optional :code:`config` argument. This should be a standard :code:`Dict` of supported options, as shown in advanced configuration below. It also returns the underlying :code:`RaygunSender`, which you may decide to use elsewhere.
155+
139156
WSGI
140157
++++
141158

@@ -164,6 +181,8 @@ An example using **Tornado**, which will pick up exceptions that occur in the WS
164181
server = wsgiref.simple_server.make_server('', 8888, raygun_wrapped_app)
165182
server.serve_forever()
166183
184+
The :code:`wsgi.Provider` constructor can also take an optional :code:`config` argument. This should be a standard :code:`Dict` of supported options, as shown in advanced configuration below.
185+
167186
Note that many frameworks (tornado, pryramid, gevent et al) will swallow exceptions that occur within their domain.
168187

169188
Let us know if we're missing middleware for your framework, or feel free to submit a pull request.
@@ -355,13 +374,6 @@ For Python 3, chained exceptions are supported and automatically sent along with
355374

356375
This occurs when an exception is raised while handling another exception - see tests_functional.py for an example.
357376

358-
Troubleshooting
359-
===============
360-
361-
To see the HTTP response code from sending the message to raygun, `print client.send()` (as in line 27 of test.py). It will be 403 if an invalid API key was entered, and 202 if successful.
362-
363-
Create a thread in the official support forums at http://raygun.io/forums, and we'll help you out.
364-
365377
Changelog
366378
=========
367379

python2/raygun4py/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# 1) we don't load dependencies by storing it in __init__.py
33
# 2) we can import it in setup.py for the same reason
44
# 3) we can import it into your module module
5-
__version__ = '4.3.2'
5+
__version__ = '4.4.0'

python3/raygun4py/cli.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
1-
import sys
21
from optparse import OptionParser
2+
33
from raygun4py import raygunprovider
44

55

66
def main():
77
usage = '\n raygun4py test <apikey>'
88
parser = OptionParser(usage=usage)
99

10-
options, args = parser.parse_args()
10+
_, args = parser.parse_args()
1111

12-
if 'install' in args:
13-
if len(args) > 1:
14-
print("Installed API key! Now run 'raygun4py test' to check it's working")
15-
else:
16-
print('Please provide a Raygun API key!')
17-
elif 'test' in args:
18-
if len(args) > 1 and isinstance(args[1], str):
19-
send_test_exception(args[1])
20-
else:
21-
print('Please provide your API key')
12+
if len(args) < 2 or not isinstance(args[1], str):
13+
print('Please provide your API key')
14+
parser.print_help()
15+
elif args[0] == 'test':
16+
send_test_exception(args[1])
2217
else:
18+
print(f"Invalid command '{args[0]}'")
2319
parser.print_help()
2420

2521

2622
def send_test_exception(apikey):
2723
client = raygunprovider.RaygunSender(apikey)
2824

2925
try:
30-
raise Exception("Test exception from Raygun4py3!")
31-
except:
26+
raise Exception("Test exception from Raygun4py (Python3)")
27+
except Exception:
3228
response = client.send_exception()
3329

34-
if response[0] is 202:
35-
print("Success! Now check your Raygun dashboard at https://app.raygun.io")
30+
if response[0] == 202:
31+
print("Success! Now check your Raygun dashboard at https://app.raygun.com")
3632
else:
37-
print("Something went wrong - please check your API key or contact us to get help. The response was:")
33+
print(
34+
"Something went wrong - please check your API key or contact us for help. The response was:")
3835
print(response)

python3/raygun4py/middleware/django.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
import django
22
from django.conf import settings
3+
34
from raygun4py import raygunprovider
4-
from django.utils.functional import cached_property
55

66
try:
77
from django.utils.deprecation import MiddlewareMixin
88
except ImportError:
99
MiddlewareMixin = object
1010

11+
1112
class Provider(MiddlewareMixin):
1213

1314
def __init__(self, get_response=None):
1415
self.get_response = get_response
1516
config = getattr(settings, 'RAYGUN4PY_CONFIG', {})
16-
apiKey = getattr(settings, 'RAYGUN4PY_API_KEY', config.get('api_key', None))
17+
apiKey = getattr(settings, 'RAYGUN4PY_API_KEY',
18+
config.get('api_key', None))
1719

1820
self.sender = raygunprovider.RaygunSender(apiKey, config=config)
1921

2022
def process_exception(self, request, exception):
2123
raygun_request = self._mapRequest(request)
2224
env = self._get_django_environment()
2325

24-
self.sender.send_exception(exception=exception, request=raygun_request, extra_environment_data=env)
26+
self.sender.send_exception(
27+
exception=exception, request=raygun_request, extra_environment_data=env)
2528

2629
def _mapRequest(self, request):
2730
headers = request.META.items()

0 commit comments

Comments
 (0)