You've already forked Mailu
							
							
				mirror of
				https://github.com/Mailu/Mailu.git
				synced 2025-10-30 23:37:43 +02:00 
			
		
		
		
	Fixed log filter not filtering out log messages for dovecot/nginx/postfix.
Fixed postfix not logging to standard out. Fixed not all containers logging to journald. Removed POSTFIX_LOG_FILE functionality. Added documentation on how to achieve the same (log to file) via journald & rsyslogd (see new FAQ entry 'How can I view and export the logs of a Mailu container?').
This commit is contained in:
		| @@ -6,6 +6,8 @@ import re | |||||||
| from pwd import getpwnam | from pwd import getpwnam | ||||||
| import socket | import socket | ||||||
| import tenacity | import tenacity | ||||||
|  | import subprocess | ||||||
|  | import threading | ||||||
|  |  | ||||||
| @tenacity.retry(stop=tenacity.stop_after_attempt(100), | @tenacity.retry(stop=tenacity.stop_after_attempt(100), | ||||||
|                 wait=tenacity.wait_random(min=2, max=5)) |                 wait=tenacity.wait_random(min=2, max=5)) | ||||||
| @@ -27,7 +29,7 @@ def _coerce_value(value): | |||||||
|     return value |     return value | ||||||
|  |  | ||||||
| class LogFilter(object): | class LogFilter(object): | ||||||
|     def __init__(self, stream, re_patterns, log_file): |     def __init__(self, stream, re_patterns): | ||||||
|         self.stream = stream |         self.stream = stream | ||||||
|         if isinstance(re_patterns, list): |         if isinstance(re_patterns, list): | ||||||
|             self.pattern = re.compile('|'.join([f'(?:{pattern})' for pattern in re_patterns])) |             self.pattern = re.compile('|'.join([f'(?:{pattern})' for pattern in re_patterns])) | ||||||
| @@ -36,7 +38,6 @@ class LogFilter(object): | |||||||
|         else: |         else: | ||||||
|             self.pattern = re_patterns |             self.pattern = re_patterns | ||||||
|         self.found = False |         self.found = False | ||||||
|         self.log_file = log_file |  | ||||||
|  |  | ||||||
|     def __getattr__(self, attr_name): |     def __getattr__(self, attr_name): | ||||||
|         return getattr(self.stream, attr_name) |         return getattr(self.stream, attr_name) | ||||||
| @@ -48,12 +49,6 @@ class LogFilter(object): | |||||||
|             if not self.pattern.search(data): |             if not self.pattern.search(data): | ||||||
|                 self.stream.write(data) |                 self.stream.write(data) | ||||||
|                 self.stream.flush() |                 self.stream.flush() | ||||||
|                 if self.log_file: |  | ||||||
|                     try: |  | ||||||
|                         with open(self.log_file, 'a', encoding='utf-8') as l: |  | ||||||
|                             l.write(data) |  | ||||||
|                     except: |  | ||||||
|                         pass |  | ||||||
|             else: |             else: | ||||||
|                 # caught bad pattern |                 # caught bad pattern | ||||||
|                 self.found = True |                 self.found = True | ||||||
| @@ -74,10 +69,10 @@ def _is_compatible_with_hardened_malloc(): | |||||||
|                 return False |                 return False | ||||||
|     return True |     return True | ||||||
|  |  | ||||||
| def set_env(required_secrets=[], log_filters=[], log_file=None): | def set_env(required_secrets=[], log_filters=[]): | ||||||
|     if log_filters: |     if log_filters: | ||||||
|         sys.stdout = LogFilter(sys.stdout, log_filters, log_file) |         sys.stdout = LogFilter(sys.stdout, log_filters) | ||||||
|         sys.stderr = LogFilter(sys.stderr, log_filters, log_file) |         sys.stderr = LogFilter(sys.stderr, log_filters) | ||||||
|     log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) |     log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) | ||||||
|  |  | ||||||
|     if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): |     if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): | ||||||
| @@ -115,3 +110,24 @@ def drop_privs_to(username='mailu'): | |||||||
|     os.setgid(pwnam.pw_gid) |     os.setgid(pwnam.pw_gid) | ||||||
|     os.setuid(pwnam.pw_uid) |     os.setuid(pwnam.pw_uid) | ||||||
|     os.environ['HOME'] = pwnam.pw_dir |     os.environ['HOME'] = pwnam.pw_dir | ||||||
|  |  | ||||||
|  | # forwards text lines from src to dst in an infinite loop | ||||||
|  | def forward_text_lines(src, dst): | ||||||
|  |     while True: | ||||||
|  |         current_line = src.readline() | ||||||
|  |         dst.write(current_line) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # runs a process and passes its standard/error output to the standard/error output of the current python script | ||||||
|  | def run_process_and_forward_output(cmd): | ||||||
|  |     process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | ||||||
|  |  | ||||||
|  |     stdout_thread = threading.Thread(target=forward_text_lines, args=(process.stdout, sys.stdout)) | ||||||
|  |     stdout_thread.daemon = True | ||||||
|  |     stdout_thread.start() | ||||||
|  |  | ||||||
|  |     stderr_thread = threading.Thread(target=forward_text_lines, args=(process.stderr, sys.stderr)) | ||||||
|  |     stderr_thread.daemon = True | ||||||
|  |     stderr_thread.start() | ||||||
|  |  | ||||||
|  |     process.wait() | ||||||
|   | |||||||
| @@ -3,13 +3,11 @@ | |||||||
| import os | import os | ||||||
| import glob | import glob | ||||||
| import multiprocessing | import multiprocessing | ||||||
| import logging as log |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| from podop import run_server | from podop import run_server | ||||||
| from socrate import system, conf | from socrate import system, conf | ||||||
|  |  | ||||||
| system.set_env(log_filters=r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$') | system.set_env(log_filters=[r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$']) | ||||||
|  |  | ||||||
| def start_podop(): | def start_podop(): | ||||||
|     system.drop_privs_to('mail') |     system.drop_privs_to('mail') | ||||||
| @@ -35,4 +33,5 @@ os.system("chown mail:mail /mail") | |||||||
| os.system("chown -R mail:mail /var/lib/dovecot /conf") | os.system("chown -R mail:mail /var/lib/dovecot /conf") | ||||||
|  |  | ||||||
| multiprocessing.Process(target=start_podop).start() | multiprocessing.Process(target=start_podop).start() | ||||||
| os.system("dovecot -c /etc/dovecot/dovecot.conf -F") | cmd = ['/usr/sbin/dovecot', '-c', '/etc/dovecot/dovecot.conf', '-F'] | ||||||
|  | system.run_process_and_forward_output(cmd) | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import os | |||||||
| import subprocess | import subprocess | ||||||
| from socrate import system | from socrate import system | ||||||
|  |  | ||||||
| system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25,110,143,587,465,993,995)$') | system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25|110|143|587|465|993|995)$') | ||||||
|  |  | ||||||
| # Check if a stale pid file exists | # Check if a stale pid file exists | ||||||
| if os.path.exists("/var/run/nginx.pid"): | if os.path.exists("/var/run/nginx.pid"): | ||||||
| @@ -17,4 +17,5 @@ elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]: | |||||||
|  |  | ||||||
| subprocess.call(["/config.py"]) | subprocess.call(["/config.py"]) | ||||||
| os.system("dovecot -c /etc/dovecot/proxy.conf") | os.system("dovecot -c /etc/dovecot/proxy.conf") | ||||||
| os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"]) | cmd = ['/usr/sbin/nginx', '-g', 'daemon off;'] | ||||||
|  | system.run_process_and_forward_output(cmd) | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ from socrate import system, conf | |||||||
| system.set_env(log_filters=[ | system.set_env(log_filters=[ | ||||||
|     r'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$', |     r'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$', | ||||||
|     r'haproxy read\: short protocol header\: QUIT$', |     r'haproxy read\: short protocol header\: QUIT$', | ||||||
|     r'discarding EHLO keywords\: PIPELINING$', |     r'discarding EHLO keywords\: PIPELINING$' | ||||||
|     ], log_file=os.environ.get('POSTFIX_LOG_FILE')) |     ]) | ||||||
|  |  | ||||||
| os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid") | os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid") | ||||||
|  |  | ||||||
| @@ -100,4 +100,5 @@ os.system("/usr/libexec/postfix/post-install meta_directory=/etc/postfix create- | |||||||
| # Before starting postfix, we need to check permissions on /queue | # Before starting postfix, we need to check permissions on /queue | ||||||
| # in the event that postfix,postdrop id have changed | # in the event that postfix,postdrop id have changed | ||||||
| os.system("postfix set-permissions") | os.system("postfix set-permissions") | ||||||
| os.system("postfix start-fg") | cmd = ['postfix', 'start-fg'] | ||||||
|  | system.run_process_and_forward_output(cmd) | ||||||
|   | |||||||
| @@ -376,22 +376,6 @@ To disable all plugins just set ``ROUNDCUBE_PLUGINS`` to ``mailu``. | |||||||
|  |  | ||||||
| To configure a plugin add php files named ``*.inc.php`` to roundcube's :ref:`override section <override-label>`. | To configure a plugin add php files named ``*.inc.php`` to roundcube's :ref:`override section <override-label>`. | ||||||
|  |  | ||||||
| Mail log settings |  | ||||||
| ----------------- |  | ||||||
|  |  | ||||||
| By default, all services log directly to stdout/stderr. Logs can be collected by any docker log processing solution. |  | ||||||
|  |  | ||||||
| Postfix writes the logs to a syslog server which logs to stdout. This is used to filter |  | ||||||
| out messages from the healthcheck. In some situations, a separate mail log is required |  | ||||||
| (e.g. for legal reasons). The syslog server can be configured to write log files to a volume. |  | ||||||
| It can be configured with the following option: |  | ||||||
|  |  | ||||||
| - ``POSTFIX_LOG_FILE``: The file to log the mail log to. When enabled, the syslog server will also log to stdout. |  | ||||||
|  |  | ||||||
| When ``POSTFIX_LOG_FILE`` is enabled, the logrotate program will automatically rotate the |  | ||||||
| logs every week and keep 52 logs. To override the logrotate configuration, create the file logrotate.conf |  | ||||||
| with the desired configuration in the :ref:`Postfix overrides folder<override-label>`. |  | ||||||
|  |  | ||||||
| .. _header_authentication: | .. _header_authentication: | ||||||
|  |  | ||||||
| Header authentication using an external proxy | Header authentication using an external proxy | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								docs/faq.rst
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								docs/faq.rst
									
									
									
									
									
								
							| @@ -451,7 +451,7 @@ down and up again. A container restart is not sufficient. | |||||||
| SMTP Banner from overrides/postfix.cf is ignored | SMTP Banner from overrides/postfix.cf is ignored | ||||||
| ```````````````````````````````````````````````` | ```````````````````````````````````````````````` | ||||||
|  |  | ||||||
| Any mail related connection is proxied by nginx. Therefore the SMTP Banner is also set by nginx. Overwriting in overrides/postfix.cf does not apply. | Any mail related connection is proxied by the front container. Therefore the SMTP Banner is also set by front container. Overwriting in overrides/postfix.cf does not apply. | ||||||
|  |  | ||||||
| *Issue reference:* `1368`_. | *Issue reference:* `1368`_. | ||||||
|  |  | ||||||
| @@ -922,3 +922,64 @@ I see a lot of "Unable to lookup the TLSA record for XXX. Is the DNSSEC zone oka | |||||||
| There may be multiple causes for it but if you are running docker 24.0.0, odds are you are `experiencing this docker bug`_ and the workaround is to switch to a different version of docker. | There may be multiple causes for it but if you are running docker 24.0.0, odds are you are `experiencing this docker bug`_ and the workaround is to switch to a different version of docker. | ||||||
|  |  | ||||||
| .. _`experiencing this docker bug`: https://github.com/Mailu/Mailu/issues/2827 | .. _`experiencing this docker bug`: https://github.com/Mailu/Mailu/issues/2827 | ||||||
|  |  | ||||||
|  | How can I view and export the logs of a Mailu container? | ||||||
|  | ```````````````````````````````````````````````````````` | ||||||
|  |  | ||||||
|  | In some situations, a separate log is required. For example a separate mail log (from postfix) could be required due to legal reasons. | ||||||
|  |  | ||||||
|  | All Mailu containers log the output to journald. The logs are written to journald with the tag: | ||||||
|  |  | ||||||
|  | | mailu-<service name> | ||||||
|  | | where <service-name> is the name of the service in the docker-compose.yml file. | ||||||
|  | | For example, the service running postfix is called smtp. To view the postfix logs use: | ||||||
|  |  | ||||||
|  | .. code-block:: bash | ||||||
|  |  | ||||||
|  |   journalctl -t mailu-smtp | ||||||
|  |  | ||||||
|  | Note: ``SHIFT+G`` can be used to jump to the end of the log file. ``G`` can be used to jump back to the top of the log file. | ||||||
|  |  | ||||||
|  | To export the log files from journald to the file system, the logs could be imported into a syslog program like ``rsyslog``. | ||||||
|  | Via ``rsyslog`` the container specific logs could be written to a separate file using a filter. | ||||||
|  |  | ||||||
|  | Below are the steps for writing the postfix (mail) logs to a log file on the file system. | ||||||
|  |  | ||||||
|  | 1. Install the ``rsyslog`` package. Note: on most distributions this program is already installed. | ||||||
|  | 2. Edit ``/etc/systemd/journald.conf``. | ||||||
|  | 3. Enable ``ForwardToSyslog=yes``. Note: on most distributions this is already enabled by default. This forwards journald to syslog. | ||||||
|  | 4. ``sudo touch /var/log/postfix.log``. This step creates the mail log file. | ||||||
|  | 5. ``sudo chown syslog:syslog /var/log/postfix.log``. This provides rsyslog the permissions for accessing this file. | ||||||
|  | 6. Create a new config file in ``/etc/rsyslog.d/export-postfix.conf`` | ||||||
|  | 7. Add ``:programname, contains, "mailu-smtp" /var/log/postfix.log``. This instructs rsyslog to write the logs for mailu-smtp to a log file on file system. | ||||||
|  | 8. ``sudo systemctl restart systemd-journald.service`` | ||||||
|  | 9. ``sudo systemctl restart rsyslog`` | ||||||
|  | 10. All messages from the smtp/postfix container are now logged to ``/var/log/postfix.log``. | ||||||
|  | 11. Rsyslog does not perform log rotation. The program (package) ``log rotate`` can be used for this task. Install the ``logrotate`` package. | ||||||
|  | 12. Modify the existing configuration file for rsyslog: ``sudo nano /etc/logrotate.d/rsyslog`` | ||||||
|  | 13. Add at the top add: ``/var/log/postfix.log``. Of course you can also use your own configuration. This is just an example. A complete example for configuring log rotate is: | ||||||
|  |  | ||||||
|  | .. code-block:: bash | ||||||
|  |  | ||||||
|  |   /var/log/postfix.log | ||||||
|  |   { | ||||||
|  |        rotate 4 | ||||||
|  |        weekly | ||||||
|  |        missingok | ||||||
|  |        notifempty | ||||||
|  |        compress | ||||||
|  |        delaycompress | ||||||
|  |        sharedscripts | ||||||
|  |        postrotate | ||||||
|  |            /usr/lib/rsyslog/rsyslog-rotate | ||||||
|  |        endscript | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | .. code-block:: bash | ||||||
|  |  | ||||||
|  |   #!/bin/sh | ||||||
|  |   #/usr/lib/rsyslog/rsyslog-rotate | ||||||
|  |  | ||||||
|  |   if [ -d /run/systemd/system ]; then | ||||||
|  |       systemctl kill -s HUP rsyslog.service | ||||||
|  |   fi | ||||||
|   | |||||||
| @@ -58,6 +58,10 @@ services: | |||||||
|   resolver: |   resolver: | ||||||
|     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}} |     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}} | ||||||
|     env_file: {{ env }} |     env_file: {{ env }} | ||||||
|  |     logging: | ||||||
|  |       driver: journald | ||||||
|  |       options: | ||||||
|  |         tag: mailu-resolver | ||||||
|     restart: always |     restart: always | ||||||
|     networks: |     networks: | ||||||
|       default: |       default: | ||||||
| @@ -220,7 +224,7 @@ services: | |||||||
|     logging: |     logging: | ||||||
|       driver: journald |       driver: journald | ||||||
|       options: |       options: | ||||||
|         tag: mailu-clamav |         tag: mailu-antivirus | ||||||
|     networks: |     networks: | ||||||
|       - clamav |       - clamav | ||||||
|     volumes: |     volumes: | ||||||
| @@ -237,6 +241,10 @@ services: | |||||||
|   webdav: |   webdav: | ||||||
|     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}} |     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}} | ||||||
|     restart: always |     restart: always | ||||||
|  |     logging: | ||||||
|  |       driver: journald | ||||||
|  |       options: | ||||||
|  |         tag: mailu-webdav | ||||||
|     volumes: |     volumes: | ||||||
|       - "{{ root }}/dav:/data" |       - "{{ root }}/dav:/data" | ||||||
|     networks: |     networks: | ||||||
| @@ -248,6 +256,10 @@ services: | |||||||
|     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}} |     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}} | ||||||
|     restart: always |     restart: always | ||||||
|     env_file: {{ env }} |     env_file: {{ env }} | ||||||
|  |     logging: | ||||||
|  |       driver: journald | ||||||
|  |       options: | ||||||
|  |         tag: mailu-fetchmail | ||||||
|     volumes: |     volumes: | ||||||
|       - "{{ root }}/data/fetchmail:/data" |       - "{{ root }}/data/fetchmail:/data" | ||||||
|     depends_on: |     depends_on: | ||||||
| @@ -267,6 +279,10 @@ services: | |||||||
|     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}webmail:${MAILU_VERSION:-{{ version }}} |     image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}webmail:${MAILU_VERSION:-{{ version }}} | ||||||
|     restart: always |     restart: always | ||||||
|     env_file: {{ env }} |     env_file: {{ env }} | ||||||
|  |     logging: | ||||||
|  |       driver: journald | ||||||
|  |       options: | ||||||
|  |         tag: mailu-webmail | ||||||
|     volumes: |     volumes: | ||||||
|       - "{{ root }}/webmail:/data" |       - "{{ root }}/webmail:/data" | ||||||
|       - "{{ root }}/overrides/{{ webmail_type }}:/overrides:ro" |       - "{{ root }}/overrides/{{ webmail_type }}:/overrides:ro" | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								towncrier/newsfragments/2939.bugfix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								towncrier/newsfragments/2939.bugfix
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | Fixed log filter not filtering out log messages for dovecot/nginx/postfix. | ||||||
|  | Fixed postfix not logging to standard out. | ||||||
|  | Fixed not all containers logging to journald. | ||||||
|  | Removed POSTFIX_LOG_FILE functionality. Added documentation on how to achieve the same (log to file) via journald & rsyslogd (see new FAQ entry 'How can I view and export the logs of a Mailu container?'). | ||||||
		Reference in New Issue
	
	Block a user