Skip to content

Commit efedb3b

Browse files
committed
improved logging
1 parent bc898f9 commit efedb3b

File tree

6 files changed

+113
-114
lines changed

6 files changed

+113
-114
lines changed

src/config-test.py

+29-33
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# limitations under the License.
1515
#
1616

17-
import traceback
1817
from datetime import datetime
1918

2019
from imapclient import IMAPClient
@@ -23,24 +22,34 @@
2322
from lib import get_envelope_subject, \
2423
get_envelope_sender_first, \
2524
get_envelope_from_first, \
26-
get_envelope_date
25+
get_envelope_date, \
26+
create_logger
2727
from lib.config import get_config, get_imap_folder, create_imap_connector
2828
from lib.connector import ImapConnector
2929

3030
if __name__ == '__main__':
31-
config = get_config()
31+
root_logger = create_logger()
32+
33+
config = get_config(logger=root_logger)
3234
if not config:
3335
exit(1)
3436

3537
sections = config.sections()
3638
if not sections:
37-
print('No IMAP servers configured. Nothing to do.')
39+
root_logger.warning('No IMAP servers configured. Nothing to do.')
3840
exit(0)
3941

42+
root_logger.info('Testing %s configured IMAP connections...', len(sections))
4043
for section in sections:
41-
connector: ImapConnector = create_imap_connector(config=config, section=section)
44+
logger = create_logger(section)
45+
46+
try:
47+
connector: ImapConnector = create_imap_connector(config=config, section=section)
48+
except Exception as ex:
49+
logger.exception('Invalid configuration. %s', str(ex))
50+
continue
4251

43-
print('[%s] Testing connection...' % section)
52+
logger.info('Testing connection...')
4453
client: IMAPClient | None = None
4554
try:
4655
folder = get_imap_folder(config=config, section=section)
@@ -51,63 +60,50 @@
5160
select_folder_readonly=True,
5261
)
5362
except Exception as ex:
54-
print('[%s] ERROR: Connection failed! %s\n%s' % (
55-
section,
56-
str(ex),
57-
'\n'.join(traceback.format_exception(ex)),
58-
))
63+
logger.exception('Connection failed. %s', str(ex))
5964
continue
6065

61-
print('[%s] Connection successful.' % section)
66+
logger.info('Connection successful.')
6267

6368
try:
6469
capabilities = []
6570
for cap in client.capabilities():
6671
capabilities.append(cap.decode('utf-8'))
6772

68-
print('[%s] Capabilities: %s' % (section, ', '.join(sorted(capabilities)),))
73+
logger.info('Capabilities: %s', ', '.join(sorted(capabilities)))
6974
except Exception as ex:
70-
print('[%s] ERROR: Can\'t load server capabilities! %s\n%s' % (
71-
section,
72-
str(ex),
73-
'\n'.join(traceback.format_exception(ex)),
74-
))
75+
logger.exception('Can\'t load server capabilities. %s', str(ex))
7576
continue
7677

77-
print('[%s] Fetch latest message from "%s".' % (section, folder,))
78+
logger.info('Fetch latest message from "%s".', folder)
7879
message_numbers = client.search()
7980
if len(message_numbers) < 1:
80-
print('[%s] No messages found in "%s".' % (section, folder,))
81+
logger.info('No messages found in "%s".', folder)
8182
continue
8283

8384
last_message_number = message_numbers[len(message_numbers) - 1]
84-
# print('[%s] Fetching message number #%s...' % (section, last_message_number,))
85-
8685
result = client.fetch([last_message_number], ['ENVELOPE'])
8786
if last_message_number not in result:
88-
print('[%s] ERROR: No envelope data found for message nr %s in "%s".' % (
89-
section,
90-
last_message_number,
91-
folder,
92-
))
87+
logger.error('No envelope data found for message nr %s in "%s".', last_message_number, folder)
9388
continue
9489

95-
print('[%s] Latest message in "%s":' % (section, folder,))
90+
message_info = ['Latest message in "%s":' % folder]
9691

9792
last_message_envelope: Envelope = result[last_message_number][b'ENVELOPE']
98-
# pprint(last_message_envelope)
9993

10094
msg_date: datetime | None = get_envelope_date(last_message_envelope)
101-
print('-> Date : %s' % msg_date)
95+
message_info.append('-> Date : %s' % msg_date)
10296

10397
subject: str | None = get_envelope_subject(last_message_envelope)
104-
print('-> Subject : %s' % subject)
98+
message_info.append('-> Subject : %s' % subject)
10599

106100
from_address: Address | None = get_envelope_from_first(last_message_envelope)
107-
print('-> From : %s' % str(from_address))
101+
message_info.append('-> From : %s' % str(from_address))
108102

109103
sender_address: Address | None = get_envelope_sender_first(last_message_envelope)
110-
print('-> Sender : %s' % str(sender_address))
104+
message_info.append('-> Sender : %s' % str(sender_address))
105+
106+
logger.info('\n'.join(message_info))
111107

112108
finally:
113109
# noinspection PyBroadException

src/lib/__init__.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# limitations under the License.
1515
#
1616

17+
import logging
18+
import sys
1719
from datetime import datetime
1820
from enum import Enum
1921

@@ -32,6 +34,32 @@ class EncryptionCertificateCheck(Enum):
3234
REQUIRED = 'required'
3335

