Esempio n. 1
0
def load(dsn, scope=None, transport_registry=None):
    """
    Parses a Sentry compatible DSN and loads it
    into the given scope.

    >>> import raven

    >>> dsn = 'https://*****:*****@sentry.local/project_id'

    >>> # Apply configuration to local scope
    >>> raven.load(dsn, locals())

    >>> # Return DSN configuration
    >>> options = raven.load(dsn)
    """

    if not transport_registry:
        from raven.transport import TransportRegistry, default_transports
        transport_registry = TransportRegistry(default_transports)

    url = urlparse(dsn)

    if not transport_registry.supported_scheme(url.scheme):
        raise ValueError('Unsupported Sentry DSN scheme: %r' % url.scheme)

    if scope is None:
        scope = {}
    scope_extras = transport_registry.compute_scope(url, scope)
    scope.update(scope_extras)

    return scope
Esempio n. 2
0
def load(dsn, scope=None, transport_registry=None):
    """
    Parses a Sentry compatible DSN and loads it
    into the given scope.

    >>> import raven

    >>> dsn = 'https://*****:*****@sentry.local/project_id'

    >>> # Apply configuration to local scope
    >>> raven.load(dsn, locals())

    >>> # Return DSN configuration
    >>> options = raven.load(dsn)
    """

    if not transport_registry:
        from raven.transport import TransportRegistry, default_transports
        transport_registry = TransportRegistry(default_transports)

    url = urlparse(dsn)

    if not transport_registry.supported_scheme(url.scheme):
        raise ValueError('Unsupported Sentry DSN scheme: %r' % url.scheme)

    if scope is None:
        scope = {}
    scope_extras = transport_registry.compute_scope(url, scope)
    scope.update(scope_extras)

    return scope
Esempio n. 3
0
 def from_string(cls, value, transport=None, transport_registry=None):
     if PY2:
         value = to_string(value)
     url = urlparse(value)
     if url.scheme not in ('http', 'https'):
         warnings.warn(
             'Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead.'
         )
     if transport is None:
         if not transport_registry:
             from raven.transport import TransportRegistry, default_transports
             transport_registry = TransportRegistry(default_transports)
         if not transport_registry.supported_scheme(url.scheme):
             raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme, value))
         transport = transport_registry.get_transport_cls(url.scheme)
     netloc = url.hostname
     if url.port:
         netloc += ':%s' % url.port
     path_bits = url.path.rsplit('/', 1)
     if len(path_bits) > 1:
         path = path_bits[0]
     else:
         path = ''
     project = path_bits[-1]
     if not all([netloc, project, url.username, url.password]):
         raise InvalidDsn('Invalid Sentry DSN: %r' % url.geturl())
     base_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, path)
     return cls(base_url=base_url,
                project=project,
                public_key=url.username,
                secret_key=url.password,
                options=dict(parse_qsl(url.query)),
                transport=transport)
Esempio n. 4
0
    def from_string(cls, value, transport=None, transport_registry=None):
        # in Python 2.x sending the DSN as a unicode value will eventually
        # cause issues in httplib
        if PY2:
            value = to_string(value)

        url = urlparse(value.strip())

        if url.scheme not in ("http", "https"):
            warnings.warn(
                "Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead."
            )

        if transport is None:
            if not transport_registry:
                from raven.transport import TransportRegistry, default_transports

                transport_registry = TransportRegistry(default_transports)

            if not transport_registry.supported_scheme(url.scheme):
                raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme, value))

            transport = transport_registry.get_transport_cls(url.scheme)

        netloc = url.hostname
        if url.port:
            netloc += ":%s" % url.port

        path_bits = url.path.rsplit("/", 1)
        if len(path_bits) > 1:
            path = path_bits[0]
        else:
            path = ""
        project = path_bits[-1]

        if not all([netloc, project, url.username, url.password]):
            raise InvalidDsn("Invalid Sentry DSN: %r" % url.geturl())

        base_url = "%s://%s%s" % (url.scheme.rsplit("+", 1)[-1], netloc, path)

        return cls(
            base_url=base_url,
            project=project,
            public_key=url.username,
            secret_key=url.password,
            options=dict(parse_qsl(url.query)),
            transport=transport,
        )
