Giter VIP home page Giter VIP logo

raygun4py's Introduction

raygun4py

image

image

Official Raygun provider for Python 2.7, Python 3.1+ and PyPy

Python 2.7 is supported in versions <= 4.4.0

Please also refer to our documentation site, as this is maintained with higher priority.

Installation

The easiest way to install this is as a pip package, as it is available from PyPI. From your command line, run:

$ pip install raygun4py

Test the installation

From the command line, run:

$ raygun4py test your_apikey

Replace your_apikey with the one listed on your Raygun dashboard. This will cause a test exception to be generated and sent.

Usage

Import and instantiate the module:

from raygun4py import raygunprovider

sender = raygunprovider.RaygunSender("paste_your_api_key_here")

Automatically send the current exception like this:

try:
    raise Exception("foo")
except:
    sender.send_exception()

See sending functions for more ways to send.

Uncaught exception handler

To automatically send unhandled exceptions, you can provide a callback function to sys.excepthook:

def handle_exception(exc_type, exc_value, exc_traceback):
    sender = raygunprovider.RaygunSender("your_apikey")
    sender.send_exception(exc_info=(exc_type, exc_value, exc_traceback))
    sys.__excepthook__(exc_type, exc_value, exc_traceback)

sys.excepthook = handle_exception

Note that after sending the exception, we invoke the default 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.

Logging

You can send errors/exceptions via a logger by attaching a RaygunHandler:

logger = logging.getLogger()
raygun_handler = raygunprovider.RaygunHandler("paste_your_api_key_here")
logger.addHandler(raygun_handler)

A RaygunHandler can also be instantiated from an existing RaygunSender:

raygun_handler = raygunprovider.RaygunHandler.from_sender(sender)

It is then recommended to use logger.exception() or logger.error(exc_info=True) in the scope of an except block:

try:
    raise Exception("Example exception")
except:
    logger.exception("Example logger.exception log")
    # Or
    logger.error("Example logger.error log", exc_info=True)

Note that using a RaygunHandler outside the scope of an except block will not allow it to populate a full stack trace.

Web frameworks

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+.

Django

To configure Django to automatically send all exceptions that are raised in views to Raygun, add the following to settings.py:

MIDDLEWARE_CLASSES = (
    'raygun4py.middleware.django.Provider'
)

RAYGUN4PY_CONFIG = {
    'api_key': 'paste_your_api_key_here'
}

The above configuration is the minimal required setup. The full set of options supported by the provider can be declared in the same way:

RAYGUN4PY_CONFIG = {
    'api_key': 'paste_your_api_key_here',
    'http_timeout': 10.0,
    'proxy': None,
    'before_send_callback': None,
    'grouping_key_callback': None,
    'filtered_keys': [],
    'ignored_exceptions': [],
    'transmit_global_variables': True,
    'transmit_local_variables': True,
    'enforce_payload_size_limit': True, 
    'log_payload_size_limit_breaches': True,
    'transmit_environment_variables:': True,
    'userversion': "Not defined",
    'user': None
}

'enforce_payload_size_limit' when enabled (default behavior) will iteratively remove the largest global or local variable from the error message until the payload is below 128kb as payloads over 128kb will not be accepted by Raygun 'log_payload_size_limit_breaches' when enabled (default behavior) will log breaches and specify which variables are being removed

Flask

To attach a request exception handler that enhances reports with Flask-specific environment data, use our middleware flask.Provider:

from flask import Flask, current_app
from raygun4py.middleware import flask

app = Flask(__name__)

flask.Provider(app, 'your_apikey').attach()

The flask.Provider constructor can also take an optional config argument. This should be a standard Dict of supported options, as shown in advanced configuration below. It also returns the underlying RaygunSender, which you may decide to use elsewhere.

WSGI

An example using Tornado, which will pick up exceptions that occur in the WSGI pipeline:

from raygun4py.middleware import wsgi

class MainHandler(tornado.web.RequestHandler):

  def initialize(self):
      raise Exception('init')

def main():
  settings = {
      'default_handler_class': MainHandler
  }

  application = tornado.web.Application([
      (r"/", MainHandler),
  ], **settings)

  wsgiapp = tornado.wsgi.WSGIAdapter(application)
  raygun_wrapped_app = wsgi.Provider(wsgiapp, 'your_apikey')
  server = wsgiref.simple_server.make_server('', 8888, raygun_wrapped_app)
  server.serve_forever()

The wsgi.Provider constructor can also take an optional config argument. This should be a standard Dict of supported options, as shown in advanced configuration below.

Note that many frameworks (tornado, pryramid, gevent et al) will swallow exceptions that occur within their domain.

Let us know if we're missing middleware for your framework, or feel free to submit a pull request.

Attaching raw HTTP request data

If you are in a web server environment and have HTTP request details available, you can pass these and the headers through in a dictionary (see sample.py).

