Merge pull request #43857 from eyj/sentry_return

Sentry return
This commit is contained in:
Gareth J. Greenaway 2017-12-07 12:02:11 -08:00 committed by GitHub
commit 88f8d337d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 81 deletions

View File

@ -52,6 +52,7 @@ from salt.ext import six
try:
from raven import Client
from raven.transport.http import HTTPTransport
has_raven = True
except ImportError:
@ -77,88 +78,8 @@ def returner(ret):
Failed states will be appended as separate list for convenience.
'''
def ret_is_not_error(result):
if result.get('return') and isinstance(result['return'], dict):
result_dict = result['return']
is_staterun = all('-' in key for key in result_dict.keys())
if is_staterun:
failed_states = {}
for state_id, state_result in six.iteritems(result_dict):
if not state_result['result']:
failed_states[state_id] = state_result
if failed_states:
result['failed_states'] = failed_states
return False
if result.get('success') and result.get('retcode', 0) == 0:
return True
return False
def get_message():
return 'func: {fun}, jid: {jid}'.format(fun=ret['fun'], jid=ret['jid'])
def connect_sentry(message, result):
'''
Connect to the Sentry server
'''
pillar_data = __salt__['pillar.raw']()
grains = __salt__['grains.items']()
raven_config = pillar_data['raven']
hide_pillar = raven_config.get('hide_pillar')
sentry_data = {
'result': result,
'pillar': 'HIDDEN' if hide_pillar else pillar_data,
'grains': grains
}
data = {
'platform': 'python',
'culprit': message,
'level': 'error'
}
tags = {}
if 'tags' in raven_config:
for tag in raven_config['tags']:
tags[tag] = grains[tag]
if ret_is_not_error(ret):
data['level'] = 'info'
if raven_config.get('report_errors_only') and data['level'] != 'error':
return
if raven_config.get('dsn'):
client = Client(raven_config.get('dsn'))
else:
try:
servers = []
for server in raven_config['servers']:
servers.append(server + '/api/store/')
client = Client(
servers=servers,
public_key=raven_config['public_key'],
secret_key=raven_config['secret_key'],
project=raven_config['project']
)
except KeyError as missing_key:
logger.error(
'Sentry returner needs key \'%s\' in pillar',
missing_key
)
return
try:
msgid = client.capture('raven.events.Message', message=message, data=data, extra=sentry_data, tags=tags)
logger.info('Message id %s written to sentry', msgid)
except Exception as exc:
logger.error(
'Can\'t send message to sentry: {0}'.format(exc),
exc_info=True
)
try:
connect_sentry(get_message(), ret)
_connect_sentry(_get_message(ret), ret)
except Exception as err:
logger.error(
'Can\'t run connect_sentry: {0}'.format(err),
@ -166,6 +87,105 @@ def returner(ret):
)
def _ret_is_not_error(result):
if result.get('return') and isinstance(result['return'], dict):
result_dict = result['return']
is_staterun = all('-' in key for key in result_dict.keys())
if is_staterun:
failed_states = {}
for state_id, state_result in six.iteritems(result_dict):
if not state_result['result']:
failed_states[state_id] = state_result
if failed_states:
result['failed_states'] = failed_states
return False
return True
if result.get('success'):
return True
return False
def _get_message(ret):
if not ret.get('fun_args'):
return 'salt func: {}'.format(ret['fun'])
arg_string = ' '.join([arg for arg in ret['fun_args'] if isinstance(arg, six.string_types)])
kwarg_string = ''
if isinstance(ret['fun_args'], list) and len(ret['fun_args']) > 0:
kwargs = ret['fun_args'][-1]
if isinstance(kwargs, dict):
kwarg_string = ' '.join(sorted(['{}={}'.format(k, v) for k, v in kwargs.items() if not k.startswith('_')]))
return 'salt func: {fun} {argstr} {kwargstr}'.format(fun=ret['fun'], argstr=arg_string, kwargstr=kwarg_string).strip()
def _connect_sentry(message, result):
'''
Connect to the Sentry server
'''
pillar_data = __salt__['pillar.raw']()
grains = __salt__['grains.items']()
raven_config = pillar_data['raven']
hide_pillar = raven_config.get('hide_pillar')
sentry_data = {
'result': result,
'pillar': 'HIDDEN' if hide_pillar else pillar_data,
'grains': grains
}
data = {
'platform': 'python',
'culprit': message,
'level': 'error'
}
tags = {}
if 'tags' in raven_config:
for tag in raven_config['tags']:
tags[tag] = grains[tag]
if _ret_is_not_error(result):
data['level'] = 'info'
if raven_config.get('report_errors_only') and data['level'] != 'error':
return
if raven_config.get('dsn'):
client = Client(raven_config.get('dsn'), transport=HTTPTransport)
else:
try:
servers = []
for server in raven_config['servers']:
servers.append(server + '/api/store/')
client = Client(
servers=servers,
public_key=raven_config['public_key'],
secret_key=raven_config['secret_key'],
project=raven_config['project'],
transport=HTTPTransport
)
except KeyError as missing_key:
logger.error(
'Sentry returner needs key \'%s\' in pillar',
missing_key
)
return
try:
msgid = client.capture(
'raven.events.Message',
message=message,
data=data,
extra=sentry_data,
tags=tags
)
logger.info('Message id {} written to sentry'.format(msgid))
except Exception as exc:
logger.error(
'Can\'t send message to sentry: {0}'.format(exc),
exc_info=True
)
def prep_jid(nocache=False, passed_jid=None): # pylint: disable=unused-argument
'''
Do any work necessary to prepare a JID, including sending a custom id

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Import Python libs
from __future__ import absolute_import
# Import Salt Testing libs
from tests.support.unit import TestCase
# Import salt libs
import salt.returners.sentry_return as sentry
class SentryReturnerTestCase(TestCase):
'''
Test Sentry Returner
'''
ret = {'id': '12345',
'fun': 'mytest.func',
'fun_args': ['arg1', 'arg2', {'foo': 'bar'}],
'jid': '54321',
'return': 'Long Return containing a Traceback'}
def test_get_message(self):
self.assertEqual(sentry._get_message(self.ret), 'salt func: mytest.func arg1 arg2 foo=bar')
self.assertEqual(sentry._get_message({'fun': 'test.func', 'fun_args': []}), 'salt func: test.func')
self.assertEqual(sentry._get_message({'fun': 'test.func'}), 'salt func: test.func')