improve logging and some fixes

This commit is contained in:
2021-03-09 19:09:21 +01:00
parent 16ca8cbbf0
commit 1349570b87
6 changed files with 63 additions and 73 deletions

View File

@@ -35,6 +35,7 @@ from collections import defaultdict
from email.header import Header from email.header import Header
from email.parser import BytesFeedParser from email.parser import BytesFeedParser
from email.policy import default as default_policy, SMTP from email.policy import default as default_policy, SMTP
from netaddr import IPNetwork, AddrFormatError
from pymodmilter.base import CustomLogger, BaseConfig, MilterMessage from pymodmilter.base import CustomLogger, BaseConfig, MilterMessage
from pymodmilter.base import replace_illegal_chars from pymodmilter.base import replace_illegal_chars
@@ -56,8 +57,7 @@ class ModifyMilterConfig(BaseConfig):
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
cfg_text = [f"{n+1}: {l}" for n, l in enumerate(cfg.splitlines())] cfg_text = [f"{n+1}: {l}" for n, l in enumerate(cfg.splitlines())]
msg = "\n".join(cfg_text) msg = "\n".join(cfg_text)
e.msg = f"{msg}\n{e.msg}" raise RuntimeError(f"{e}\n{msg}")
raise e
if "global" in cfg: if "global" in cfg:
assert isinstance(cfg["global"], dict), \ assert isinstance(cfg["global"], dict), \
@@ -90,15 +90,21 @@ class ModifyMilterConfig(BaseConfig):
[isinstance(addr, str) for addr in local_addrs]), \ [isinstance(addr, str) for addr in local_addrs]), \
"global: local_addrs: invalid value, " \ "global: local_addrs: invalid value, " \
"should be list of strings" "should be list of strings"
self["local_addrs"] = local_addrs
else: else:
self["local_addrs"] = [ local_addrs = [
"::1/128", "::1/128",
"127.0.0.0/8", "127.0.0.0/8",
"10.0.0.0/8", "10.0.0.0/8",
"172.16.0.0/12", "172.16.0.0/12",
"192.168.0.0/16"] "192.168.0.0/16"]
self["local_addrs"] = []
try:
for addr in local_addrs:
self["local_addrs"].append(IPNetwork(addr))
except AddrFormatError as e:
raise ValueError(f"{self['name']}: local_addrs: {e}")
self.logger.debug(f"socket={self['socket']}, " self.logger.debug(f"socket={self['socket']}, "
f"local_addrs={self['local_addrs']}, " f"local_addrs={self['local_addrs']}, "
f"pretend={self['pretend']}, " f"pretend={self['pretend']}, "

View File

@@ -160,7 +160,7 @@ def _has_content_before_body_tag(soup):
return False return False
def _patch_message_body(milter, action, text, html, logger): def _patch_message_body(milter, action, text_template, html_template, logger):
text_body, text_content = _get_body_content(milter.msg, "plain") text_body, text_content = _get_body_content(milter.msg, "plain")
html_body, html_content = _get_body_content(milter.msg, "html") html_body, html_content = _get_body_content(milter.msg, "html")
@@ -171,9 +171,9 @@ def _patch_message_body(milter, action, text, html, logger):
logger.info(f"{action} text disclaimer") logger.info(f"{action} text disclaimer")
if action == "prepend": if action == "prepend":
content = f"{text}{text_content}" content = f"{text_template}{text_content}"
else: else:
content = f"{text_content}{text}" content = f"{text_content}{text_template}"
text_body.set_content( text_body.set_content(
content.encode(), maintype="text", subtype="plain") content.encode(), maintype="text", subtype="plain")
@@ -192,9 +192,9 @@ def _patch_message_body(milter, action, text, html, logger):
body = soup body = soup
if action == "prepend": if action == "prepend":
body.insert(0, copy(html)) body.insert(0, copy(html_template))
else: else:
body.append(html) body.append(html_template)
html_body.set_content( html_body.set_content(
str(soup).encode(), maintype="text", subtype="html") str(soup).encode(), maintype="text", subtype="html")
@@ -239,26 +239,28 @@ def _inject_body(milter):
milter.msg.attach(attachment) milter.msg.attach(attachment)
def add_disclaimer(milter, text, html, action, policy, pretend=False, def add_disclaimer(milter, text_template, html_template, action, error_policy,
logger=logging.getLogger(__name__)): pretend=False, logger=logging.getLogger(__name__)):
"""Append or prepend a disclaimer to the mail body.""" """Append or prepend a disclaimer to the mail body."""
old_headers = milter.msg.items() old_headers = milter.msg.items()
try: try:
try: try:
_patch_message_body(milter, action, text, html, logger) _patch_message_body(
milter, action, text_template, html_template, logger)
except RuntimeError as e: except RuntimeError as e:
logger.info(f"{e}, inject empty plain and html body") logger.info(f"{e}, inject empty plain and html body")
_inject_body(milter) _inject_body(milter)
_patch_message_body(milter, action, text, html, logger) _patch_message_body(
milter, action, text_template, html_template, logger)
except Exception as e: except Exception as e:
logger.warning(e) logger.warning(e)
if policy == "ignore": if error_policy == "ignore":
logger.info( logger.info(
"unable to add disclaimer to message body, " "unable to add disclaimer to message body, "
"ignore error according to policy") "ignore error according to policy")
return return
elif policy == "reject": elif error_policy == "reject":
logger.info( logger.info(
"unable to add disclaimer to message body, " "unable to add disclaimer to message body, "
"reject message according to policy") "reject message according to policy")
@@ -268,7 +270,8 @@ def add_disclaimer(milter, text, html, action, policy, pretend=False,
logger.info("wrap original message in a new message envelope") logger.info("wrap original message in a new message envelope")
try: try:
_wrap_message(milter, logger) _wrap_message(milter, logger)
_patch_message_body(milter, action, text, html, logger) _patch_message_body(
milter, action, text_template, html_template, logger)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
raise Exception( raise Exception(
@@ -331,6 +334,9 @@ def store(milter, directory, pretend=False,
class ActionConfig(BaseConfig): class ActionConfig(BaseConfig):
def __init__(self, idx, rule_cfg, cfg, debug): def __init__(self, idx, rule_cfg, cfg, debug):
if "name" in cfg: if "name" in cfg:
assert isinstance(cfg["name"], str), \
f"{rule_cfg['name']}: Action #{idx}: name: invalid value, " \
f"should be string"
cfg["name"] = f"{rule_cfg['name']}: {cfg['name']}" cfg["name"] = f"{rule_cfg['name']}: {cfg['name']}"
else: else:
cfg["name"] = f"{rule_cfg['name']}: Action #{idx}" cfg["name"] = f"{rule_cfg['name']}: Action #{idx}"
@@ -353,7 +359,7 @@ class ActionConfig(BaseConfig):
assert "type" in cfg, \ assert "type" in cfg, \
f"{self['name']}: mandatory parameter 'type' not found" f"{self['name']}: mandatory parameter 'type' not found"
assert isinstance(cfg["type"], str), \ assert isinstance(cfg["type"], str), \
f"{self['name']}: invalid value, should be string" f"{self['name']}: type: invalid value, should be string"
self["type"] = cfg["type"] self["type"] = cfg["type"]
if self["type"] == "add_header": if self["type"] == "add_header":
@@ -437,13 +443,19 @@ class ActionConfig(BaseConfig):
elif self["type"] == "store": elif self["type"] == "store":
self["func"] = store self["func"] = store
self["need_body"] = True self["need_body"] = True
self.add_string_arg(cfg, "storage_type")
assert self["args"]["storage_type"] in ("file"), \
f"{self['name']}: storage_type: invalid value, " \
f"should be 'file'"
if self["args"]["storage_type"] == "file": assert "storage_type" in cfg, \
f"{self['name']}: mandatory parameter 'storage_type' not found"
assert isinstance(cfg["type"], str), \
f"{self['name']}: storage_type: invalid value, " \
f"should be string"
self["storage_type"] = cfg["storage_type"]
if self["storage_type"] == "file":
self.add_string_arg(cfg, "directory") self.add_string_arg(cfg, "directory")
else:
raise RuntimeError(
f"{self['name']}: storage_type: invalid storage type")
else: else:
raise RuntimeError(f"{self['name']}: type: invalid action type") raise RuntimeError(f"{self['name']}: type: invalid action type")
@@ -464,9 +476,6 @@ class Action:
def __init__(self, milter_cfg, cfg): def __init__(self, milter_cfg, cfg):
self.logger = cfg.logger self.logger = cfg.logger
#logger = logging.getLogger(cfg["name"])
#self.logger = CustomLogger(logger, {"name": cfg["name"]})
#self.logger.setLevel(cfg["loglevel"])
if cfg["conditions"] is None: if cfg["conditions"] is None:
self.conditions = None self.conditions = None
@@ -474,30 +483,10 @@ class Action:
self.conditions = Conditions(milter_cfg, cfg["conditions"]) self.conditions = Conditions(milter_cfg, cfg["conditions"])
self.pretend = cfg["pretend"] self.pretend = cfg["pretend"]
self._name = cfg["name"]
self._func = cfg["func"] self._func = cfg["func"]
self._args = cfg["args"] self._args = cfg["args"]
self._need_body = cfg["need_body"]
action_type = cfg["type"]
if action_type == "add_header":
self._func = add_header
self._need_body = False
elif action_type == "mod_header":
self._func = mod_header
self._need_body = False
elif action_type == "del_header":
self._func = del_header
self._need_body = False
elif action_type == "add_disclaimer":
self._func = add_disclaimer
self._need_body = True
elif action_type == "rewrite_links":
self._func = rewrite_links
self._need_body = True
elif action_type == "store":
self._func = store
self._need_body = True
else:
raise ValueError(f"invalid action type: {action_type}")
def need_body(self): def need_body(self):
"""Return the needs of this action.""" """Return the needs of this action."""
@@ -508,7 +497,8 @@ class Action:
if pretend is None: if pretend is None:
pretend = self.pretend pretend = self.pretend
logger = CustomLogger(self.logger, {"qid": milter.qid}) logger = CustomLogger(
self.logger, {"name": self._name, "qid": milter.qid})
return self._func(milter=milter, pretend=pretend, return self._func(milter=milter, pretend=pretend,
logger=logger, **self._args) logger=logger, **self._args)

View File

@@ -26,10 +26,10 @@ from email.message import MIMEPart
class CustomLogger(logging.LoggerAdapter): class CustomLogger(logging.LoggerAdapter):
def process(self, msg, kwargs): def process(self, msg, kwargs):
if "name" in self.extra: if "name" in self.extra:
msg = "{}: {}".format(self.extra["name"], msg) msg = f"{self.extra['name']}: {msg}"
if "qid" in self.extra: if "qid" in self.extra:
msg = "{}: {}".format(self.extra["qid"], msg) msg = f"{self.extra['qid']}: {msg}"
if self.logger.getEffectiveLevel() != logging.DEBUG: if self.logger.getEffectiveLevel() != logging.DEBUG:
msg = msg.replace("\n", "").replace("\r", "") msg = msg.replace("\n", "").replace("\r", "")
@@ -64,7 +64,7 @@ class BaseConfig:
logger = logging.getLogger(self["name"]) logger = logging.getLogger(self["name"])
logger.setLevel(self["loglevel"]) logger.setLevel(self["loglevel"])
self.logger = CustomLogger(logger, {"name": self["name"]}) self.logger = logger
# the keys/values of args are used as parameters # the keys/values of args are used as parameters
# to functions # to functions

View File

@@ -16,11 +16,10 @@ __all__ = [
"ConditionsConfig", "ConditionsConfig",
"Conditions"] "Conditions"]
import logging
import re import re
from netaddr import IPAddress, IPNetwork, AddrFormatError from netaddr import IPAddress, IPNetwork, AddrFormatError
from pymodmilter import CustomLogger, BaseConfig from pymodmilter import BaseConfig
class ConditionsConfig(BaseConfig): class ConditionsConfig(BaseConfig):
@@ -69,9 +68,6 @@ class Conditions:
def __init__(self, milter_cfg, cfg): def __init__(self, milter_cfg, cfg):
self.logger = cfg.logger self.logger = cfg.logger
#logger = logging.getLogger(cfg["name"])
#self.logger = CustomLogger(logger, {"name": cfg["name"]})
#self.logger.setLevel(cfg["loglevel"])
self._local_addrs = milter_cfg["local_addrs"] self._local_addrs = milter_cfg["local_addrs"]
self._args = cfg["args"] self._args = cfg["args"]

View File

@@ -16,16 +16,17 @@ __all__ = [
"RuleConfig", "RuleConfig",
"Rule"] "Rule"]
import logging from pymodmilter import BaseConfig
from pymodmilter import CustomLogger, BaseConfig
from pymodmilter.actions import ActionConfig, Action from pymodmilter.actions import ActionConfig, Action
from pymodmilter.conditions import ConditionsConfig, Conditions from pymodmilter.conditions import ConditionsConfig, Conditions
class RuleConfig(BaseConfig): class RuleConfig(BaseConfig):
def __init__(self, idx, milter_cfg, cfg, debug=False): def __init__(self, idx, milter_cfg, cfg, debug=False):
if "name" not in cfg: if "name" in cfg:
assert isinstance(cfg["name"], str), \
f"Rule #{idx}: name: invalid value, should be string"
else:
cfg["name"] = f"Rule #{idx}" cfg["name"] = f"Rule #{idx}"
if "loglevel" not in cfg: if "loglevel" not in cfg:
@@ -70,9 +71,6 @@ class Rule:
def __init__(self, milter_cfg, cfg): def __init__(self, milter_cfg, cfg):
self.logger = cfg.logger self.logger = cfg.logger
#logger = logging.getLogger(cfg["name"])
#self.logger = CustomLogger(logger, {"name": cfg["name"]})
#self.logger.setLevel(cfg["loglevel"])
if cfg["conditions"] is None: if cfg["conditions"] is None:
self.conditions = None self.conditions = None

View File

@@ -67,17 +67,10 @@ def main():
# setup console log # setup console log
stdouthandler = logging.StreamHandler(sys.stdout) stdouthandler = logging.StreamHandler(sys.stdout)
stdouthandler.setFormatter( formatter = logging.Formatter("%(levelname)s: %(message)s")
logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")) stdouthandler.setFormatter(formatter)
root_logger.addHandler(stdouthandler) root_logger.addHandler(stdouthandler)
# setup syslog
sysloghandler = logging.handlers.SysLogHandler(
address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_MAIL)
sysloghandler.setFormatter(
logging.Formatter("pymodmilter: %(message)s"))
root_logger.addHandler(sysloghandler)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if not args.debug: if not args.debug:
@@ -120,6 +113,13 @@ def main():
stdouthandler.setFormatter(formatter) stdouthandler.setFormatter(formatter)
stdouthandler.setLevel(logging.DEBUG) stdouthandler.setLevel(logging.DEBUG)
# setup syslog
sysloghandler = logging.handlers.SysLogHandler(
address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_MAIL)
sysloghandler.setFormatter(
logging.Formatter("pymodmilter: %(message)s"))
root_logger.addHandler(sysloghandler)
logger.info("pymodmilter starting") logger.info("pymodmilter starting")
ModifyMilter.set_config(cfg) ModifyMilter.set_config(cfg)
@@ -132,7 +132,7 @@ def main():
rc = 0 rc = 0
try: try:
Milter.runmilter("pymodmilter", socketname=socket, timeout=30) Milter.runmilter("pymodmilter", socketname=socket, timeout=300)
logger.info("pymodmilter stopped") logger.info("pymodmilter stopped")
except Milter.milter.error as e: except Milter.milter.error as e:
logger.error(e) logger.error(e)