Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

"""Error telemetry for `Sentry <https://sentry.io>`_.""" 

from urllib.parse import urlparse, urlunparse 

from subprocess import CalledProcessError 

import os 

 

from celery.utils.log import get_logger 

from safe_netrc import netrc, NetrcParseError 

import sentry_sdk 

from sentry_sdk.integrations import celery, flask, redis, tornado 

 

from . import _version 

from .util import SPHINX 

 

log = get_logger(__name__) 

 

__all__ = ('configure', 'DSN') 

 

DSN = 'https://sentry.io/1425216' 

"""Sentry data source name (DSN).""" 

 

 

def before_send(event, hint): 

"""Capture stderr and stdout from CalledProcessError exceptions.""" 

if 'exc_info' not in hint: 

return event 

 

_, e, _ = hint['exc_info'] 

if not isinstance(e, CalledProcessError): 

return event 

 

breadcrumbs = event.get('breadcrumbs', []) 

if len(breadcrumbs) < 1: 

return event 

breadcrumb = breadcrumbs[0] 

 

for key in ['stderr', 'stdout']: 

value = getattr(e, key) 

if value: 

breadcrumb.setdefault('data', {})[key] = value.decode( 

errors='replace') 

return event 

 

 

def _read_classad(filename): 

with open(filename) as f: 

for line in f: 

key, _, value = line.partition('=') 

key = key.strip() 

value = value.strip().strip('"') 

yield key, value 

 

 

def _add_htcondor(): 

"""Record HTCondor job information in Sentry.""" 

try: 

data = dict(_read_classad(os.environ['_CONDOR_JOB_AD'])) 

except (KeyError, IOError): 

return 

with sentry_sdk.configure_scope() as scope: 

scope.set_tag('htcondor.cluster_id', '{}.{}'.format( 

data['ClusterId'], data['ProcId'])) 

 

 

def configure(): 

"""Configure Sentry logging integration for Celery. 

 

See the `official instructions for Celery integration 

<https://docs.sentry.io/platforms/python/celery/>`_. 

 

Notes 

----- 

Add the API key username/pasword pair to your netrc file. 

 

""" 

# Catching NetrcParseError confuses sphinx. 

if SPHINX: # pragma: no cover 

return 

 

# Delayed import 

from . import app 

 

scheme, netloc, *rest = urlparse(DSN) 

 

try: 

auth = netrc().authenticators(netloc) 

if not auth: 

raise ValueError('No netrc entry found for {}'.format(netloc)) 

except (NetrcParseError, OSError, ValueError): 

log.exception('Disabling Sentry integration because we could not load ' 

'the username and password for %s from the netrc file', 

netloc) 

return 

 

# The "legacy" Sentry DSN requires a "public key" and a "private key", 

# which are transmitted as the username and password in the URL. 

# However, as of Sentry 9, then "private key" part is no longer required. 

username, _, _ = auth 

dsn = urlunparse( 

(scheme, '{}@{}'.format(username, netloc), *rest)) 

version = 'gwcelery-{}'.format(_version.get_versions()['version']) 

environment = app.conf['sentry_environment'] 

sentry_sdk.init(dsn, environment=environment, release=version, 

before_send=before_send, 

integrations=[celery.CeleryIntegration(), 

flask.FlaskIntegration(), 

redis.RedisIntegration(), 

tornado.TornadoIntegration()]) 

_add_htcondor()