Code running on Google App Engine should now be supported - you can test this locally, and has been reported working once deployed (the latter currently requires a paid account due to needed SSL support).

Documentation

Initialization options

RaygunSender accepts a config dict which is used to set options for the provider (the defaults are shown below):

from raygun4py import raygunprovider

client = raygunprovider.RaygunSender('your_apikey', config={
    'http_timeout': 10.0,
    'proxy': None,
    'before_send_callback': None,
    'grouping_key_callback': None,
    'filtered_keys': [],
    'ignored_exceptions': [],
    'transmit_global_variables': True,
    'transmit_local_variables': True,
    'transmit_environment_variables:': True,
    'userversion': "Not defined",
    'user': None
})

For the local/global/environment variables, if their options are set to False the corresponding variables will not be sent with exception payloads.

httpTimeout controls the maximum time the HTTP request can take when POSTing to the Raygun API, and is of type 'float'.

Sending functions

+----------------+---------------+--------------------+ | Function | Arguments | Type | +================+===============+====================+ | send_exception | exception | Exception | + +---------------+--------------------+ | | exc_info | 3-tuple | + +---------------+--------------------+ | | tags | List | + +---------------+--------------------+ | | userCustomData| Dict | + +---------------+--------------------+ | | httpRequest | Dict | +----------------+---------------+--------------------+

All parameters are optional.

Call this function from within a catch block to send the current exception to Raygun:

# Automatically gets the current exception
httpResult = client.send_exception()

# Uses the supplied sys.exc_info() tuple
httpResult = client.send_exception(exc_info=sys.exc_info())

# Uses a supplied Exception object
httpResult = client.send_exception(exception=exception)

# Send tags, custom data and an HTTP request object
httpResult = client.send_exception(tags=[], userCustomData={}, request={})

You can pass in either of these two exception params:

  • exception should be a subclass of type Exception. Pass this in if you want to manually transmit an exception object to Raygun.
  • exc_info should be the 3-tuple returned from sys.exc_info(). Pass this tuple in if you wish to use it in other code aside from send_exception().

send_exception also supports the following extra data parameters:

  • tags is a list of tags relating to the current context which you can define.
  • userCustomData is a dict containing custom key-values also of your choosing.
  • httpRequest is HTTP Request data - see sample.py for the expected format of the object.

Config and data functions

Function Arguments Type
filter_keys keys List

If you want to filter sensitive data out of the payload that is sent to Raygun, pass in a list of keys here. Any matching keys on the top level Raygun message object, or within dictionaries on the top level Raygun message object (including dictionaries nested within dictionaries) will have their value replaced with <filtered> - useful for passwords, credit card data etc. Supports * at the end of a key to indicate you want to filter any key that contains that key, ie foo* will filter foo_bar, foo_qux, foo_baz etc

Function Arguments Type
ignore_exceptions exceptions List

Provide a list of exception types to ignore here. Any exceptions that are passed to send_exception that match a type in this list won't be sent.

Function Arguments Type
on_before_send callback Function

You can mutate the candidate payload by passing in a function that accepts one parameter using this function. This allows you to completely customize what data is sent, immediately before it happens.

Function Arguments Type
on_grouping_key callback Function

Pass a callback function to this method to configure custom grouping logic. The callback should take one parameter, an instance of RaygunMessage, and return a string between 1 and 100 characters in length (see 'Custom Grouping Logic' below for more details).

+----------------+---------------+--------------------+ | Function | Arguments | Type | +================+===============+====================+ | set_proxy | host | String | + +---------------+--------------------+ | | port | Integer | +----------------+---------------+--------------------+

Call this function if your code is behind a proxy and want Raygun4py to make the HTTP request to the Raygun endpoint through it.

Function Arguments Type
set_version version String

Call this to attach a SemVer version to each message that is sent. This will be visible on the dashboard and can be used to filter exceptions to a particular version, deployment tracking etc.

Function Arguments Type
set_user user_info Dict

Customer data can be passed in which will be displayed in the Raygun web app. The dict you pass in should look this this:

client.set_user({
    'firstName': 'Foo',
    'fullName': 'Foo Bar',
    'email': '[email protected]',
    'isAnonymous': False,
    'identifier': '[email protected]'
  })

identifier should be whatever unique key you use to identify customers, for instance an email address. This will be used to create the count of affected customers. If you wish to anonymize it, you can generate and store a UUID or hash one or more of their unique login data fields, if available.

Custom grouping logic

You can create custom exception grouping logic that overrides the automatic Raygun grouping by passing in a function that accepts one parameter using this function. The callback's one parameter is an instance of RaygunMessage (python[2/3]/raygunmsgs.py), and the callback should return a string.

The RaygunMessage instance contains all the error and state data that is about to be sent to the Raygun API. In your callback you can inspect this RaygunMessage, hash together the fields you want to group by, then return a string which is the grouping key.

