Skip to content

Commit

Permalink
1. Protect Django and Flask views 2. Django REST Framework request bo…
Browse files Browse the repository at this point in the history
…dy support. 3. Simplify error reporting using

#1 Protect Django and Flask views
#2 Django REST Framework request body support.
#3 Simplify error reporting using

context manager
capture_message
capture_exception methods
  • Loading branch information
sonus21 authored Jan 14, 2020
2 parents 14250a9 + 98fa367 commit 38b736a
Show file tree
Hide file tree
Showing 47 changed files with 773 additions and 178 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ install:

script:
- coverage run tests/DjangoTest/runners/common_runner.py
- coverage run -a tests/DjangoTest/runners/drf_test_runner.py
- coverage run -a tests/DjangoTest/runners/custom_masking_test_runner.py
- coverage run -a tests/DjangoTest/runners/masking_disabled_test_runner.py
- coverage run -a tests/DjangoTest/runners/model_test_runner.py
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ app.py
# Record exception when 404 error code is raised
@app.errorhandler(403)
def error_403(e):
error_tracker.record_exception()
error_tracker.capture_exception()
# any custom logic
# Record error using decorator
Expand Down
62 changes: 57 additions & 5 deletions docs/source/django-app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,33 +94,85 @@ Setting details
.. note::
Class must implements all abstract methods

- Exception Listing page permission
By default exception listing is enabled for only admin users.


.. code::
APP_ERROR_VIEW_PERMISSION = 'permission.ErrorViewPermission'
.. note::
Class must not have any constructor parameters and should implement __call__ method.


Manual Exception Tracking
~~~~~~~~~~~~~~~~~~~~~~~~~

Error can be tracked programmatically using ErrorTracker's object available in middleware module. For tracking exception call
error_tracker.record_exception method.
Error can be tracked programmatically using `ErrorTracker`'s utility methods available in error_tracker module.
For tracking exception call `capture_exception` method.

.. code::
from error_tracker.django.middleware import error_tracker
from error_tracker import capture_exception
...
try
...
catch Exception as e:
error_tracker.record_exception(request, e)
capture_exception(request=request, exception=e)
A message can be captured using `capture_message` method.

.. code::
from error_tracker import capture_message
try
...
catch Exception as e:
capture_message("Something went wrong", request=request, exception=e)
Decorator based exception recording, record exception as it occurs in a method call.

.. note::
Exception will be re-raised so it must be caught in the caller or ignored.
Re-raising of exception can be disabled using `silent=True` parameter

.. code::
from error_tracker.django.middleware import track_exception
from error_tracker import track_exception
@track_exception
def do_something():
...
So far, you have seen only uses where context is provided upfront using default context builder or some other means.
Sometimes, we need to put context based on the current code path, like add user_id and email in login flow.
ErrorTracker comes with context manager that can be used for such use cases.

