From 828d00c80984e7fc0ae3e3af58173d32526768a5 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 20 Jul 2021 16:03:01 +0200 Subject: [PATCH] misc: fix override of trace emails recipients for Django 2 (#55687) --- debian/debian_config.py | 2 + passerelle/base/management/commands/cron.py | 3 +- passerelle/base/models.py | 26 ++++++--- passerelle/log.py | 59 +++++++++++++++++++++ passerelle/settings.py | 5 ++ passerelle/views.py | 3 +- tests/test_misc.py | 2 +- 7 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 passerelle/log.py diff --git a/debian/debian_config.py b/debian/debian_config.py index 1c377432..3d7850c6 100644 --- a/debian/debian_config.py +++ b/debian/debian_config.py @@ -36,6 +36,8 @@ LOGGING['loggers']['paramiko.transport'] = { 'propagate': True, } +ADMIN_EMAIL_HANDLER_CLASS = 'passerelle.log.AdminEmailHandler' + exec(open('/etc/%s/settings.py' % PROJECT_NAME).read()) # run additional settings snippets diff --git a/passerelle/base/management/commands/cron.py b/passerelle/base/management/commands/cron.py index f1094cd9..efdc3375 100644 --- a/passerelle/base/management/commands/cron.py +++ b/passerelle/base/management/commands/cron.py @@ -57,7 +57,8 @@ class Command(BaseCommand): if options.get('slug') and connector.slug != options.get('slug'): continue try: - getattr(connector, frequency)() + with connector.logger.mail_admin_filter(): + getattr(connector, frequency)() except Exception as e: connector.logger.exception( 'connector "%s.%s" error running %s job' diff --git a/passerelle/base/models.py b/passerelle/base/models.py index 94cc45b0..fb6600d5 100644 --- a/passerelle/base/models.py +++ b/passerelle/base/models.py @@ -24,7 +24,6 @@ from django.db import connection, models, transaction from django.db.models import Q from django.forms.models import modelform_factory from django.forms.widgets import ClearableFileInput -from django.test import override_settings from django.urls import reverse from django.utils import six, timezone from django.utils.encoding import force_text @@ -37,6 +36,7 @@ from model_utils.managers import InheritanceManager as ModelUtilsInheritanceMana import passerelle from passerelle.forms import GenericConnectorForm +from passerelle.log import AddResourceFilter from passerelle.utils.api import endpoint from passerelle.utils.jsonresponse import APIError from passerelle.utils.sftp import SFTP, SFTPField @@ -820,7 +820,8 @@ class Job(models.Model): self.status = 'running' self.save() try: - getattr(self.resource, self.method_name)(**self.parameters) + with self.resource.logger.mail_admin_filter(): + getattr(self.resource, self.method_name)(**self.parameters) except SkipJob as e: self.status = 'registered' self.set_after_timestamp(e.after_timestamp) @@ -922,6 +923,20 @@ class ProxyLogger(object): for handler in mail_admin_handlers: handler.setLevel(handler.previous_level) + @contextmanager + def mail_admin_filter(self): + mail_admin_handlers = [ + handler for handler in self._logger.root.handlers if isinstance(handler, AdminEmailHandler) + ] + filter_ = AddResourceFilter(resource=self.connector) + for handler in mail_admin_handlers: + handler.addFilter(filter_) + try: + yield + finally: + for handler in mail_admin_handlers: + handler.removeFilter(filter_) + def _log(self, levelname, message, *args, **kwargs): force = kwargs.pop('force', False) if self.connector.down() and not force: @@ -986,12 +1001,7 @@ class ProxyLogger(object): ResourceLog.objects.create(**attr) - admins = settings.ADMINS - logging_parameters = self.connector.logging_parameters - if logging_parameters.trace_emails: - admins = [('', x) for x in logging_parameters.trace_emails.splitlines()] - with override_settings(ADMINS=admins): - getattr(self._logger, levelname.lower())(message, *args, **kwargs) + getattr(self._logger, levelname.lower())(message, *args, **kwargs) def exception(self, message, *args, **kwargs): kwargs['exc_info'] = 1 diff --git a/passerelle/log.py b/passerelle/log.py new file mode 100644 index 00000000..c8937432 --- /dev/null +++ b/passerelle/log.py @@ -0,0 +1,59 @@ +# Copyright (C) 2021 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import logging + +import django.utils.log +from django.conf import settings +from django.core.mail.message import EmailMultiAlternatives + + +class AddResourceFilter(logging.Filter): + def __init__(self, *args, resource=None, **kwargs): + self.resource = resource + super().__init__(*args, **kwargs) + + def filter(self, record): + record.resource = self.resource + return True + + +class AdminEmailHandler(django.utils.log.AdminEmailHandler): + def __init__(self, *args, **kwargs): + self.emails = [] + super().__init__(*args, **kwargs) + + def emit(self, record): + if hasattr(record, 'resource'): + logging_parameters = record.resource.logging_parameters + self.emails = logging_parameters.trace_emails.splitlines() + if not self.emails and settings.ADMINS: + self.emails = [a[1] for a in settings.ADMINS] + return super().emit(record) + + def send_mail(self, subject, message, *args, **kwargs): + if not self.emails: + return + + mail = EmailMultiAlternatives( + '%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), + message, + settings.SERVER_EMAIL, + self.emails, + connection=self.connection(), + ) + if 'html_message' in kwargs: + mail.attach_alternative(kwargs['html_message'], 'text/html') + + mail.send(fail_silently=kwargs.get('fail_silently', False)) diff --git a/passerelle/settings.py b/passerelle/settings.py index 3750c8f0..78011980 100644 --- a/passerelle/settings.py +++ b/passerelle/settings.py @@ -244,6 +244,11 @@ LOGGING = { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, + 'mail_admins': { + 'level': 'ERROR', + 'class': 'passerelle.log.AdminEmailHandler', + 'include_html': True, + }, }, 'loggers': { 'django.request': { diff --git a/passerelle/views.py b/passerelle/views.py index 010cac41..f628f28b 100644 --- a/passerelle/views.py +++ b/passerelle/views.py @@ -443,7 +443,8 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View): if kwargs.get('endpoint') == 'up' and hasattr(self.connector.check_status, 'not_implemented'): # hide automatic up endpoint if check_status method is not implemented raise Http404() - return super(GenericEndpointView, self).dispatch(request, *args, **kwargs) + with self.connector.logger.mail_admin_filter(): + return super(GenericEndpointView, self).dispatch(request, *args, **kwargs) def _allowed_methods(self): return [x.upper() for x in self.endpoint.endpoint_info.methods] diff --git a/tests/test_misc.py b/tests/test_misc.py index a4dcd635..83983ebb 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -91,7 +91,7 @@ def test_log_cleaning(app, db, admin_user, settings): def email_handler(): import logging - from django.utils.log import AdminEmailHandler + from passerelle.log import AdminEmailHandler root = logging.getLogger() handler = AdminEmailHandler(include_html=True) -- 2.20.1