This string needs to be between 1 and 100 characters long. If the callback is not set or the string isn't valid, the default automatic grouping will be used.

By example:

class MyClass(object):

    def my_callback(self, raygun_message):
        return raygun_message.get_error().message[:100] # Use naive message-based grouping only

    def create_raygun_and_bind_callback(self):
        sender = raygunprovider.RaygunSender('api_key')
        sender.on_grouping_key(self.my_callback)

The RaygunSender above will use the my_callback to execute custom grouping logic when an exception is raised. The above logic will use the exception message only - you'll want to use a more sophisticated approach, usually involving sanitizing or ignoring data.

Chained exceptions

For Python 3, chained exceptions are supported and automatically sent along with their traceback.

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

Changelog

View the release history here

raygun4py's People

Contributors

abendig avatar alexlatchford avatar bentleycook avatar brock avatar callum-mckay avatar cmdrkeen avatar donspaulding avatar ebuckley avatar ericb-granular avatar etaiklein avatar ferringb avatar fundead avatar grugcrood82 avatar jamiepenney avatar kentzo avatar martin308 avatar msabramo avatar piannelli avatar proredcat avatar quantumnightmare avatar redj4y avatar sumitramanga avatar ubermouse avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

raygun4py's Issues

Environment Variables are always sent to Raygun

Currently is not possible to prevent environment variables from being sent to Raygun's servers. It is common to have sensitive information like API keys on environment variables, and not have a way to disable this behavior could lead to a serious security breach via Raygun.

This problem doesn't exist on libs for other languages like raygun4java and raygun4ruby.

RaygunHandler fails when logging outside of an exception context.

Looking at the current Raygun logging handler:

class RaygunHandler(logging.Handler):
def __init__(self, api_key, version=None):
logging.Handler.__init__(self)
self.sender = RaygunSender(api_key)
self.version = version
def emit(self, record):
userCustomData = {
"Logger Message": record.msg
}
self.sender.send_exception(userCustomData=userCustomData)

it calls send_exception() unconditionally, and that function expects to run within an exception context.

However, if I add the Raygun handler to my logging hierarchy then itโ€™s likely to be invoked for non-exception contexts as well which causes the following exception:

  File "/usr/local/lib/python3.7/site-packages/some_package/some_file.py", line X, in some_function
    logger.error("Here be an error message")
  File "/usr/local/lib/python3.7/logging/__init__.py", line 1407, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/local/lib/python3.7/logging/__init__.py", line 1514, in _log
    self.handle(record)
  File "/usr/local/lib/python3.7/logging/__init__.py", line 1524, in handle
    self.callHandlers(record)
  File "/usr/local/lib/python3.7/logging/__init__.py", line 1586, in callHandlers
    hdlr.handle(record)
  File "/usr/local/lib/python3.7/logging/__init__.py", line 894, in handle
    self.emit(record)
  File "/usr/local/lib/python3.7/site-packages/raygun4py/raygunprovider.py", line 178, in emit
    self.sender.send_exception(userCustomData=userCustomData)
  File "/usr/local/lib/python3.7/site-packages/raygun4py/raygunprovider.py", line 94, in send_exception
    errorMessage = raygunmsgs.RaygunErrorMessage(exc_type, exc_value, exc_traceback, options)
  File "/usr/local/lib/python3.7/site-packages/raygun4py/raygunmsgs.py", line 133, in __init__
    self.className = exc_type.__name__
AttributeError: 'NoneType' object has no attribute '__name__'

As per documentation, a Handler is expected to ignore messages below itโ€™s current logging level (docs). Because thereโ€™s no dedicated โ€œexceptionโ€ level I think that in this case the Raygun handler should also ignore messages that donโ€™t run in an exception context.

Thus, Iโ€™d like to propose the following change:

class RaygunHandler(logging.Handler):

    def emit(self, record):
        exc_type, _, _ = exc_info = sys.exc_info()
        if exc_type is not None:
            userCustomData = {
                "Logger Message": record.msg
            }
            self.sender.send_exception(exc_info=exc_info, userCustomData=userCustomData)

AttributeError: class RaygunErrorMessage has no attribute '__mro__'

Using old-style classes causes this error:

