Skip to content

Commit 83cf077

Browse files
ted kaemmingmattrobenolt
authored andcommitted
Merge pull request #2569 from getsentry/notification-logging
Add logging to digest building and email delivery.
1 parent 0f223c6 commit 83cf077

File tree

4 files changed

+98
-31
lines changed

4 files changed

+98
-31
lines changed

src/sentry/digests/notifications.py

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,57 @@ def attach_state(project, groups, rules, event_counts, user_counts):
9292
}
9393

9494

95+
class Pipeline(object):
96+
def __init__(self):
97+
self.operations = []
98+
99+
def __call__(self, sequence):
100+
return reduce(lambda x, operation: operation(x), self.operations, sequence)
101+
102+
def apply(self, function):
103+
def operation(sequence):
104+
result = function(sequence)
105+
logger.debug('%r applied to %s items.', function, len(sequence))
106+
return result
107+
self.operations.append(operation)
108+
return self
109+
110+
def filter(self, function):
111+
def operation(sequence):
112+
result = filter(function, sequence)
113+
logger.debug('%r filtered %s items to %s.', function, len(sequence), len(result))
114+
return result
115+
self.operations.append(operation)
116+
return self
117+
118+
def map(self, function):
119+
def operation(sequence):
120+
result = map(function, sequence)
121+
logger.debug('%r applied to %s items.', function, len(sequence))
122+
return result
123+
self.operations.append(operation)
124+
return self
125+
126+
def reduce(self, function, initializer):
127+
def operation(sequence):
128+
result = reduce(function, sequence, initializer(sequence))
129+
logger.debug('%r reduced %s items to %s.', function, len(sequence), len(result))
130+
return result
131+
self.operations.append(operation)
132+
return self
133+
134+
95135
def rewrite_record(record, project, groups, rules):
96136
event = record.value.event
137+
138+
# Reattach the group to the event.
97139
group = groups.get(event.group_id)
98-
if group is None:
140+
if group is not None:
141+
event.group = group
142+
else:
143+
logger.debug('%r could not be associated with a group.', record)
99144
return
100145

