-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathad_logger.py
executable file
·208 lines (180 loc) · 7.43 KB
/
ad_logger.py
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
Automate demultiplex logging. Classes required for logging
"""
import sys
import re
import logging
import logging.handlers
from config.ad_config import AdLoggerConfig
def remove_all_loggers() -> None:
"""
Remove all loggers
:return None:
"""
for name in list(logging.Logger.manager.loggerDict.keys()):
if isinstance(logging.Logger.manager.loggerDict[name], logging.Logger):
logging.getLogger(name).handlers = []
logging.getLogger(name).propagate = True
logging.Logger.manager.loggerDict.pop(name)
def get_logging_formatter() -> str:
"""
Get formatter for logging. This is script mode-dependent
:return (str): Logging formatter string
"""
return (
f"%(asctime)s - {AdLoggerConfig.SCRIPT_MODE} "
"- %(name)s - %(levelname)s - %(message)s"
)
def set_root_logger() -> object:
"""
Set up root logger and add stream handler and syslog handler - we only want to add these once
else it will duplicate log messages to the terminal. All loggers named with the same stem
as the root logger will use these same syslog handler and stream handler
:return logger: Logging object
"""
sensitive_formatter = SensitiveFormatter(get_logging_formatter())
logger = logging.getLogger(AdLoggerConfig.REPO_NAME)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(sensitive_formatter)
stream_handler.name = "stream_handler"
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
syslog_handler.setFormatter(sensitive_formatter)
syslog_handler.name = "syslog_handler"
logging.basicConfig(
level=logging.INFO,
force=True,
handlers=[
stream_handler,
syslog_handler,
],
)
return logger
def shutdown_logs(logger: logging.Logger) -> None:
"""
Close and remove all handlers for a logging object
:param logger (logging.Logger): Logger
:return (None):
"""
for handler in logger.handlers[:]:
logger.removeHandler(handler)
handler.close()
class SensitiveFormatter(logging.Formatter):
"""
Formatter that removes sensitive information in logs. Inherits
the properties and methods from logging.Formatter
Methods
_filter(message)
Filter out the auth key with regex
format(record)
Format the the record using logging.Formatter and _filter
"""
@staticmethod
def _filter(message: str) -> str:
"""
Filter out the auth key with regex
:param message (str): Message to be filtered
:return (str): Filtered log message
"""
return re.sub(r"--auth [^ ]+ ", r"--auth <MASKED_KEY> ", message)
def format(self, record: logging.LogRecord) -> str:
"""
Format the the record using _filter
:param record (logging.LogRecord): Object to be logged
:return (str): Formatted filtered log message
"""
original = logging.Formatter.format(self, record) # Call parent method
return self._filter(original)
class AdLogger(AdLoggerConfig):
"""
Creates a python logging object with custom attributes and a file handler
Attributes
logger_name (str): Name of logger
logger_type (str): Type of logger, e.g. demultiplex, sw, etc.
filepath (str): Name of filepath to provide to _file_handler()
formatter (object): Sensitive formatter object
Methods
get_logger()
Returns a Python logging object
_get_file_handler()
Get file handler for the logger
"""
def __init__(self, logger_name: str, logger_type: str, filepath: str):
"""
Constructor for the AdLogger class
:param logger_name (str): Name of logger
:param logger_type (str): Type of logger, e.g. demultiplex, sw, etc.
:param filepath (str): Name of filepath to provide to _file_handler()
"""
self.logger_name = logger_name
self.logger_type = logger_type
self.filepath = filepath
self.formatter = SensitiveFormatter(get_logging_formatter())
def get_logger(self) -> logging.Logger:
"""
Returns a Python logging object, and give it a name
:return logger (object): Python logging object with custom attributes
"""
logger = logging.getLogger(f"{AdLoggerConfig.REPO_NAME}.{self.logger_name}")
logger.filepath = self.filepath
logger.setLevel(logging.DEBUG)
logger.addHandler(self._get_file_handler())
logger.timestamp = (
AdLoggerConfig.TIMESTAMP
) # Timestamp in the format %Y%m%d_%H%M%S
logger.log_msgs = (
AdLoggerConfig.LOG_MSGS["general"] | AdLoggerConfig.LOG_MSGS["ad_email"]
)
if self.logger_type in AdLoggerConfig.LOG_MSGS.keys():
logger.log_msgs.update(AdLoggerConfig.LOG_MSGS[self.logger_type])
return logger
def _get_file_handler(self) -> logging.FileHandler:
"""
Get file handler for the logger, and give it a name
:return file_handler (logging.FileHandler): FileHandler
"""
file_handler = logging.FileHandler(self.filepath, mode="w", delay=True)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(self.formatter)
file_handler.name = "file_handler"
return file_handler
class RunfolderLoggers(object):
"""
Creates an RunfolderLoggers object that contains various loggers required by the
script that calls it. The loggers created are dictated by the logfiles_config dict
provided as a parameter
Attributes
script (str): Script from which the base logger was setup
(named in same way to ensure it uses the root syslog / stream handler)
runfolder_name (str): Runfolder name
logfiles_config (dict): Dictionary of paths for each logfile
loggers (dict): Dict of loggers
Methods
get_loggers()
Create loggers dict using AdLogger class and logfiles_config
"""
def __init__(self, script: str, runfolder_name: str, logfiles_config: dict):
"""
Constructor for the RunfolderLoggers class
:param script (str): Script from which the base logger was setup
(named in same way to ensure it uses the root syslog / stream handler)
:param runfolder_name (str): Runfolder name
:param logfiles_config (dict): Dictionary containing the configuration
for the required loggers
"""
self.script = script
self.runfolder_name = runfolder_name
self.logfiles_config = logfiles_config
self.loggers = self.get_loggers() # Collect all loggers
def get_loggers(self) -> dict:
"""
Assign loggers using logfiles_config
:return all_loggers (dict): Dict of logger types
"""
loggers = {}
for logger_type in self.logfiles_config.keys():
logger_name = f"{self.script}.{logger_type}-{self.runfolder_name}"
ad_logger_obj = AdLogger(
logger_name, logger_type, self.logfiles_config[logger_type]
)
loggers[logger_type] = ad_logger_obj.get_logger()
return loggers