Traceback (most recent call last):
  File "[..]/env/lib/python2.7/site-packages/[..]/raygun/logger.py", line 34, in raygun_log
    provider.sender.send_exception(**kwargs)
  File "[..]/env/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 110, in send_exception
    return self._post(message)
  File "[..]/env/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 156, in _post 
    json = jsonpickle.encode(raygunMessage, unpicklable=False)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/__init__.py", line 131, in encode
    warn=warn)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 42, in encode 
    return backend.encode(context.flatten(value, reset=reset))
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 153, in flatten
    return self._flatten(obj)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 157, in _flatten
    return self._pop(self._flatten_obj(obj))
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 173, in _flatten_obj
    return flatten_func(obj)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 425, in _flatten_dict_obj
    flatten(k, v, data)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 491, in _flatten_key_value_pair
    data[k] = self._flatten(v)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 157, in _flatten
    return self._pop(self._flatten_obj(obj))
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 173, in _flatten_obj
    return flatten_func(obj)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 425, in _flatten_dict_obj
    flatten(k, v, data)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 491, in _flatten_key_value_pair
    data[k] = self._flatten(v)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 157, in _flatten
    return self._pop(self._flatten_obj(obj))
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 173, in _flatten_obj
    return flatten_func(obj)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 231, in _ref_obj_instance
    return self._flatten_obj_instance(obj)
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/pickler.py", line 361, in _flatten_obj_instance
    if util.is_iterator(obj):
  File "[..]/env/lib/python2.7/site-packages/jsonpickle/util.py", line 337, in is_iterator
    return (isinstance(obj, collections.Iterator) and
  File "[..]/env/lib/python2.7/abc.py", line 144, in __instancecheck__
    return cls.__subclasscheck__(subtype)
  File "[..]/env/lib/python2.7/abc.py", line 180, in __subclasscheck__
    if issubclass(subclass, scls):
  File "[..]/env/lib/python2.7/abc.py", line 161, in __subclasscheck__
    ok = cls.__subclasshook__(subclass)
  File "[..]/env/lib/python2.7/site-packages/backports_abc.py", line 66, in __subclasshook__
    mro = C.__mro__
  AttributeError: class RaygunErrorMessage has no attribute '__mro__'

raygun4py==3.1.3

Update Documentation related to callback functions

If I have time, I'll open an MR for this later. Logging here though...

The on_before_send function on the RaygunSender and the before_send_callback option in the config are not immediately clear when reading the docs. It was only after I read the code that I understood that these are related.

It would be nice to ideally change the online documentation to split up the "Config and data functions" into two separate sections:

  1. Rename Config to "Passing a config object to the RaygunSender client"
    a. Lead with an example of passing in one large config object with all options included.
    b. Inside each option, outline the types of data you can pass, with examples
  2. Create a section called "Calling functions on the instantiated RaygunSender client"
    a. Show examples like client.on_before_send(callback) with similar example in here.

Chained exceptions

Dear developers,

Raygun REST API supports chained exceptions using nested innerError. Would be very nice to have Python3 chained exception support in raygun4py.

Thanks

UnicodeEncodeError when traceback contains non-ascii character

Here's what I got:

Traceback (most recent call last):
  File "/home/fiatjaf/comp/wft/model-updates/reader.py", line 49, in process_messages
    done = process_message(payload)
  File "/home/fiatjaf/comp/wft/model-updates/reader.py", line 74, in process_message
    tags=['boardSetup']
  File "/home/fiatjaf/comp/wft/model-updates/venv/local/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 76, in send_exception
    errorMessage = raygunmsgs.RaygunErrorMessage(exc_type, exc_value, exc_traceback, options)
  File "/home/fiatjaf/comp/wft/model-updates/venv/local/lib/python2.7/site-packages/raygun4py/raygunmsgs.py", line 129, in __init__
    'localVariables': self._get_locals(frame[0]) if 'transmitLocalVariables' in options and options['transmitLocalVariables'] is True else None
  File "/home/fiatjaf/comp/wft/model-updates/venv/local/lib/python2.7/site-packages/raygun4py/raygunmsgs.py", line 145, in _get_locals
    result[key] = str(localVars[key])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 2: ordinal not in range(128

My function call does not show entirely there, but it is

            raygun.set_user(payload['username'])
            raygun.send_exception(
                exc_info=sys.exc_info(),
                userCustomData={'board_id': payload['board_id']},
                tags=['boardSetup']
            )

So, there is nothing non-ascii there, but I know there would be non-ascii characters in the traceback if it was going to be printed to stdout (there is a variable holding the value idรฉias, which has the character u'\xe9' in position 2 as mentioned in the error above), so the problem is probably there.

Add git tags when publishing

It looks like versions 4.2.0 and 4.2.1 aren't added as git tags here in the repo. It would be helpful if you could keep that up.

Thanks

AttributeError: 'dict' object has no attribute 'localVariables'

Traceback (most recent call last):
[..]
    client.send_exception(exception=exception, userCustomData=custom_data)
  File "[..]/env/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 95, in send_exception
    errorMessage = raygunmsgs.RaygunErrorMessage(type(exception), exception, exc_traceback, options)
  File "[..]/env/lib/python2.7/site-packages/raygun4py/raygunmsgs.py", line 152, in __init__
    if frame.localVariables:
AttributeError: 'dict' object has no attribute 'localVariables'

raygun4py==3.1.3

Documentation for Custom grouping logic has incorrect definition of the callback parameter

The documentation for Custom grouping logic states that:

The callback's one parameter is an instance of RaygunMessage (python[2/3]/raygunmsgs.py), and the callback should return a string.

But it appears that this is not the case. Instead it appears to be a dict object which is returned from RaygunSender._transform_message as a result of calling utilities.filter_keys.

The code at utilities.py Line 16 has this:

if isinstance(object, raygunmsgs.RaygunMessage):
        iteration_target = object.__dict__

which is the point where the RaygunMessage is converted to a dict.

The sample code return raygun_message.get_error().message[:100] therefore crashes with an AttributeError:

AttributeError: 'dict' object has no attribute 'get_error'

This however will work:

return raygun_message['details']['error'].message[:100]

The raygun_message['details']['error'] accesses the instance of RaygunErrorMessage

AttributeError: 'NoneType' object has no attribute 'set_user'

Recently upgraded from 3.1.6 to 4.1.0 and it looks like RaygunMessageBuilder.set_request_details is returning None in some situations, causing exception sending to fail with an AttributeError: 'NoneType' object has no attribute 'set_user'

https://github.com/MindscapeHQ/raygun4py/blob/v4.1.0/python3/raygun4py/raygunprovider.py#L124-L125
https://github.com/MindscapeHQ/raygun4py/blob/v4.1.0/python3/raygun4py/raygunmsgs.py#L92-L97

Stacktrace:

/usr/local/lib/python3.6/site-packages/raygun4py/raygunprovider.py:96: in send_exception
    message = self._create_message(errorMessage, tags, custom_data, http_request, extra_environment_data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <raygun4py.raygunprovider.RaygunSender object at 0x7f6dbbbf0320>, raygunExceptionMessage = <raygun4py.raygunmsgs.RaygunErrorMessage object at 0x7f6dbbbccf60>
tags = ['django-novars'], user_custom_data = None
http_request = {'form': {}, 'headers': {'CONTENT_TYPE': 'application/octet-stream', 'HTTP_COOKIE': '', 'PATH_INFO': '/', 'QUERY_STRING': '', ...}, 'hostName': 'testserver', 'httpMethod': 'GET', ...}
extra_environment_data = {'frameworkVersion': '2.1.2'}

    def _create_message(self, raygunExceptionMessage, tags, user_custom_data, http_request, extra_environment_data):
        return raygunmsgs.RaygunMessageBuilder().new() \
            .set_machine_name(socket.gethostname()) \
            .set_version(self.userversion) \
            .set_client_details() \
            .set_exception_details(raygunExceptionMessage) \
            .set_environment_details(extra_environment_data) \
            .set_tags(tags) \
            .set_customdata(user_custom_data) \
>           .set_request_details(http_request) \
            .set_user(self.user) \
            .build()
E       AttributeError: 'NoneType' object has no attribute 'set_user'

/usr/local/lib/python3.6/site-packages/raygun4py/raygunprovider.py:124: AttributeError

UnicodeDecodeError if local args contains byte string

With the python 2 implementation, I'm getting a UnicodeDecodeError when trying to use send_exception. Looks like the jsonpickle.encode() is blowing up, specifically when trying to encode the raygunMessage['details']['error'].stackTrace list. One of the stackTrace items has a localVariables dict with a key verification that contains the following value: \x8d\x80\x92uK!M\xed\x16u3\x0b\xcc\xb1r1N\xdfs\x13,>\x01\x87\xae\xd0E\xe8\x02\x05\xd0d\xaa\xbd\xc9wc}\x1b\xbd\x06\xf8R3\xb4\xf3_F\x1b\xac}\xba\x11gul%v9\xf8\x0cp\x03\t

If the config option transmitLocalVariables is set to False, the issue goes away, as you might expect. However, I'd really like to be able to send all local vars!

Disable transmitting local/global variables by default

I was just integrating Raygun to a Flask API and trasmitting local/global variables caused a stackoverflow exception which took me quite some time to understand and fix.

The original error was a Mongo connection that failed because it was missing the connection parameters. When raygun4py tried to get local/global variables it re-triggered the connection initialization for some reason, which in turn caused the exception to be re-raised, etc.

I think these options should be disabled by default. Sending local/global variables automatically is of limited usefulness in real applications anyway. And as the Python Zen says "Explicit is better than implicit.", i.e. in this case this should be opt-in.

Crashed on logging error from a POST in Django

I was testing the provider and forced an exception handling a POST using Django and Django REST Framework and it triggered the exception masked behind a fault:

You cannot access body after reading from request's data stream

Going into the code, this appears as "request.body" is being accessed. I fixed locally for test purposes favoring request.data or by just removing the data in favor of the fallback when request doesn't respond to body.

Accessing directly optional request argument in WSGI middleware

QUERY_STRING is an optional argument for environ, hence should probably be accessed using get. Moreover, I think exceptions should not be just pass-ed. Currently, if QUERY_STRING is absent, KeyError will result in request be equal to {} which will throw another exception when we try to access headers.

Valid for both python2 and python3

python2/raygun4py/middleware/wsgi.py:

try:
    request = {
        'httpMethod': environ['REQUEST_METHOD'],
        'url': environ['PATH_INFO'],
        'ipAddress': environ['REMOTE_ADDR'],
        'hostName': environ['HTTP_HOST'].replace(' ', ''),
        'queryString': environ['QUERY_STRING'],
        'headers': {},
        'form': {},
        'rawData': {}
    }
except Exception:
    pass

for key, value in environ.items():
    if key.startswith('HTTP_'):
       request['headers'][key] = value

transmit_global_variables is broken

The transmit_global_variables should transmit globals respective to the module of the bottom frame of the traceback (e.g. frame.f_globals much like frame.f_locals is used for local vars). Here's the code in question:

https://github.com/MindscapeHQ/raygun4py/blob/master/python2/raygun4py/raygunmsgs.py#L121

What this does is transmit the globals of the raygunmsgs.py module, which are almost certainly completely unrelated to the actual error being logged, unless I'm missing something?

How to stop sending environment variables with errors with flask

I've configured Raygun with flask as per the installation instructions:

from flask import Flask, current_app
from raygun4py.middleware import flask

app = Flask(__name__)

flask.Provider(app, 'your_apikey').attach()

How do you set the config options with this so that the environment variables don't get sent with the exception?

Logging method does not work, at all

Sample code:

import sys
import os
import logging
import cratejoy.util.raygun as raygun

log = logging.getLogger(u"cratejoy")

raygun.init_raygun(os.environ.get('RAYGUN_STORE_KEY'))

try:
    log.debug(u"Sup")
except Exception as e:
    print "Caught exception on a regular log statement"
    print e
    sys.exit(1)

try:
    raise Exception(u"wtf")
except Exception as e:
    log.exception(u"error")

The first log statement should NOT except and it should get to the bottom try/catch. It dies at the first one.

(venv)amir-laptop:cratejoy aelaguiz$ foreman run python cratejoy/scripts/error.py
2014-01-02 15:47:04,055 [57104] [DEBUG] error: Sup
<LogRecord: cratejoy, 10, cratejoy/scripts/error.py, 11, "Sup">
Caught exception on a regular log statement
local variable 'exc' referenced before assignment

The code in question in the raygun library:

    def emit(self, record):
        print record
        if record.exc_info:
            exc = record.exc_info

        tags = None
        userCustomData = { "Logger Message" : record.msg }
        request = None
        className = None
        self.sender.send(exc[0], exc[1], exc[2], className, tags, userCustomData, request)

Clearly it will reference exc[0] if no exc_info is attached to a log record. So basically this only works for outputting errors.

Graceful degradation of error logging

Hi all,

So I was just tailing our production logs and found a few of these errors:

172.17.42.1 - - [06/Oct/2016:12:29:45] "" 200 - "" ""
Exception in thread Thread-8:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 811, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 764, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/var/www/a8_product/database/__init__.py", line 83, in _connection
    return f(*args, **kwargs)
  File "/var/www/a8_product/controllers/briefs/refresh.py", line 121, in _refresh_brief_articles
    populate_brief(db_brief, event=event)
  File "/var/www/a8_product/controllers/briefs/refresh.py", line 29, in populate_brief
    audit_brief(db_brief, event)
  File "/var/www/a8_product/controllers/briefs/audit.py", line 15, in audit_brief
    raygun_log(e.message, custom_data={'brief_uuid': str(db_brief.uuid)})
  File "/var/www/a8_product/lib/raygun_logging.py", line 10, in raygun_log
    return _raygun_log(*args, **kwargs)
  File "/usr/lib/python2.7/site-packages/hg_logging/raygun/logger.py", line 34, in raygun_log
    provider.sender.send_exception(**kwargs)
  File "/usr/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 106, in send_exception
    message = self._create_message(errorMessage, tags, custom_data, http_request, extra_environment_data)
  File "/usr/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 131, in _create_message
    .set_environment_details(extra_environment_data) \
  File "/usr/lib/python2.7/site-packages/raygun4py/raygunmsgs.py", line 35, in set_environment_details
    "architecture": platform.architecture()[0],
  File "/usr/lib64/python2.7/platform.py", line 1111, in architecture
    output = _syscmd_file(executable, '')
  File "/usr/lib64/python2.7/platform.py", line 1054, in _syscmd_file
    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  File "/usr/lib64/python2.7/site-packages/gevent/subprocess.py", line 534, in __init__
    reraise(*exc_info)
  File "/usr/lib64/python2.7/site-packages/gevent/subprocess.py", line 503, in __init__
    restore_signals, start_new_session)
  File "/usr/lib64/python2.7/site-packages/gevent/subprocess.py", line 1112, in _execute_child
    self.pid = fork_and_watch(self._on_child, self._loop, True, fork)
  File "/usr/lib64/python2.7/site-packages/gevent/os.py", line 341, in fork_and_watch
    watcher = loop.child(pid, ref=ref)
  File "gevent/corecext.pyx", line 511, in gevent.corecext.loop.child (gevent/gevent.corecext.c:9881)
  File "gevent/corecext.pyx", line 1876, in gevent.corecext.child.__init__ (gevent/gevent.corecext.c:32615)
TypeError: child watchers are only available on the default loop

This particular trace is from a background thread, launched as a part of a web request which altered an object which we audit out S3. Obviously have a bug somewhere that is causing that process to fail, now I see this error is stopping the original bug being recorded in raygun ๐Ÿ‘Ž

I'm in a bit of bind here, looks like the gevent bug (see warning at the top: http://www.gevent.org/gevent.subprocess.html) is a limitation that won't be fixed. I'm trying porting our background threads over to gevent but I figured I'd suggest a new feature too.

So we never really use the architecture stuff when debugging and I'd much rather have the error with less detail than no error whatsoever. So can I ask for either the ability to turn off/on feature enrichment manually and/or to have it gracefully fallback so it just get's the minimum it needs but can still log the error.

Cheers,
Alex

Any good reason to pin down the dependencies?

I notice the dependencies of this library are being aggressively pinned down.

'jsonpickle == 0.9.2',
'blinker == 1.3.0',
'requests == 2.9.1'

Now, while this is a very good practice for application-like projects, for libraries (such as raygun4py) this is actually not recommended, unless extremely good reasons exists.

Just to better explain a use case, in my flask powered project, I'm trying to use the latest flask today with the latest blinker today, but this library will force me to use blinker==1.3.0.

If there are no any other reasons to do that, can you please unlock the dependencies and let the clients of your library to worry about pinning the versions?

Possible bug in RaygunSender._post?

It appears that the following line encounters a NameError when attempts to refer to log on the RaygunSender class without using the class or self to make the reference.

https://github.com/MindscapeHQ/raygun4py/blob/master/python2/raygun4py/raygunprovider.py#L167

It appears to me that this line should read self.log.error(...) as opposed to log.error(...).

I hit this, but I'm doing some rather hackish things with RaygunSender at the moment, and so I'm not confident the bug is on the raygun4py side as opposed to within my own usage of it.

Here's the traceback:

Traceback (most recent call last):
  File "code/manage.py", line 8, in <module>
    execute_from_command_line(sys.argv)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 354, in execute_from_command_line
    utility.execute()
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 346, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/core/management/base.py", line 394, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/core/management/base.py", line 445, in execute
    output = self.handle(*args, **options)

  ... snip a few frames that basically just call myinstance.save() ...

  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/base.py", line 734, in save
    force_update=force_update, update_fields=update_fields)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/base.py", line 762, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/base.py", line 827, in _save_table
    forced_update)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/base.py", line 877, in _do_update
    return filtered._update(values) > 0
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/query.py", line 580, in _update
    return query.get_compiler(self.db).execute_sql(CURSOR)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 1062, in execute_sql
    cursor = super(SQLUpdateCompiler, self).execute_sql(result_type)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 829, in execute_sql
    sql, params = self.as_sql()
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 1030, in as_sql
    val = field.get_db_prep_save(val, connection=self.connection)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 710, in get_db_prep_save
    prepared=False)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1482, in get_db_prep_value
    value = self.get_prep_value(value)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1474, in get_prep_value
    RuntimeWarning)
  File "/usr/lib/python2.7/logging/__init__.py", line 1720, in _showwarning
    logger.warning("%s", s)
  File "/usr/lib/python2.7/logging/__init__.py", line 1171, in warning
    self._log(WARNING, msg, args, **kwargs)
  File "/usr/lib/python2.7/logging/__init__.py", line 1278, in _log
    self.handle(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 1288, in handle
    self.callHandlers(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 1328, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 751, in handle
    self.emit(record)
  File "/var/projects/myproject/code/myproject/logging_config.py", line 84, in emit
    self.sender.send_exception(exc_info=(exc_type, exc_value, None), frames=reversed(inspect.getouterframes(src_frame)))
  File "/var/projects/myproject/code/myproject/logging_config.py", line 32, in send_exception
    return raygunprovider.RaygunSender.send_exception(self, *args, **kwargs)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 109, in send_exception
    return self._post(message)
  File "/var/projects/myproject/virtualenv/local/local/lib/python2.7/site-packages/raygun4py/raygunprovider.py", line 167, in _post
    log.error(e)
NameError: global name 'log' is not defined

AttributeError when running the example script from the Heroku docs

Using raygun4py 3.1.2 and Python 2.7.10, I was following the official getting started instructions from Heroku: https://devcenter.heroku.com/articles/raygun#using-with-python

Here's my entire test script (running locally, not on Heroku, with RAYGUN_APIKEY set):

import os, sys, logging
from raygun4py import raygunprovider

logger = logging.getLogger("mylogger")

rgHandler = raygunprovider.RaygunHandler(os.environ.get('RAYGUN_APIKEY'))

logger.addHandler(rgHandler)

def log_exception(exc_type, exc_value, exc_traceback):
    print "Logging: %s" % exc_value
    logger.error("A python error occurred", exc_info = (exc_type, exc_value, exc_traceback))

sys.excepthook = log_exception

(a, b) = (1, 2, 3,)

And here's the error I'm getting from raygun4py:

Error in sys.excepthook:
Logging: too many values to unpack
Traceback (most recent call last):
  File "test.py", line 11, in log_exception
    logger.error("A python error occurred", exc_info = (exc_type, exc_value, exc_traceback))
  File "C:\Python\2.7\lib\logging\__init__.py", line 1191, in error
    self._log(ERROR, msg, args, **kwargs)
  File "C:\Python\2.7\lib\logging\__init__.py", line 1284, in _log
    self.handle(record)
  File "C:\Python\2.7\lib\logging\__init__.py", line 1294, in handle
    self.callHandlers(record)
  File "C:\Python\2.7\lib\logging\__init__.py", line 1334, in callHandlers
    hdlr.handle(record)
  File "C:\Python\2.7\lib\logging\__init__.py", line 757, in handle
    self.emit(record)
  File "C:\Python\2.7\lib\site-packages\raygun4py\raygunprovider.py", line 183, in emit
    self.sender.send_exception(userCustomData=userCustomData)
  File "C:\Python\2.7\lib\site-packages\raygun4py\raygunprovider.py", line 97, in send_exception
    errorMessage = raygunmsgs.RaygunErrorMessage(exc_type, exc_value, exc_traceback, options)
  File "C:\Python\2.7\lib\site-packages\raygun4py\raygunmsgs.py", line 118, in __init__
    self.className = exc_type.__name__
AttributeError: 'NoneType' object has no attribute '__name__'

Original exception was:
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    (a, b) = (1, 2, 3,)
ValueError: too many values to unpack

Obviously, the second exception is correct and to be expected, but the first one is not.

Add support for wildcard matching of environment variable blacklist

Related to #85, but a different functionality...

It would be nice to be able to provide wildcards around certain terms that you'd like to omit from environment variables. For example, right now our config contains a list of known ENV vars, but as more are added, they get sent through.

It would be nice to be able to specify wildcards like the following:

FILTERED_KEYS = [
  'AWS_*',
  '*PASSWORD*',
  '*KEY*'
]

Update Documentation with Best Practices for subclassing an exception

This would be really helpful to new users, or users that are migrating from Python2 to Python3.

It would be nice to have an example of a subclassed Exception in the documentation, along with best practices for using the subclassed Exception and how those affect what shows up in Raygun.

After using Raygun for some time, we still find things like unique ID's in the message field, which prevents the Raygun interface from merging these as they come in.

Some helpful posts for us getting our reporting cleaned up are Proper way to declare custom exceptions in modern python and How to make a custom exception class picklable The latter, in particular, is helpful when stack overflow errors are generated while debugging an app that also reports to Raygun.

Errors should be logged instead of printed to stderr

There's a few places (such as raygunprovider.py) that print error messages to stderr. It'd be better to log these as errors using standard Python logging so apps can filter them and log them to the correct place.

(In particular I'm hitting these messages while running tests in Django locally, where messages about not logging to Raygun don't matter.)

It'd also help categorize the errors; the message linked above would be better-suited as a warning rather than an error, since execution continues even if no API key is set.

I hope to submit a PR to fix this within the next few days when I can get to it. :D

WSGI middleware exception on close() looks broken.

Just on visual inspection the code at:

looks broken.

        finally:
            if chunk and hasattr(chunk, 'close') and callable(chunk.close):
                try:
                    chunk.close()
                except Exception as e:
                    request = build_request(environ)
                    self.send_exception(exception=e, request=request)

Shouldn't that be calling self.sender.send_exception()?

You also aren't reraising the exception.

Technically you also shouldn't be checking to see if close() is callable and instead should just call it. By checking to see if it is callable you are hiding to a higher layer that the close() method wasn't callable, which is actually an error in itself as far as the code satisfying the WSGI specification.

AttributeError: 'Provider' object has no attribute '_is_coroutine'

When I try to upgrade Django 3.2.16 to 4.1.4, I'm getting

AttributeError: 'Provider' object has no attribute '_is_coroutine'
  File "django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "django/utils/deprecation.py", line 131, in __call__
    if self._is_coroutine:

I'm using the following in my list of MIDDLEWAREs:

MIDDLEWARE = [
    ...
    'raygun4py.middleware.django.Provider'
]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.