3436

37+
__LOGGERS: dict[str, logging.Logger] = {}
38+
39+
40+
def create_logger(name: str = 'app', level: int = logging.INFO) -> logging.Logger:
41+
if name in __LOGGERS:
42+
return __LOGGERS[name]
43+
44+
logger: logging.Logger = logging.getLogger(name)
45+
logger.setLevel(level)
46+
47+
# noinspection SpellCheckingInspection
48+
logger_format = '[%(levelname)s] %(asctime)s | %(message)s' if name == 'app' \
49+
else '[%(levelname)s:%(name)s] %(asctime)s | %(message)s'
50+
formatter: logging.Formatter = logging.Formatter(
51+
fmt=logger_format,
52+
datefmt='%Y-%m-%d %H:%M:%S'
53+
)
54+
55+
handler: logging.StreamHandler = logging.StreamHandler(stream=sys.stdout)
56+
handler.setFormatter(formatter)
57+
logger.addHandler(handler)
58+
59+
__LOGGERS[name] = logger
60+
return logger
61+
62+
3563
def get_address_mail(address: Address, charset='utf-8') -> str | None:
3664
"""
3765
Extracts mail address from an address.
@@ -59,7 +87,7 @@ def get_address_name(address: Address, charset='utf-8') -> str | None:
5987
:return: mail name or None, if invalid
6088
"""
6189

62-
if address.name:
90+
if not address.name:
6391
return None
6492

6593
return address.name.decode(charset).strip()

src/lib/callback.py

+17-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#
1616

1717
import subprocess
18-
import traceback
1918
from datetime import datetime
2019
from os import getcwd
2120
from threading import Thread
@@ -30,7 +29,8 @@
3029
get_envelope_sender_first, \
3130
get_envelope_to_first, \
3231
get_address_name, \
33-
get_address_mail
32+
get_address_mail, \
33+
create_logger
3434

3535

3636
class CallbackHandler:
@@ -115,6 +115,14 @@ def __init__(
115115
self.__command = command
116116
self.__environment = {**environment}
117117
self.__thread = Thread(target=self.__run)
118+
self.__logger = create_logger(self.__name)
119+
120+
# make sure, that environment dict does not contain None values
121+
# as it might lead to errors on execution
122+
for key, value in self.__environment.items():
123+
if value is None:
124+
self.__logger.warning('Environment variable "%s" has None value.', key)
125+
self.__environment[key] = ''
118126

119127
def start(self):
120128
"""
@@ -136,31 +144,22 @@ def __run(self):
136144
"""
137145

138146
try:
139-
print('[%s] Running "%s" from working directory "%s"...' % (
140-
self.__name,
141-
self.__command,
142-
getcwd(),
143-
))
147+
self.__logger.info('Running "%s" from working directory "%s"...', self.__command, getcwd())
144148

145149
result: subprocess.CompletedProcess = subprocess.run(
146150
self.__command,
147151
shell=True,
148152
env=self.__environment,
149-
cwd=getcwd(),
150-
text=True
153+
cwd=getcwd()
151154
)
152155

153156
if result.returncode != 0:
154-
print('[%s] ERROR: Callback script "%s" returned non-zero exit code (%s)!' % (
155-
self.__name,
157+
self.__logger.warning(
158+
'Callback script "%s" returned non-zero exit code (%s)!',
156159
self.__command,
157-
result.returncode,
158-
))
160+
result.returncode
161+
)
159162
return
160163

161164
except Exception as ex:
162-
print('[%s] ERROR: Callback script error! %s\n%s' % (
163-
self.__name,
164-
str(ex),
165-
'\n'.join(traceback.format_exception(ex)),
166-
))
165+
self.__logger.exception('Unexpected callback error. %s', str(ex))

src/lib/config.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515
#
1616

17+
import logging
1718
import os
1819
import sys
1920
from configparser import ConfigParser
@@ -24,14 +25,14 @@
2425
from .idle import ImapIdleHandler
2526

2627

27-
def get_config() -> ConfigParser | None:
28+
def get_config(logger: logging.Logger) -> ConfigParser | None:
2829
if len(sys.argv) < 2:
29-
print('ERROR: Please provide a config file as first argument!')
30+
logger.error('Please provide a config file as first argument!')
3031
return None
3132

3233
config_path = sys.argv[1]
3334
if not os.path.isfile(config_path):
34-
print('ERROR: Can\'t find config file at "%s"!' % config_path)
35+
logger.error('Can\'t find config file at "%s"!' % config_path)
3536
return None
3637

3738
config = ConfigParser()
@@ -74,15 +75,20 @@ def create_imap_connector(
7475
section: str,
7576
use_uid=False
7677
) -> ImapConnector:
78+
try:
79+
port: int = int(config.get(
80+
section, 'port',
81+
fallback='143',
82+
).strip())
83+
except ValueError:
84+
raise Exception('Can\'t read port number "%s".' % config.get(section, 'port'))
85+
7786
return ImapConnector(
7887
host=config.get(
7988
section, 'host',
8089
fallback='localhost',
8190
),
82-
port=int(config.get(
83-
section, 'port',
84-
fallback='143',
85-
).strip()),
91+
port=port,
8692
username=config.get(
8793
section, 'username',
8894
fallback=None,

0 commit comments

Comments
 (0)