Skip to content

Commit f9b1075

Browse files
committed
feat: Initial commit
0 parents  commit f9b1075

File tree

9 files changed

+885
-0
lines changed

9 files changed

+885
-0
lines changed

sentry_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .api import *
2+
from .api import __all__

sentry_sdk/_compat.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import sys
2+
3+
4+
PY2 = sys.version_info[0] == 2
5+
6+
if PY2:
7+
import urlparse
8+
text_type = unicode
9+
import Queue as queue
10+
number_types = (int, long, float)
11+
12+
def implements_str(cls):
13+
cls.__unicode__ = cls.__str__
14+
cls.__str__ = lambda x: unicode(x).encode('utf-8')
15+
return cls
16+
else:
17+
import urllib.parse as urlparse
18+
import queue
19+
text_type = str
20+
implements_str = lambda x: x
21+
number_types = (int, float)
22+
23+
24+
def with_metaclass(meta, *bases):
25+
class metaclass(type):
26+
def __new__(cls, name, this_bases, d):
27+
return meta(name, bases, d)
28+
return type.__new__(metaclass, 'temporary_class', (), {})

sentry_sdk/api.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import atexit
2+
3+
from .hub import Hub
4+
from .client import Client
5+
6+
7+
__all__ = ['Hub', 'Client']
8+
9+
10+
def public(f):
11+
__all__.append(f.__name__)
12+
return f
13+
14+
15+
class _InitGuard(object):
16+
17+
def __init__(self, client):
18+
self._client = client
19+
20+
def __enter__(self):
21+
return self
22+
23+
def __exit__(self, exc_type, exc_value, tb):
24+
c = self._client
25+
if c is not None:
26+
c.close()
27+
28+
29+
@public
30+
def init(*args, **kwargs):
31+
client = Client(*args, **kwargs)
32+
if client.dsn is not None:
33+
Hub.main.bind_client(client)
34+
return _InitGuard(client)
35+
36+
37+
@public
38+
def capture_event(event):
39+
hub = Hub.current
40+
if hub is not None:
41+
return hub.capture_event(event)
42+
43+
44+
@public
45+
def capture_message(message, level=None):
46+
hub = Hub.current
47+
if hub is not None:
48+
return hub.capture_message(message, level)
49+
50+
51+
@public
52+
def capture_exception(error=None):
53+
hub = Hub.current
54+
if hub is not None:
55+
return hub.capture_exception(error)
56+
57+
58+
@public
59+
def add_breadcrumb(crumb):
60+
hub = Hub.current
61+
if hub is not None:
62+
return hub.add_breadcrumb(crumb)

sentry_sdk/client.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import os
2+
import uuid
3+
4+
from .utils import Dsn
5+
from .transport import Transport
6+
from .consts import DEFAULT_OPTIONS, SDK_INFO
7+
8+
9+
NO_DSN = object()
10+
11+
12+
class Client(object):
13+
14+
def __init__(self, dsn=None, *args, **kwargs):
15+
if dsn is NO_DSN:
16+
dsn = None
17+
else:
18+
if dsn is None:
19+
dsn = os.environ.get('SENTRY_DSN')
20+
if not dsn:
21+
dsn = None
22+
else:
23+
dsn = Dsn(dsn)
24+
options = dict(DEFAULT_OPTIONS)
25+
options.update(*args, **kwargs)
26+
self.options = options
27+
if dsn is None:
28+
self._transport = None
29+
else:
30+
self._transport = Transport(dsn)
31+
self._transport.start()
32+
33+
@property
34+
def dsn(self):
35+
"""The DSN that created this event."""
36+
if self._transport is not None:
37+
return self._transport.dsn
38+
39+
@classmethod
40+
def disabled(cls):
41+
"""Creates a guarnateed to be disabled client."""
42+
return cls(NO_DSN)
43+
44+
def _prepare_event(self, event, scope):
45+
if event.get('event_id') is None:
46+
event['event_id'] = uuid.uuid4().hex
47+
48+
if scope is not None:
49+
scope.apply_to_event(event)
50+
51+
for key in 'release', 'environment', 'server_name':
52+
if event.get(key) is None:
53+
event[key] = self.options[key]
54+
if event.get('sdk') is None:
55+
event['sdk'] = SDK_INFO
56+
57+
if event.get('platform') is None:
58+
event['platform'] = 'python'
59+
60+
def capture_event(self, event, scope=None):
61+
"""Captures an event."""
62+
if self._transport is None:
63+
return
64+
self._prepare_event(event, scope)
65+
self._transport.capture_event(event)
66+
67+
def drain_events(self, timeout=None):
68+
if timeout is None:
69+
timeout = self.options['drain_timeout']
70+
if self._transport is not None:
71+
self._transport.drain_events(timeout)
72+
73+
def close(self):
74+
self.drain_events()
75+
if self._transport is not None:
76+
self._transport.close()

sentry_sdk/consts.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import socket
2+
3+
VERSION = '0.1'
4+
DEFAULT_SERVER_NAME = socket.gethostname() if hasattr(socket, 'gethostname') else None
5+
DEFAULT_OPTIONS = {
6+
'with_locals': True,
7+
'max_breadcrumbs': 100,
8+
'release': None,
9+
'environment': None,
10+
'server_name': DEFAULT_SERVER_NAME,
11+
'drain_timeout': 2.0,
12+
}
13+
14+
SDK_INFO = {
15+
'name': 'sentry-python',
16+
'version': VERSION,
17+
}

