prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Parse the email template contexts for identifiers and log them along with the mail.queued event.
  We currently log types of mail sent but do not add any context along with that, so it's easy to see that someone got an email about an event, but since the `event_id` is not passed along, the log event is somewhat useless.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/sentry/plugins/bases/notify.py`
  Content:
  ```
  1 """
  2 sentry.plugins.bases.notify
  3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4 
  5 :copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details.
  6 :license: BSD, see LICENSE for more details.
  7 """
  8 from __future__ import absolute_import, print_function
  9 
  10 import logging
  11 import six
  12 
  13 from django import forms
  14 
  15 from sentry.app import (
  16     digests,
  17     ratelimiter,
  18 )
  19 from sentry.digests import get_option_key as get_digest_option_key
  20 from sentry.digests.notifications import (
  21     event_to_record,
  22     unsplit_key,
  23 )
  24 from sentry.plugins import Notification, Plugin
  25 from sentry.models import (
  26     ProjectOption,
  27     UserOption,
  28 )
  29 from sentry.tasks.digests import deliver_digest
  30 
  31 
  32 class NotificationConfigurationForm(forms.Form):
  33     pass
  34 
  35 
  36 class BaseNotificationUserOptionsForm(forms.Form):
  37     def __init__(self, plugin, user, *args, **kwargs):
  38         self.plugin = plugin
  39         self.user = user
  40         super(BaseNotificationUserOptionsForm, self).__init__(*args, **kwargs)
  41 
  42     def get_title(self):
  43         return self.plugin.get_conf_title()
  44 
  45     def get_description(self):
  46         return ""
  47 
  48     def save(self):
  49         raise NotImplementedError
  50 
  51 
  52 class NotificationPlugin(Plugin):
  53     description = ('Notify project members when a new event is seen for the first time, or when an '
  54                    'already resolved event has changed back to unresolved.')
  55     # site_conf_form = NotificationConfigurationForm
  56     project_conf_form = NotificationConfigurationForm
  57 
  58     def get_plugin_type(self):
  59         return 'notification'
  60 
  61     def notify(self, notification):
  62         event = notification.event
  63         return self.notify_users(event.group, event)
  64 
  65     def rule_notify(self, event, futures):
  66         rules = []
  67         for future in futures:
  68             rules.append(future.rule)
  69             if not future.kwargs:
  70                 continue
  71             raise NotImplementedError('The default behavior for notification de-duplication does not support args')
  72 
  73         project = event.group.project
  74         if hasattr(self, 'notify_digest') and digests.enabled(project):
  75             get_digest_option = lambda key: ProjectOption.objects.get_value(
  76                 project,
  77                 get_digest_option_key(self.get_conf_key(), key),
  78             )
  79             digest_key = unsplit_key(self, event.group.project)
  80             immediate_delivery = digests.add(
  81                 digest_key,
  82                 event_to_record(event, rules),
  83                 increment_delay=get_digest_option('increment_delay'),
  84                 maximum_delay=get_digest_option('maximum_delay'),
  85             )
  86             if immediate_delivery:
  87                 deliver_digest.delay(digest_key)
  88 
  89         else:
  90             notification = Notification(
  91                 event=event,
  92                 rules=rules,
  93             )
  94             self.notify(notification)
  95             self.logger.info('notification.dispatched', extra={
  96                 'event_id': event.id,
  97                 'plugin': self.slug,
  98                 'rule_id': rules[0].id if rules else None,
  99             })
  100 
  101     def notify_users(self, group, event, fail_silently=False):
  102         raise NotImplementedError
  103 
  104     def notify_about_activity(self, activity):
  105         pass
  106 
  107     def get_sendable_users(self, project):
  108         """
  109         Return a collection of user IDs that are eligible to receive
  110         notifications for the provided project.
  111         """
  112         conf_key = self.get_conf_key()
  113 
  114         alert_settings = dict(
  115             (o.user_id, int(o.value))
  116             for o in UserOption.objects.filter(
  117                 project=project,
  118                 key='%s:alert' % conf_key,
  119             )
  120         )
  121 
  122         disabled = set(u for u, v in six.iteritems(alert_settings) if v == 0)
  123 
  124         member_set = set(project.member_set.exclude(
  125             user__in=disabled,
  126         ).values_list('user', flat=True))
  127 
  128         # determine members default settings
  129         members_to_check = set(u for u in member_set if u not in alert_settings)
  130         if members_to_check:
  131             disabled = set(UserOption.objects.filter(
  132                 key='subscribe_by_default',
  133                 value='0',
  134                 user__in=members_to_check,
  135             ).values_list('user', flat=True))
  136             member_set = [x for x in member_set if x not in disabled]
  137 
  138         return member_set
  139 
  140     def __is_rate_limited(self, group, event):
  141         return ratelimiter.is_limited(
  142             project=group.project,
  143             key=self.get_conf_key(),
  144             limit=10,
  145         )
  146 
  147     def is_configured(self, project):
  148         raise NotImplementedError
  149 
  150     def should_notify(self, group, event):
  151         project = event.project
  152         if not self.is_configured(project=project):
  153             return False
  154 
  155         if group.is_muted():
  156             return False
  157 
  158         # If the plugin doesn't support digests or they are not enabled,
  159         # perform rate limit checks to support backwards compatibility with
  160         # older plugins.
  161         if not (hasattr(self, 'notify_digest') and digests.enabled(project)) and self.__is_rate_limited(group, event):
  162             logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))
  163             logger.info('notification.rate_limited', extra={'project_id': project.id})
  164             return False
  165 
  166         return True
  167 
  168     def test_configuration(self, project):
  169         from sentry.utils.samples import create_sample_event
  170         event = create_sample_event(project, platform='python')
  171         notification = Notification(event=event)
  172         return self.notify(notification)
  173 
  174     def get_notification_doc_html(self, **kwargs):
  175         return ""
  176 
  177 
  178 # Backwards-compatibility
  179 NotifyConfigurationForm = NotificationConfigurationForm
  180 NotifyPlugin = NotificationPlugin
  181
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/sentry/plugins/bases/notify.py b/src/sentry/plugins/bases/notify.py
  --- a/src/sentry/plugins/bases/notify.py
  +++ b/src/sentry/plugins/bases/notify.py
  @@ -64,19 +64,28 @@
   
       def rule_notify(self, event, futures):
           rules = []
  +        extra = {
  +            'event_id': event.id,
  +            'group_id': event.group_id,
  +            'plugin': self.slug,
  +        }
  +        log_event = 'dispatched'
           for future in futures:
               rules.append(future.rule)
  +            extra['rule_id'] = future.rule.id
               if not future.kwargs:
                   continue
               raise NotImplementedError('The default behavior for notification de-duplication does not support args')
   
           project = event.group.project
  +        extra['project_id'] = project.id
           if hasattr(self, 'notify_digest') and digests.enabled(project):
               get_digest_option = lambda key: ProjectOption.objects.get_value(
                   project,
                   get_digest_option_key(self.get_conf_key(), key),
               )
               digest_key = unsplit_key(self, event.group.project)
  +            extra['digest_key'] = digest_key
               immediate_delivery = digests.add(
                   digest_key,
                   event_to_record(event, rules),
  @@ -85,6 +94,8 @@
               )
               if immediate_delivery:
                   deliver_digest.delay(digest_key)
  +            else:
  +                log_event = 'digested'
   
           else:
               notification = Notification(
  @@ -92,11 +103,8 @@
                   rules=rules,
               )
               self.notify(notification)
  -            self.logger.info('notification.dispatched', extra={
  -                'event_id': event.id,
  -                'plugin': self.slug,
  -                'rule_id': rules[0].id if rules else None,
  -            })
  +
  +        self.logger.info('notification.%s' % log_event, extra=extra)
   
       def notify_users(self, group, event, fail_silently=False):
           raise NotImplementedError
  
verification_info: {"golden_diff": "diff --git a/src/sentry/plugins/bases/notify.py b/src/sentry/plugins/bases/notify.py\n--- a/src/sentry/plugins/bases/notify.py\n+++ b/src/sentry/plugins/bases/notify.py\n@@ -64,19 +64,28 @@\n \n     def rule_notify(self, event, futures):\n         rules = []\n+        extra = {\n+            'event_id': event.id,\n+            'group_id': event.group_id,\n+            'plugin': self.slug,\n+        }\n+        log_event = 'dispatched'\n         for future in futures:\n             rules.append(future.rule)\n+            extra['rule_id'] = future.rule.id\n             if not future.kwargs:\n                 continue\n             raise NotImplementedError('The default behavior for notification de-duplication does not support args')\n \n         project = event.group.project\n+        extra['project_id'] = project.id\n         if hasattr(self, 'notify_digest') and digests.enabled(project):\n             get_digest_option = lambda key: ProjectOption.objects.get_value(\n                 project,\n                 get_digest_option_key(self.get_conf_key(), key),\n             )\n             digest_key = unsplit_key(self, event.group.project)\n+            extra['digest_key'] = digest_key\n             immediate_delivery = digests.add(\n                 digest_key,\n                 event_to_record(event, rules),\n@@ -85,6 +94,8 @@\n             )\n             if immediate_delivery:\n                 deliver_digest.delay(digest_key)\n+            else:\n+                log_event = 'digested'\n \n         else:\n             notification = Notification(\n@@ -92,11 +103,8 @@\n                 rules=rules,\n             )\n             self.notify(notification)\n-            self.logger.info('notification.dispatched', extra={\n-                'event_id': event.id,\n-                'plugin': self.slug,\n-                'rule_id': rules[0].id if rules else None,\n-            })\n+\n+        self.logger.info('notification.%s' % log_event, extra=extra)\n \n     def notify_users(self, group, event, fail_silently=False):\n         raise NotImplementedError\n", "issue": "Parse the email template contexts for identifiers and log them along with the mail.queued event.\nWe currently log types of mail sent but do not add any context along with that, so it's easy to see that someone got an email about an event, but since the `event_id` is not passed along, the log event is somewhat useless.\n\n", "before_files": [{"content": "\"\"\"\nsentry.plugins.bases.notify\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\nfrom __future__ import absolute_import, print_function\n\nimport logging\nimport six\n\nfrom django import forms\n\nfrom sentry.app import (\n    digests,\n    ratelimiter,\n)\nfrom sentry.digests import get_option_key as get_digest_option_key\nfrom sentry.digests.notifications import (\n    event_to_record,\n    unsplit_key,\n)\nfrom sentry.plugins import Notification, Plugin\nfrom sentry.models import (\n    ProjectOption,\n    UserOption,\n)\nfrom sentry.tasks.digests import deliver_digest\n\n\nclass NotificationConfigurationForm(forms.Form):\n    pass\n\n\nclass BaseNotificationUserOptionsForm(forms.Form):\n    def __init__(self, plugin, user, *args, **kwargs):\n        self.plugin = plugin\n        self.user = user\n        super(BaseNotificationUserOptionsForm, self).__init__(*args, **kwargs)\n\n    def get_title(self):\n        return self.plugin.get_conf_title()\n\n    def get_description(self):\n        return \"\"\n\n    def save(self):\n        raise NotImplementedError\n\n\nclass NotificationPlugin(Plugin):\n    description = ('Notify project members when a new event is seen for the first time, or when an '\n                   'already resolved event has changed back to unresolved.')\n    # site_conf_form = NotificationConfigurationForm\n    project_conf_form = NotificationConfigurationForm\n\n    def get_plugin_type(self):\n        return 'notification'\n\n    def notify(self, notification):\n        event = notification.event\n        return self.notify_users(event.group, event)\n\n    def rule_notify(self, event, futures):\n        rules = []\n        for future in futures:\n            rules.append(future.rule)\n            if not future.kwargs:\n                continue\n            raise NotImplementedError('The default behavior for notification de-duplication does not support args')\n\n        project = event.group.project\n        if hasattr(self, 'notify_digest') and digests.enabled(project):\n            get_digest_option = lambda key: ProjectOption.objects.get_value(\n                project,\n                get_digest_option_key(self.get_conf_key(), key),\n            )\n            digest_key = unsplit_key(self, event.group.project)\n            immediate_delivery = digests.add(\n                digest_key,\n                event_to_record(event, rules),\n                increment_delay=get_digest_option('increment_delay'),\n                maximum_delay=get_digest_option('maximum_delay'),\n            )\n            if immediate_delivery:\n                deliver_digest.delay(digest_key)\n\n        else:\n            notification = Notification(\n                event=event,\n                rules=rules,\n            )\n            self.notify(notification)\n            self.logger.info('notification.dispatched', extra={\n                'event_id': event.id,\n                'plugin': self.slug,\n                'rule_id': rules[0].id if rules else None,\n            })\n\n    def notify_users(self, group, event, fail_silently=False):\n        raise NotImplementedError\n\n    def notify_about_activity(self, activity):\n        pass\n\n    def get_sendable_users(self, project):\n        \"\"\"\n        Return a collection of user IDs that are eligible to receive\n        notifications for the provided project.\n        \"\"\"\n        conf_key = self.get_conf_key()\n\n        alert_settings = dict(\n            (o.user_id, int(o.value))\n            for o in UserOption.objects.filter(\n                project=project,\n                key='%s:alert' % conf_key,\n            )\n        )\n\n        disabled = set(u for u, v in six.iteritems(alert_settings) if v == 0)\n\n        member_set = set(project.member_set.exclude(\n            user__in=disabled,\n        ).values_list('user', flat=True))\n\n        # determine members default settings\n        members_to_check = set(u for u in member_set if u not in alert_settings)\n        if members_to_check:\n            disabled = set(UserOption.objects.filter(\n                key='subscribe_by_default',\n                value='0',\n                user__in=members_to_check,\n            ).values_list('user', flat=True))\n            member_set = [x for x in member_set if x not in disabled]\n\n        return member_set\n\n    def __is_rate_limited(self, group, event):\n        return ratelimiter.is_limited(\n            project=group.project,\n            key=self.get_conf_key(),\n            limit=10,\n        )\n\n    def is_configured(self, project):\n        raise NotImplementedError\n\n    def should_notify(self, group, event):\n        project = event.project\n        if not self.is_configured(project=project):\n            return False\n\n        if group.is_muted():\n            return False\n\n        # If the plugin doesn't support digests or they are not enabled,\n        # perform rate limit checks to support backwards compatibility with\n        # older plugins.\n        if not (hasattr(self, 'notify_digest') and digests.enabled(project)) and self.__is_rate_limited(group, event):\n            logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))\n            logger.info('notification.rate_limited', extra={'project_id': project.id})\n            return False\n\n        return True\n\n    def test_configuration(self, project):\n        from sentry.utils.samples import create_sample_event\n        event = create_sample_event(project, platform='python')\n        notification = Notification(event=event)\n        return self.notify(notification)\n\n    def get_notification_doc_html(self, **kwargs):\n        return \"\"\n\n\n# Backwards-compatibility\nNotifyConfigurationForm = NotificationConfigurationForm\nNotifyPlugin = NotificationPlugin\n", "path": "src/sentry/plugins/bases/notify.py"}], "after_files": [{"content": "\"\"\"\nsentry.plugins.bases.notify\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\nfrom __future__ import absolute_import, print_function\n\nimport logging\nimport six\n\nfrom django import forms\n\nfrom sentry.app import (\n    digests,\n    ratelimiter,\n)\nfrom sentry.digests import get_option_key as get_digest_option_key\nfrom sentry.digests.notifications import (\n    event_to_record,\n    unsplit_key,\n)\nfrom sentry.plugins import Notification, Plugin\nfrom sentry.models import (\n    ProjectOption,\n    UserOption,\n)\nfrom sentry.tasks.digests import deliver_digest\n\n\nclass NotificationConfigurationForm(forms.Form):\n    pass\n\n\nclass BaseNotificationUserOptionsForm(forms.Form):\n    def __init__(self, plugin, user, *args, **kwargs):\n        self.plugin = plugin\n        self.user = user\n        super(BaseNotificationUserOptionsForm, self).__init__(*args, **kwargs)\n\n    def get_title(self):\n        return self.plugin.get_conf_title()\n\n    def get_description(self):\n        return \"\"\n\n    def save(self):\n        raise NotImplementedError\n\n\nclass NotificationPlugin(Plugin):\n    description = ('Notify project members when a new event is seen for the first time, or when an '\n                   'already resolved event has changed back to unresolved.')\n    # site_conf_form = NotificationConfigurationForm\n    project_conf_form = NotificationConfigurationForm\n\n    def get_plugin_type(self):\n        return 'notification'\n\n    def notify(self, notification):\n        event = notification.event\n        return self.notify_users(event.group, event)\n\n    def rule_notify(self, event, futures):\n        rules = []\n        extra = {\n            'event_id': event.id,\n            'group_id': event.group_id,\n            'plugin': self.slug,\n        }\n        log_event = 'dispatched'\n        for future in futures:\n            rules.append(future.rule)\n            extra['rule_id'] = future.rule.id\n            if not future.kwargs:\n                continue\n            raise NotImplementedError('The default behavior for notification de-duplication does not support args')\n\n        project = event.group.project\n        extra['project_id'] = project.id\n        if hasattr(self, 'notify_digest') and digests.enabled(project):\n            get_digest_option = lambda key: ProjectOption.objects.get_value(\n                project,\n                get_digest_option_key(self.get_conf_key(), key),\n            )\n            digest_key = unsplit_key(self, event.group.project)\n            extra['digest_key'] = digest_key\n            immediate_delivery = digests.add(\n                digest_key,\n                event_to_record(event, rules),\n                increment_delay=get_digest_option('increment_delay'),\n                maximum_delay=get_digest_option('maximum_delay'),\n            )\n            if immediate_delivery:\n                deliver_digest.delay(digest_key)\n            else:\n                log_event = 'digested'\n\n        else:\n            notification = Notification(\n                event=event,\n                rules=rules,\n            )\n            self.notify(notification)\n\n        self.logger.info('notification.%s' % log_event, extra=extra)\n\n    def notify_users(self, group, event, fail_silently=False):\n        raise NotImplementedError\n\n    def notify_about_activity(self, activity):\n        pass\n\n    def get_sendable_users(self, project):\n        \"\"\"\n        Return a collection of user IDs that are eligible to receive\n        notifications for the provided project.\n        \"\"\"\n        conf_key = self.get_conf_key()\n\n        alert_settings = dict(\n            (o.user_id, int(o.value))\n            for o in UserOption.objects.filter(\n                project=project,\n                key='%s:alert' % conf_key,\n            )\n        )\n\n        disabled = set(u for u, v in six.iteritems(alert_settings) if v == 0)\n\n        member_set = set(project.member_set.exclude(\n            user__in=disabled,\n        ).values_list('user', flat=True))\n\n        # determine members default settings\n        members_to_check = set(u for u in member_set if u not in alert_settings)\n        if members_to_check:\n            disabled = set(UserOption.objects.filter(\n                key='subscribe_by_default',\n                value='0',\n                user__in=members_to_check,\n            ).values_list('user', flat=True))\n            member_set = [x for x in member_set if x not in disabled]\n\n        return member_set\n\n    def __is_rate_limited(self, group, event):\n        return ratelimiter.is_limited(\n            project=group.project,\n            key=self.get_conf_key(),\n            limit=10,\n        )\n\n    def is_configured(self, project):\n        raise NotImplementedError\n\n    def should_notify(self, group, event):\n        project = event.project\n        if not self.is_configured(project=project):\n            return False\n\n        if group.is_muted():\n            return False\n\n        # If the plugin doesn't support digests or they are not enabled,\n        # perform rate limit checks to support backwards compatibility with\n        # older plugins.\n        if not (hasattr(self, 'notify_digest') and digests.enabled(project)) and self.__is_rate_limited(group, event):\n            logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))\n            logger.info('notification.rate_limited', extra={'project_id': project.id})\n            return False\n\n        return True\n\n    def test_configuration(self, project):\n        from sentry.utils.samples import create_sample_event\n        event = create_sample_event(project, platform='python')\n        notification = Notification(event=event)\n        return self.notify(notification)\n\n    def get_notification_doc_html(self, **kwargs):\n        return \"\"\n\n\n# Backwards-compatibility\nNotifyConfigurationForm = NotificationConfigurationForm\nNotifyPlugin = NotificationPlugin\n", "path": "src/sentry/plugins/bases/notify.py"}]}