101-
event.group = group
102-
103146
return Record(
104147
record.key,
105148
Notification(
@@ -110,36 +153,38 @@ def rewrite_record(record, project, groups, rules):
110153
)
111154

112155

113-
def group_records(records):
114-
results = defaultdict(lambda: defaultdict(list))
115-
for record in records:
116-
group = record.value.event.group
117-
for rule in record.value.rules:
118-
results[rule][group].append(record)
156+
def group_records(groups, record):
157+
group = record.value.event.group
158+
rules = record.value.rules
159+
if not rules:
160+
logger.debug('%r has no associated rules, and will not be added to any groups.', record)
161+
162+
for rule in rules:
163+
groups[rule][group].append(record)
119164

120-
return results
165+
return groups
121166

122167

123-
def sort_groups(grouped):
124-
def sort_by_events(groups):
125-
return OrderedDict(
168+
def sort_group_contents(rules):
169+
for key, groups in rules.iteritems():
170+
rules[key] = OrderedDict(
126171
sorted(
127172
groups.items(),
128173
key=lambda (group, records): (group.event_count, group.user_count),
129174
reverse=True,
130-
),
175+
)
131176
)
177+
return rules
132178

133-
def sort_by_groups(rules):
134-
return OrderedDict(
135-
sorted(
136-
rules.items(),
137-
key=lambda (rule, groups): len(groups),
138-
reverse=True,
139-
),
140-
)
141179

142-
return sort_by_groups({rule: sort_by_events(groups) for rule, groups in grouped.iteritems()})
180+
def sort_rule_groups(rules):
181+
return OrderedDict(
182+
sorted(
183+
rules.items(),
184+
key=lambda (rule, groups): len(groups),
185+
reverse=True,
186+
),
187+
)
143188

144189

145190
def build_digest(project, records, state=None):
@@ -153,6 +198,16 @@ def build_digest(project, records, state=None):
153198
state = fetch_state(project, records)
154199

155200
state = attach_state(**state)
156-
records = filter(None, map(functools.partial(rewrite_record, **state), records))
157-
records = filter(lambda record: record.value.event.group.get_status() is GroupStatus.UNRESOLVED, records)
158-
return sort_groups(group_records(records))
201+
202+
def check_group_state(record):
203+
return record.value.event.group.get_status() is GroupStatus.UNRESOLVED
204+
205+
pipeline = Pipeline(). \
206+
map(functools.partial(rewrite_record, **state)). \
207+
filter(bool). \
208+
filter(check_group_state). \
209+
reduce(group_records, lambda sequence: defaultdict(lambda: defaultdict(list))). \
210+
apply(sort_group_contents). \
211+
apply(sort_rule_groups)
212+
213+
return pipeline(records)

src/sentry/plugins/sentry_mail/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def _build_message(self, subject, template=None, html_template=None, body=None,
5252
project=None, group=None, headers=None, context=None):
5353
send_to = self.get_send_to(project)
5454
if not send_to:
55+
logger.debug('Skipping message rendering, no users to send to.')
5556
return
5657

5758
subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix

src/sentry/utils/email.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
from __future__ import absolute_import
99

10+
import logging
1011
import os
1112
import time
1213
import toronado
@@ -25,6 +26,9 @@
2526
from sentry.utils import metrics
2627
from sentry.utils.safe import safe_execute
2728

29+
30+
logger = logging.getLogger(__name__)
31+
2832
SMTP_HOSTNAME = getattr(settings, 'SENTRY_SMTP_HOSTNAME', 'localhost')
2933
ENABLE_EMAIL_REPLIES = getattr(settings, 'SENTRY_ENABLE_EMAIL_REPLIES', False)
3034

@@ -240,7 +244,10 @@ def build(self, to, reply_to=None, cc=None, bcc=None):
240244
def get_built_messages(self, to=None, bcc=None):
241245
send_to = set(to or ())
242246
send_to.update(self._send_to)
243-
return [self.build(to=email, reply_to=send_to, bcc=bcc) for email in send_to]
247+
results = [self.build(to=email, reply_to=send_to, bcc=bcc) for email in send_to]
248+
if not results:
249+
logger.debug('Did not build any messages, no users to send to.')
250+
return results
244251

245252
def send(self, to=None, bcc=None, fail_silently=False):
246253
messages = self.get_built_messages(to, bcc=bcc)

tests/sentry/digests/test_notifications.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import absolute_import
22

3-
from collections import OrderedDict
3+
from collections import (
4+
OrderedDict,
5+
defaultdict,
6+
)
47

58
from exam import fixture
69

@@ -10,7 +13,8 @@
1013
event_to_record,
1114
rewrite_record,
1215
group_records,
13-
sort_groups,
16+
sort_group_contents,
17+
sort_rule_groups,
1418
)
1519
from sentry.testutils import TestCase
1620

@@ -78,7 +82,7 @@ def rule(self):
7882
def test_success(self):
7983
events = [self.create_event(group=self.group) for _ in xrange(3)]
8084
records = [Record(event.id, Notification(event, [self.rule]), event.datetime) for event in events]
81-
assert group_records(records) == {
85+
assert reduce(group_records, records, defaultdict(lambda: defaultdict(list))) == {
8286
self.rule: {
8387
self.group: records,
8488
},
@@ -109,7 +113,7 @@ def test_success(self):
109113
},
110114
}
111115

112-
assert sort_groups(grouped) == OrderedDict((
116+
assert sort_rule_groups(sort_group_contents(grouped)) == OrderedDict((
113117
(rules[1], OrderedDict((
114118
(groups[1], []),
115119
(groups[2], []),

0 commit comments

Comments
 (0)