sentry_sdk/hub.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import sys
2+
import copy
3+
import linecache
4+
from threading import local
5+
from contextlib import contextmanager
6+
7+
from ._compat import with_metaclass
8+
from .scope import Scope
9+
from .utils import exceptions_from_error_tuple, create_event, \
10+
skip_internal_frames
11+
12+
13+
_local = local()
14+
15+
16+
class HubMeta(type):
17+
18+
@property
19+
def current(self):
20+
try:
21+
rv = _local.hub
22+
except AttributeError:
23+
_local.hub = rv = Hub(GLOBAL_HUB)
24+
return rv
25+
26+
@property
27+
def main(self):
28+
return GLOBAL_HUB
29+
30+
31+
class _HubManager(object):
32+
33+
def __init__(self, hub):
34+
self._old = Hub.current
35+
_local.hub = hub
36+
37+
def __exit__(self, exc_type, exc_value, tb):
38+
_local.hub = self._old
39+
40+
41+
class _ScopeManager(object):
42+
43+
def __init__(self, hub):
44+
self._hub = hub
45+
46+
def __exit__(self, exc_type, exc_value, tb):
47+
self._hub.stack.pop()
48+
49+
50+
class Hub(with_metaclass(HubMeta)):
51+
52+
def __init__(self, client_or_hub=None, scope=None):
53+
if isinstance(client_or_hub, Hub):
54+
hub = client_or_hub
55+
client, other_scope = hub._stack[-1]
56+
if scope is None:
57+
hub._flush_event_processors()
58+
scope = copy.copy(other_scope)
59+
else:
60+
client = client_or_hub
61+
if scope is None:
62+
scope = Scope()
63+
self._stack = [(client, scope)]
64+
self._pending_processors = []
65+
66+
def __enter__(self):
67+
return _HubManager(self)
68+
69+
def run(self, callback):
70+
"""Runs a callback in the context of the hub. Alternatively the
71+
with statement can be used on the hub directly.
72+
"""
73+
with self:
74+
callback()
75+
76+
@property
77+
def client(self):
78+
"""Returns the current client on the hub."""
79+
return self._stack[-1][0]
80+
81+
def bind_client(self, new):
82+
"""Binds a new client to the hub."""
83+
top = self._stack[-1]
84+
self._stack[-1] = (new, top[1])
85+
86+
def capture_event(self, event):
87+
"""Captures an event."""
88+
self._flush_event_processors()
89+
client, scope = self._stack[-1]
90+
if client is not None:
91+
client.capture_event(event, scope)
92+
return event.get('event_id')
93+
94+
def capture_message(self, message, level=None):
95+
"""Captures a message."""
96+
if self.client is None:
97+
return
98+
if level is None:
99+
level = 'info'
100+
event = create_event()
101+
event['message'] = message
102+
if level is not None:
103+
event['level'] = level
104+
return self.capture_event(event)
105+
106+
def capture_exception(self, error=None):
107+
"""Captures an exception."""
108+
client = self.client
109+
if client is None:
110+
return
111+
if error is None:
112+
exc_type, exc_value, tb = sys.exc_info()
113+
else:
114+
tb = getattr(error, '__traceback__', None)
115+
if tb is not None:
116+
exc_type = type(error)
117+
exc_value = error
118+
else:
119+
exc_type, exc_value, tb = sys.exc_info()
120+
tb = skip_internal_frames(tb)
121+
if exc_value is not error:
122+
tb = None
123+
exc_value = error
124+
exc_type = type(error)
125+
126+
event = create_event()
127+
event['exception'] = {
128+
'values': exceptions_from_error_tuple(
129+
exc_type, exc_value, tb, client.options['with_locals'])
130+
}
131+
return self.capture_event(event)
132+
133+
def add_breadcrumb(self, crumb):
134+
"""Adds a breadcrumb."""
135+
client, scope = self._stack[-1]
136+
if client is None:
137+
return
138+
if callable(crumb):
139+
crumb = crumb()
140+
if crumb is not None:
141+
scope.breadcrumbs.append(crumb)
142+
while len(scope.breadcrumbs) >= client.options['max_breadcrumbs']:
143+
scope.breadcrumbs.popleft()
144+
145+
def add_event_processor(self, factory):
146+
"""Registers a new event processor with the top scope."""
147+
self._pending_processors.append(factory)
148+
149+
def push_scope(self):
150+
"""Pushes a new layer on the scope stack."""
151+
client, scope = self._stack[-1]
152+
self._stack.append((client, copy.copy(scope)))
153+
return _ScopeManager(self)
154+
155+
@contextmanager
156+
def configure_scope(self):
157+
"""Reconfigures the scope."""
158+
yield self._stack[-1][1]
159+
160+
def _flush_event_processors(self):
161+
rv = self._pending_processors
162+
self._pending_processors = []
163+
top = self._stack[-1][1]
164+
for factory in rv:
165+
top._event_processors.append(factory())
166+
167+
168+
GLOBAL_HUB = Hub()

0 commit comments

Comments
 (0)