Esempio n. 5
0
    def from_string(cls, value, transport=None, transport_registry=None):
        url = urlparse(value)

        if url.scheme not in ('http', 'https'):
            warnings.warn('Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead.')

        if transport is None:
            if not transport_registry:
                from raven.transport import TransportRegistry, default_transports
                transport_registry = TransportRegistry(default_transports)

            if not transport_registry.supported_scheme(url.scheme):
                raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme))

            transport = transport_registry.get_transport_cls(url.scheme)

        netloc = url.hostname
        if url.port:
            netloc += ':%s' % url.port

        path_bits = url.path.rsplit('/', 1)
        if len(path_bits) > 1:
            path = path_bits[0]
        else:
            path = ''
        project = path_bits[-1]

        if not all([netloc, project, url.username, url.password]):
            raise InvalidDsn('Invalid Sentry DSN: %r' % url.geturl())

        base_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, path)

        return cls(
            base_url=base_url,
            project=project,
            public_key=url.username,
            secret_key=url.password,
            options=dict(parse_qsl(url.query)),
            transport=transport,
        )
class Client(object):
    """
    The base Raven client, which handles both local direct
    communication with Sentry (through the GroupedMessage API), as
    well as communicating over the HTTP API to multiple servers.

    Will read default configuration from the environment variable
    ``SENTRY_DSN`` if available.

    >>> from raven import Client

    >>> # Read configuration from ``os.environ['SENTRY_DSN']``
    >>> client = Client()

    >>> # Specify a DSN explicitly
    >>> client =
    >>> Client(dsn='https://*****:*****@sentry.local/project_id')

    >>> # Configure the client manually
    >>> client = Client(
    >>>     servers=['http://sentry.local/api/store/'],
    >>>     include_paths=['my.package'],
    >>>     project='project_id',
    >>>     public_key='public_key',
    >>>     secret_key='secret_key',
    >>> )

    >>> # Record an exception
    >>> try:
    >>>     1/0
    >>> except ZeroDivisionError:
    >>>     ident = client.get_ident(client.captureException())
    >>>     print "Exception caught; reference is %%s" %% ident
    """
    logger = logging.getLogger('raven')
    protocol_version = '2.0'

    _registry = TransportRegistry(transports=default_transports)

    def __init__(self,
                 servers=None,
                 include_paths=None,
                 exclude_paths=None,
                 timeout=None,
                 name=None,
                 auto_log_stacks=None,
                 key=None,
                 string_max_length=None,
                 list_max_length=None,
                 site=None,
                 public_key=None,
                 secret_key=None,
                 processors=None,
                 project=None,
                 dsn=None,
                 **kwargs):
        # configure loggers first
        cls = self.__class__
        self.state = ClientState()
        self.logger = logging.getLogger('%s.%s' %
                                        (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger('sentry.errors')

        if isinstance(servers, basestring):
            # must be a DSN:
            if dsn:
                # TODO: this should indicate what the caller can do to correct
                # the constructor
                msg = "You seem to be incorrectly instantiating the " + \
                      "raven Client class"
                raise ValueError(msg)
            dsn = servers
            servers = None

        if dsn is None and os.environ.get('SENTRY_DSN'):
            msg = "Configuring Raven from environment variable 'SENTRY_DSN'"
            self.logger.info(msg)
            dsn = os.environ['SENTRY_DSN']

        if dsn:
            # TODO: should we validate other options werent sent?
            urlparts = urlparse(dsn)
            msg = "Configuring Raven for host: %s://%s:%s" % (
                urlparts.scheme, urlparts.netloc, urlparts.path)
            self.logger.info(msg)
            options = raven.load(dsn, transport_registry=self._registry)
            servers = options['SENTRY_SERVERS']
            project = options['SENTRY_PROJECT']
            public_key = options['SENTRY_PUBLIC_KEY']
            secret_key = options['SENTRY_SECRET_KEY']

        # servers may be set to a NoneType (for Django)
        if servers and not (key or (secret_key and public_key)):
            msg = 'Missing configuration for client. Please see documentation.'
            raise TypeError(msg)

        self.servers = servers
        self.include_paths = set(include_paths or defaults.INCLUDE_PATHS)
        self.exclude_paths = set(exclude_paths or defaults.EXCLUDE_PATHS)
        self.timeout = int(timeout or defaults.TIMEOUT)
        self.name = unicode(name or defaults.NAME)
        self.auto_log_stacks = bool(auto_log_stacks
                                    or defaults.AUTO_LOG_STACKS)
        self.key = str(key or defaults.KEY)
        self.string_max_length = int(string_max_length
                                     or defaults.MAX_LENGTH_STRING)
        self.list_max_length = int(list_max_length or defaults.MAX_LENGTH_LIST)
        if (site or defaults.SITE):
            self.site = unicode(site or defaults.SITE)
        else:
            self.site = None
        self.public_key = public_key
        self.secret_key = secret_key
        self.project = project or defaults.PROJECT

        self.processors = processors or defaults.PROCESSORS
        self.module_cache = ModuleProxyCache()

    @classmethod
    def register_scheme(cls, scheme, transport_class):
        cls._registry.register_scheme(scheme, transport_class)

    def get_processors(self):
        for processor in self.processors:
            yield self.module_cache[processor](self)

    def get_ident(self, result):
        """
        Returns a searchable string representing a message.

        >>> result = client.process(**kwargs)
        >>> ident = client.get_ident(result)
        """
        return '$'.join(result)

    def get_handler(self, name):
        return self.module_cache[name](self)

    def build_msg(self,
                  event_type,
                  data=None,
                  date=None,
                  time_spent=None,
                  extra=None,
                  stack=None,
                  public_key=None,
                  **kwargs):
        """
        Captures, processes and serializes an event into a dict object
        """

        # create ID client-side so that it can be passed to application
        event_id = uuid.uuid4().hex

        if data is None:
            data = {}
        if extra is None:
            extra = {}
        if not date:
            date = datetime.datetime.utcnow()
        if stack is None:
            stack = self.auto_log_stacks

        if '.' not in event_type:
            # Assume it's a builtin
            event_type = 'raven.events.%s' % event_type

        handler = self.get_handler(event_type)
        result = handler.capture(**kwargs)

        # data (explicit) culprit takes over auto event detection
        culprit = result.pop('culprit', None)
        if data.get('culprit'):
            culprit = data['culprit']

        for k, v in result.iteritems():
            if k not in data:
                data[k] = v

        if stack and 'sentry.interfaces.Stacktrace' not in data:
            if stack is True:
                frames = iter_stack_frames()

            else:
                frames = stack

            data.update({
                'sentry.interfaces.Stacktrace': {
                    'frames':
                    varmap(
                        lambda k, v: shorten(v,
                                             string_length=self.
                                             string_max_length,
                                             list_length=self.list_max_length),
                        get_stack_info(frames))
                },
            })

        if 'sentry.interfaces.Stacktrace' in data and not culprit:
            culprit = get_culprit(
                data['sentry.interfaces.Stacktrace']['frames'],
                self.include_paths, self.exclude_paths)

        if not data.get('level'):
            data['level'] = logging.ERROR
        data['modules'] = get_versions(self.include_paths)
        data['server_name'] = self.name
        data.setdefault('extra', {})
        data.setdefault('level', logging.ERROR)

        # Shorten lists/strings
        for k, v in extra.iteritems():
            data['extra'][k] = shorten(v,
                                       string_length=self.string_max_length,
                                       list_length=self.list_max_length)

        if culprit:
            data['culprit'] = culprit

        if 'checksum' not in data:
            checksum_bits = handler.get_hash(data)
        else:
            checksum_bits = data['checksum']

        if isinstance(checksum_bits, (list, tuple)):
            checksum = hashlib.md5()
            for bit in checksum_bits:
                checksum.update(to_string(bit))
            checksum = checksum.hexdigest()
        else:
            checksum = checksum_bits

        data['checksum'] = checksum

        # Run the data through processors
        for processor in self.get_processors():
            data.update(processor.process(data))

        # Make sure all data is coerced
        data = transform(data)

        if 'message' not in data:
            data['message'] = handler.to_string(data)

        data.update({
            'timestamp': date,
            'time_spent': time_spent,
            'event_id': event_id,
        })
        data.setdefault('project', self.project)
        data.setdefault('site', self.site)
        data.setdefault('public_key', self.public_key)

        return data

    def capture(self,
                event_type,
                data=None,
                date=None,
                time_spent=None,
                extra=None,
                stack=None,
                public_key=None,
                **kwargs):
        """
        Captures and processes an event and pipes it off to SentryClient.send.

        To use structured data (interfaces) with capture:

        >>> capture('Message', message='foo', data={
        >>>     'sentry.interfaces.Http': {
        >>>         'url': '...',
        >>>         'data': {},
        >>>         'query_string': '...',
        >>>         'method': 'POST',
        >>>     },
        >>>     'logger': 'logger.name',
        >>>     'site': 'site.name',
        >>> }, extra={
        >>>     'key': 'value',
        >>> })

        The finalized ``data`` structure contains the following (some optional)
        builtin values:

        >>> {
        >>>     # the culprit and version information
        >>>     'culprit': 'full.module.name', # or /arbitrary/path
        >>>
        >>>     # all detectable installed modules
        >>>     'modules': {
        >>>         'full.module.name': 'version string',
        >>>     },
        >>>
        >>>     # arbitrary data provided by user
        >>>     'extra': {
        >>>         'key': 'value',
        >>>     }
        >>> }

        :param event_type: the module path to the Event class. Builtins can use
                           shorthand class notation and exclude the full module
                           path.
        :param data: the data base, useful for specifying structured data
                           interfaces. Any key which contains a '.' will be
                           assumed to be a data interface.
        :param date: the datetime of this event
        :param time_spent: a float value representing the duration of the event
        :param event_id: a 32-length unique string identifying this event
        :param extra: a dictionary of additional standard metadata
        :param culprit: a string representing the cause of this event
                        (generally a path to a function)
        :param public_key: the public key to use for this message
                           (defaults to ``Client.public_key``)
        :return: a 32-length string identifying this event
        """

        data = self.build_msg(event_type,
                              data,
                              date,
                              time_spent,
                              extra,
                              stack,
                              public_key=public_key,
                              **kwargs)

        self.send(**data)

        return (data['event_id'], data['checksum'])

    def _send_remote(self, url, data, headers={}):
        parsed = urlparse(url)

        transport = self._registry.get_transport(parsed)
        # Push sending of errors to the background so they're async
        deferred.defer(transport.send, data, headers)

    def _get_log_message(self, data):
        # decode message so we can show the actual event
        try:
            data = self.decode(data)
        except:
            message = '<failed decoding data>'
        else:
            message = data.pop('message', '<no message value>')
        return message

    def send_remote(self, url, data, headers={}):
        if not self.state.should_try():
            message = self._get_log_message(data)
            self.error_logger.error(message)
            return

        try:
            self._send_remote(url=url, data=data, headers=headers)
        except Exception, e:
            if isinstance(e, urllib2.HTTPError):
                body = e.read()
                self.error_logger.error(
                    'Unable to reach Sentry log server: %s (url: %%s, body: %%s)'
                    % (e, ),
                    url,
                    body,
                    exc_info=True,
                    extra={'data': {
                        'body': body,
                        'remote_url': url
                    }})
            else:
                tmpl = 'Unable to reach Sentry log server: %s (url: %%s)'
                self.error_logger.error(tmpl % (e, ),
                                        url,
                                        exc_info=True,
                                        extra={'data': {
                                            'remote_url': url
                                        }})

            message = self._get_log_message(data)
            self.error_logger.error('Failed to submit message: %r', message)
            self.state.set_fail()
        else:
Esempio n. 7
0
def setup_handlers():
    if 'sentry_handler' not in __opts__:
        log.debug('No \'sentry_handler\' key was found in the configuration')
        return False
    options = {}
    dsn = get_config_value('dsn')
    if dsn is not None:
        try:
            # support raven ver 5.5.0
            from raven.transport import TransportRegistry, default_transports
            from raven.utils.urlparse import urlparse
            transport_registry = TransportRegistry(default_transports)
            url = urlparse(dsn)
            if not transport_registry.supported_scheme(url.scheme):
                raise ValueError('Unsupported Sentry DSN scheme: {0}'.format(
                    url.scheme))
            dsn_config = {}
            conf_extras = transport_registry.compute_scope(url, dsn_config)
            dsn_config.update(conf_extras)
            options.update({
                'project': dsn_config['SENTRY_PROJECT'],
                'servers': dsn_config['SENTRY_SERVERS'],
                'public_key': dsn_config['SENTRY_PUBLIC_KEY'],
                'secret_key': dsn_config['SENTRY_SECRET_KEY']
            })
        except ValueError as exc:
            log.info('Raven failed to parse the configuration provided '
                     'DSN: {0}'.format(exc))

    # Allow options to be overridden if previously parsed, or define them
    for key in ('project', 'servers', 'public_key', 'secret_key'):
        config_value = get_config_value(key)
        if config_value is None and key not in options:
            log.debug('The required \'sentry_handler\' configuration key, '
                      '\'{0}\', is not properly configured. Not configuring '
                      'the sentry logging handler.'.format(key))
            return
        elif config_value is None:
            continue
        options[key] = config_value

    # site: An optional, arbitrary string to identify this client installation.
    options.update({
        # site: An optional, arbitrary string to identify this client
        # installation
        'site': get_config_value('site'),

        # name: This will override the server_name value for this installation.
        # Defaults to socket.gethostname()
        'name': get_config_value('name'),

        # exclude_paths: Extending this allow you to ignore module prefixes
        # when sentry attempts to discover which function an error comes from
        'exclude_paths': get_config_value('exclude_paths', ()),

        # include_paths: For example, in Django this defaults to your list of
        # INSTALLED_APPS, and is used for drilling down where an exception is
        # located
        'include_paths': get_config_value('include_paths', ()),

        # list_max_length: The maximum number of items a list-like container
        # should store.
        'list_max_length': get_config_value('list_max_length'),

        # string_max_length: The maximum characters of a string that should be
        # stored.
        'string_max_length': get_config_value('string_max_length'),

        # auto_log_stacks: Should Raven automatically log frame stacks
        # (including locals) all calls as it would for exceptions.
        'auto_log_stacks': get_config_value('auto_log_stacks'),

        # timeout: If supported, the timeout value for sending messages to
        # remote.
        'timeout': get_config_value('timeout', 1),

        # processors: A list of processors to apply to events before sending
        # them to the Sentry server. Useful for sending additional global state
        # data or sanitizing data that you want to keep off of the server.
        'processors': get_config_value('processors'),

        # dsn: Ensure the DSN is passed into the client
        'dsn': dsn
    })

    client = raven.Client(**options)
    context = get_config_value('context')
    context_dict = {}
    if context is not None:
        for tag in context:
            tag_value = __salt__['grains.get'](tag)
            if len(tag_value) > 0:
                context_dict[tag] = tag_value
        if len(context_dict) > 0:
            client.context.merge({'tags': context_dict})
    try:
        handler = SentryHandler(client)
        handler.setLevel(LOG_LEVELS[get_config_value('log_level', 'error')])
        return handler
    except ValueError as exc:
        log.debug(
            'Failed to setup the sentry logging handler: {0}'.format(exc),
            exc_info=exc)
Esempio n. 8
0
def setup_handlers():
    """
    sets up the sentry handler
    """
    if not __opts__.get("sentry_handler"):
        log.debug("'sentry_handler' config is empty or not defined")
        return False

    # Regenerating dunders can be expensive, so only do it if the user enables
    # `sentry_handler` as checked above
    __grains__ = salt.loader.grains(__opts__)
    __salt__ = salt.loader.minion_mods(__opts__)

    options = {}
    dsn = get_config_value("dsn")
    if dsn is not None:
        try:
            # support raven ver 5.5.0
            from raven.transport import TransportRegistry, default_transports
            from raven.utils.urlparse import urlparse

            transport_registry = TransportRegistry(default_transports)
            url = urlparse(dsn)
            if not transport_registry.supported_scheme(url.scheme):
                raise ValueError("Unsupported Sentry DSN scheme: {}".format(
                    url.scheme))
        except ValueError as exc:
            log.info(
                "Raven failed to parse the configuration provided DSN: %s",
                exc)

    if not dsn:
        for key in ("project", "servers", "public_key", "secret_key"):
            config_value = get_config_value(key)
            if config_value is None and key not in options:
                log.debug(
                    "The required 'sentry_handler' configuration key, "
                    "'%s', is not properly configured. Not configuring "
                    "the sentry logging handler.",
                    key,
                )
                return
            elif config_value is None:
                continue
            options[key] = config_value

    # site: An optional, arbitrary string to identify this client installation.
    options.update({
        # site: An optional, arbitrary string to identify this client
        # installation
        "site": get_config_value("site"),
        # name: This will override the server_name value for this installation.
        # Defaults to socket.gethostname()
        "name": get_config_value("name"),
        # exclude_paths: Extending this allow you to ignore module prefixes
        # when sentry attempts to discover which function an error comes from
        "exclude_paths": get_config_value("exclude_paths", ()),
        # include_paths: For example, in Django this defaults to your list of
        # INSTALLED_APPS, and is used for drilling down where an exception is
        # located
        "include_paths": get_config_value("include_paths", ()),
        # list_max_length: The maximum number of items a list-like container
        # should store.
        "list_max_length": get_config_value("list_max_length"),
        # string_max_length: The maximum characters of a string that should be
        # stored.
        "string_max_length": get_config_value("string_max_length"),
        # auto_log_stacks: Should Raven automatically log frame stacks
        # (including locals) all calls as it would for exceptions.
        "auto_log_stacks": get_config_value("auto_log_stacks"),
        # timeout: If supported, the timeout value for sending messages to
        # remote.
        "timeout": get_config_value("timeout", 1),
        # processors: A list of processors to apply to events before sending
        # them to the Sentry server. Useful for sending additional global state
        # data or sanitizing data that you want to keep off of the server.
        "processors": get_config_value("processors"),
        # dsn: Ensure the DSN is passed into the client
        "dsn": dsn,
    })

    client = raven.Client(**options)
    context = get_config_value("context")
    context_dict = {}
    if context is not None:
        for tag in context:
            try:
                tag_value = __grains__[tag]
            except KeyError:
                log.debug("Sentry tag '%s' not found in grains.", tag)
                continue
            if tag_value:
                context_dict[tag] = tag_value
        if context_dict:
            client.context.merge({"tags": context_dict})
    try:
        handler = SentryHandler(client)

        exclude_patterns = get_config_value("exclude_patterns", None)
        if exclude_patterns:
            filter_regexes = [
                re.compile(pattern) for pattern in exclude_patterns
            ]

            class FilterExcludedMessages:
                @staticmethod
                def filter(record):
                    m = record.getMessage()
                    return not any(regex.search(m) for regex in filter_regexes)

            handler.addFilter(FilterExcludedMessages())

        handler.setLevel(LOG_LEVELS[get_config_value("log_level", "error")])
        return handler
    except ValueError as exc:
        log.debug("Failed to setup the sentry logging handler", exc_info=True)
Esempio n. 9
0
def setup_handlers():
    '''
    sets up the sentry handler
    '''
    __grains__ = salt.loader.grains(__opts__)
    __salt__ = salt.loader.minion_mods(__opts__)
    if 'sentry_handler' not in __opts__:
        log.debug('No \'sentry_handler\' key was found in the configuration')
        return False
    options = {}
    dsn = get_config_value('dsn')
    if dsn is not None:
        try:
            # support raven ver 5.5.0
            from raven.transport import TransportRegistry, default_transports
            from raven.utils.urlparse import urlparse
            transport_registry = TransportRegistry(default_transports)
            url = urlparse(dsn)
            if not transport_registry.supported_scheme(url.scheme):
                raise ValueError('Unsupported Sentry DSN scheme: {0}'.format(
                    url.scheme))
        except ValueError as exc:
            log.info(
                'Raven failed to parse the configuration provided DSN: %s',
                exc)

    if not dsn:
        for key in ('project', 'servers', 'public_key', 'secret_key'):
            config_value = get_config_value(key)
            if config_value is None and key not in options:
                log.debug(
                    'The required \'sentry_handler\' configuration key, '
                    '\'%s\', is not properly configured. Not configuring '
                    'the sentry logging handler.', key)
                return
            elif config_value is None:
                continue
            options[key] = config_value

    # site: An optional, arbitrary string to identify this client installation.
    options.update({
        # site: An optional, arbitrary string to identify this client
        # installation
        'site': get_config_value('site'),

        # name: This will override the server_name value for this installation.
        # Defaults to socket.gethostname()
        'name': get_config_value('name'),

        # exclude_paths: Extending this allow you to ignore module prefixes
        # when sentry attempts to discover which function an error comes from
        'exclude_paths': get_config_value('exclude_paths', ()),

        # include_paths: For example, in Django this defaults to your list of
        # INSTALLED_APPS, and is used for drilling down where an exception is
        # located
        'include_paths': get_config_value('include_paths', ()),

        # list_max_length: The maximum number of items a list-like container
        # should store.
        'list_max_length': get_config_value('list_max_length'),

        # string_max_length: The maximum characters of a string that should be
        # stored.
        'string_max_length': get_config_value('string_max_length'),

        # auto_log_stacks: Should Raven automatically log frame stacks
        # (including locals) all calls as it would for exceptions.
        'auto_log_stacks': get_config_value('auto_log_stacks'),

        # timeout: If supported, the timeout value for sending messages to
        # remote.
        'timeout': get_config_value('timeout', 1),

        # processors: A list of processors to apply to events before sending
        # them to the Sentry server. Useful for sending additional global state
        # data or sanitizing data that you want to keep off of the server.
        'processors': get_config_value('processors'),

        # dsn: Ensure the DSN is passed into the client
        'dsn': dsn
    })

    client = raven.Client(**options)
    context = get_config_value('context')
    context_dict = {}
    if context is not None:
        for tag in context:
            try:
                tag_value = __grains__[tag]
            except KeyError:
                log.debug('Sentry tag \'%s\' not found in grains.', tag)
                continue
            if len(tag_value) > 0:
                context_dict[tag] = tag_value
        if len(context_dict) > 0:
            client.context.merge({'tags': context_dict})
    try:
        handler = SentryHandler(client)

        exclude_patterns = get_config_value('exclude_patterns', None)
        if exclude_patterns:
            filter_regexes = [
                re.compile(pattern) for pattern in exclude_patterns
            ]

            class FilterExcludedMessages(object):
                @staticmethod
                def filter(record):
                    m = record.getMessage()
                    return not any(regex.search(m) for regex in filter_regexes)

            handler.addFilter(FilterExcludedMessages())

        handler.setLevel(LOG_LEVELS[get_config_value('log_level', 'error')])
        return handler
    except ValueError as exc:
        log.debug('Failed to setup the sentry logging handler', exc_info=True)
Esempio n. 10
0
def setup_handlers():
    if 'sentry_handler' not in __opts__:
        log.debug('No \'sentry_handler\' key was found in the configuration')
        return False
    options = {}
    dsn = get_config_value('dsn')
    if dsn is not None:
        try:
            # support raven ver 5.5.0
            from raven.transport import TransportRegistry, default_transports
            from raven.utils.urlparse import urlparse
            transport_registry = TransportRegistry(default_transports)
            url = urlparse(dsn)
            if not transport_registry.supported_scheme(url.scheme):
                raise ValueError('Unsupported Sentry DSN scheme: {0}'.format(url.scheme))
            dsn_config = {}
            conf_extras = transport_registry.compute_scope(url, dsn_config)
            dsn_config.update(conf_extras)
            options.update({
                'project': dsn_config['SENTRY_PROJECT'],
                'servers': dsn_config['SENTRY_SERVERS'],
                'public_key': dsn_config['SENTRY_PUBLIC_KEY'],
                'secret_key': dsn_config['SENTRY_SECRET_KEY']
            })
        except ValueError as exc:
            log.info(
                'Raven failed to parse the configuration provided '
                'DSN: {0}'.format(exc)
            )

    # Allow options to be overridden if previously parsed, or define them
    for key in ('project', 'servers', 'public_key', 'secret_key'):
        config_value = get_config_value(key)
        if config_value is None and key not in options:
            log.debug(
                'The required \'sentry_handler\' configuration key, '
                '\'{0}\', is not properly configured. Not configuring '
                'the sentry logging handler.'.format(key)
            )
            return
        elif config_value is None:
            continue
        options[key] = config_value

    # site: An optional, arbitrary string to identify this client installation.
    options.update({
        # site: An optional, arbitrary string to identify this client
        # installation
        'site': get_config_value('site'),

        # name: This will override the server_name value for this installation.
        # Defaults to socket.gethostname()
        'name': get_config_value('name'),

        # exclude_paths: Extending this allow you to ignore module prefixes
        # when sentry attempts to discover which function an error comes from
        'exclude_paths': get_config_value('exclude_paths', ()),

        # include_paths: For example, in Django this defaults to your list of
        # INSTALLED_APPS, and is used for drilling down where an exception is
        # located
        'include_paths': get_config_value('include_paths', ()),

        # list_max_length: The maximum number of items a list-like container
        # should store.
        'list_max_length': get_config_value('list_max_length'),

        # string_max_length: The maximum characters of a string that should be
        # stored.
        'string_max_length': get_config_value('string_max_length'),

        # auto_log_stacks: Should Raven automatically log frame stacks
        # (including locals) all calls as it would for exceptions.
        'auto_log_stacks': get_config_value('auto_log_stacks'),

        # timeout: If supported, the timeout value for sending messages to
        # remote.
        'timeout': get_config_value('timeout', 1),

        # processors: A list of processors to apply to events before sending
        # them to the Sentry server. Useful for sending additional global state
        # data or sanitizing data that you want to keep off of the server.
        'processors': get_config_value('processors'),

        # dsn: Ensure the DSN is passed into the client
        'dsn': dsn
    })

    client = raven.Client(**options)
    context = get_config_value('context')
    context_dict = {}
    if context is not None:
        for tag in context:
            tag_value = __salt__['grains.get'](tag)
            if len(tag_value) > 0:
                context_dict[tag] = tag_value
        if len(context_dict) > 0:
            client.context.merge({'tags': context_dict})
    try:
        handler = SentryHandler(client)
        handler.setLevel(LOG_LEVELS[get_config_value('log_level', 'error')])
        return handler
    except ValueError as exc:
        log.debug(
            'Failed to setup the sentry logging handler: {0}'.format(exc),
            exc_info=exc
        )