.. code::
from error_tracker import configure_scope
with configure_scope(request=request) as scope:
scope.set_extra("user_id", 1234)
scope.set_extra("email", "example@example.com"
In this case whenever exception would be raised, it will capture the exception automatically and these context details would be stored as well.


.. code::
{
...
"context" : {
"id" : 1234,
"email" : "example@example.com"
}
}
49 changes: 46 additions & 3 deletions docs/source/flask-app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ Config details
Manual Exception Tracking
~~~~~~~~~~~~~~~~~~~~~~~~~
Error can be tracked programmatically using AppErrorTracker's record_exception method.
Error can be tracked programmatically using AppErrorTracker's capture_exception method.
ErrorTracker provides many ways to capture error.

Capture Error using `capture_exception` method
`capture_exception` takes another parameter for `additional_context` (dictionary of key value pairs).
This parameter can be used to provide additional details about the failure.


.. code::
Expand All @@ -76,19 +82,56 @@ Error can be tracked programmatically using AppErrorTracker's record_exception m
try
...
catch Exception as e:
error_tracker.record_exception()
error_tracker.capture_exception()
A simple Message can be captured using `capture_message` method.


.. code::
try
...
catch Exception as e:
error_tracker.capture_message("Something went wrong!")
Decorator based exception recording, record exception as it occurs in a method call.

.. note::
Exception will be re-raised so it must be caught in the caller or ignored.
Raised exception can be ignored by passing `silent=True`.
Also more context detail can be provided using `additional_context` parameter.


.. code::
error_tracker = AppErrorTracker(...)
@error_tracker.auto_track_exception
def fun():
pass
So far, you have seen only uses where context is provided upfront using default context builder or some other means.
Sometimes, we need to put context based on the current code path, like add user_id and email in login flow.
ErrorTracker comes with context manager that can be used for such use cases.

.. code::
from error_tracker import flask_scope
with flask_scope() as scope:
scope.set_extra("user_id", 1234)
scope.set_extra("email", "example@example.com" )
Now error_tracker will automatically capture exception as it will occur. This data will be stored in request_data detail as

.. code::
{
...
"context" : {
"id" : 1234,
"email" : "example@example.com"
}
}
32 changes: 26 additions & 6 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Recording exception/error
An error/exception can be recorded using decorator or function call.

- To record the error using decorator, decorate a function with :code:`track_exception` or :code:`auto_track_exception`
- Where as to record error using function call use :code:`record_exception` function.
- Where as to record error using function call use :code:`capture_exception` function.
- Exception detail can be written to a file, console or logger etc call method :code:`print_exception`

All the data will be stored in the configured data store and these data will be available at configure URL path.
Expand All @@ -64,6 +64,12 @@ An instance of :code:`AppErrorTracker` needs to be created and have to be config
Monitoring feature can be configured either using object based configuration or app-based configuration,
the only important thing here is we should have all the required key configs in the app.config otherwise it will fail.

.. note::
Exception listing page is disabled by default. You need to enable that using view_permission parameter.
view_permission function/callable class must return True/False based on the current request detail.
This method would be called as view_permission(request).


For object based configuration add
**settings.py**

Expand Down Expand Up @@ -97,15 +103,22 @@ For object based configuration add
message = Message(email_subject, recipient_list, email_body, sender=from_email)
self.send(message)
mailer = Notifier(app=app)
error_tracker = AppErrorTracker(app=app, db=db, notifier=mailer)
# enable for all users
class ViewPermission(ViewPermissionMixin):
def __call__(self, request):
return True
error_tracker = AppErrorTracker(app=app, db=db, notifier=mailer, view_permission=ViewPermission())
....
....
# Record exception when 404 error code is raised
@app.errorhandler(403)
def error_403(e):
error_tracker.record_exception()
error_tracker.capture_exception()
# any custom logic
# Record error using decorator
Expand All @@ -131,12 +144,19 @@ We need to update settings.py file as

.. [1] This should be added at the end so that it can process exception 1st in the middleware call stack.
.. note::
Exception listing page is only enable for admin by default.
You can enable for others by providing a custom implementation of ViewPermissionMixin.
This class must return True/False based on the current request, False means not authorized, True means authorized.

.. code::
...
APP_ERROR_RECIPIENT_EMAIL = ('example@example.com',)
APP_ERROR_SUBJECT_PREFIX = "Server Error"
APP_ERROR_EMAIL_SENDER = 'user@example.com'
# optional setting otherwise it's enabled for admin only
APP_ERROR_VIEW_PERMISSION = 'permission.ErrorViewPermission'
INSTALLED_APPS = [
...
Expand Down Expand Up @@ -168,14 +188,14 @@ For example, if we want to use Flask then do
* Create Flask App instance
* Create Error Tracker app instance
* DO NOT call run method of Flask app instance
* To track exception call :code:`error_tracker.record_exception` method
* To track exception call :code:`capture_exception` method

- Django App
* Create Django App with settings and all configuration
* Set environment variable **DJANGO_SETTINGS_MODULE**
* call :code:`django.setup()`
* :code:`from error_tracker.django.middleware import error_tracker`
* To track exception do :code:`error_tracker.record_exception(None, exception)`
* :code:`from error_tracker import error_tracker`
* To track exception do :code:`capture_exception(None, exception)`



Expand Down
20 changes: 15 additions & 5 deletions error_tracker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@
#
# Error Tracking app
#
# :copyright: 2019 Sonu Kumar
# :copyright: 2020 Sonu Kumar
# :license: BSD-3-Clause
#

__version__ = '1.1.2'
__version__ = '1.1.3'
__author__ = 'Sonu Kumar'
__email__ = 'sonunitw12@gmail.com'

from error_tracker.libs.mixins import *
from error_tracker.flask import *
from error_tracker.django import *
from error_tracker.flask.utils import configure_scope as flask_scope
from error_tracker.django.apps import DjangoErrorTracker
from error_tracker.django.utils import capture_message, track_exception, configure_scope, capture_exception
from error_tracker.libs.exception_formatter import *

__all__ = [
"AppErrorTracker", "DefaultFlaskContextBuilder",
# flask modules
"AppErrorTracker", "DefaultFlaskContextBuilder", "flask_scope",

# mixin classes
"NotificationMixin", "ModelMixin", "MaskingMixin",
"ContextBuilderMixin", "TicketingMixin",
"DefaultDjangoContextBuilder", "DjangoErrorTracker",
"ContextBuilderMixin", "TicketingMixin", "ViewPermissionMixin",

# Django modules
"DefaultDjangoContextBuilder", "DjangoErrorTracker", "DefaultDjangoViewPermission",
"capture_message", "track_exception", "configure_scope", "capture_exception",

# lower level methods
"format_exception", "print_exception"
]
15 changes: 8 additions & 7 deletions error_tracker/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
#
# Django components
#
# :copyright: 2019 Sonu Kumar
# :copyright: 2020 Sonu Kumar
# :license: BSD-3-Clause
#


from .utils import DefaultDjangoContextBuilder, DjangoNotification
from .settings import APP_ERROR_MASKED_KEY_HAS, APP_ERROR_MASK_WITH, APP_ERROR_RECIPIENT_EMAIL, \
APP_ERROR_EMAIL_SENDER, get
from .utils import DefaultDjangoContextBuilder, DjangoNotification, DefaultDjangoViewPermission
from .settings import *

from .utils import DjangoNotification, DefaultDjangoContextBuilder
from error_tracker.libs.utils import Masking, get_class_from_path, get_class_instance
from error_tracker import ModelMixin, MaskingMixin, TicketingMixin, NotificationMixin, ContextBuilderMixin
from error_tracker import ModelMixin, MaskingMixin, TicketingMixin, NotificationMixin, ContextBuilderMixin, \
ViewPermissionMixin
from django.apps import apps as django_apps
import warnings
from .settings import * # noqa


def get_exception_model():
Expand Down Expand Up @@ -68,4 +67,6 @@ def get_context_builder():
DefaultDjangoContextBuilder, "ContextBuilder")



def get_view_permission():
return get_class_instance(APP_ERROR_VIEW_PERMISSION, ViewPermissionMixin, DefaultDjangoViewPermission,
"ViewPermission")
Loading

0 comments on commit 38b736a

Please sign in to comment.