aboutsummaryrefslogtreecommitdiff
path: root/app/maillogger.py
blob: 10a046a4636a45f49ec3b7aaae2570ef914e8009 (plain)
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
109
import logging
from enum import Enum
from app.tasks.emails import sendEmailRaw

def _has_newline(line):
	"""Used by has_bad_header to check for \\r or \\n"""
	if line and ("\r" in line or "\n" in line):
		return True
	return False

def _is_bad_subject(subject):
	"""Copied from: flask_mail.py class Message def has_bad_headers"""
	if _has_newline(subject):
		for linenum, line in enumerate(subject.split("\r\n")):
			if not line:
				return True
			if linenum > 0 and line[0] not in "\t ":
				return True
			if _has_newline(line):
				return True
			if len(line.strip()) == 0:
				return True
	return False


class FlaskMailSubjectFormatter(logging.Formatter):
	def format(self, record):
		record.message = record.getMessage()
		if self.usesTime():
			record.asctime = self.formatTime(record, self.datefmt)
		s = self.formatMessage(record)
		return s

class FlaskMailTextFormatter(logging.Formatter):
	pass

# TODO: hier nog niet tevreden over (vooral logger.error(..., exc_info, stack_info))
class FlaskMailHTMLFormatter(logging.Formatter):
	pre_template = "<h1>%s</h1><pre>%s</pre>"
	def formatException(self, exc_info):
		formatted_exception = logging.Handler.formatException(self, exc_info)
		return FlaskMailHTMLFormatter.pre_template % ("Exception information", formatted_exception)
	def formatStack(self, stack_info):
		return FlaskMailHTMLFormatter.pre_template % ("<h1>Stack information</h1><pre><code>%s</code></pre>", stack_info)


# see: https://github.com/python/cpython/blob/3.6/Lib/logging/__init__.py (class Handler)

class FlaskMailHandler(logging.Handler):
	def __init__(self, mailer, subject_template, level=logging.NOTSET):
		logging.Handler.__init__(self, level)
		self.mailer = mailer
		self.send_to = mailer.app.config["MAIL_UTILS_ERROR_SEND_TO"]
		self.subject_template = subject_template
		self.html_formatter = None

	def setFormatter(self, text_fmt, html_fmt=None):
		"""
		Set the formatters for this handler. Provide at least one formatter.
		When no text_fmt is provided, no text-part is created for the email body.
		"""
		assert (text_fmt, html_fmt) != (None, None), "At least one formatter should be provided"
		if type(text_fmt)==str:
			text_fmt = FlaskMailTextFormatter(text_fmt)
		self.formatter = text_fmt
		if type(html_fmt)==str:
			html_fmt = FlaskMailHTMLFormatter(html_fmt)
		self.html_formatter = html_fmt

	def getSubject(self, record):
		fmt = FlaskMailSubjectFormatter(self.subject_template)
		subject = fmt.format(record)
		#Since templates can cause header problems, and we rather have a incomplete email then an error, we fix this
		if _is_bad_subject(subject):
			subject="FlaskMailHandler log-entry from %s [original subject is replaced, because it would result in a bad header]" % self.mailer.app.name
		return subject

	def emit(self, record):
		text = self.format(record)				if self.formatter	  else None
		html = self.html_formatter.format(record) if self.html_formatter else None
		sendEmailRaw.delay(self.send_to, self.getSubject(record), text, html)


def register_mail_error_handler(app, mailer):
	subject_template = "ContentDB crashed (%(module)s > %(funcName)s)"
	text_template = """
Message type: %(levelname)s
Location:	 %(pathname)s:%(lineno)d
Module:	   %(module)s
Function:	 %(funcName)s
Time:		 %(asctime)s
Message:
%(message)s"""
	html_template = """
<style>th { text-align: right}</style><table>
<tr><th>Message type:</th><td>%(levelname)s</td></tr>
<tr>	<th>Location:</th><td>%(pathname)s:%(lineno)d</td></tr>
<tr>	  <th>Module:</th><td>%(module)s</td></tr>
<tr>	<th>Function:</th><td>%(funcName)s</td></tr>
<tr>		<th>Time:</th><td>%(asctime)s</td></tr>
</table>
<h2>Message</h2>
<pre><code>%(message)s</code></pre>"""

	import logging
	mail_handler = FlaskMailHandler(mailer, subject_template)
	mail_handler.setLevel(logging.ERROR)
	mail_handler.setFormatter(text_template, html_template)
	app.logger.addHandler(mail_handler)