-
Notifications
You must be signed in to change notification settings - Fork 14
Refactor logs using structlog #168
Changes from 12 commits
a595055
46418ea
6b8af2e
ce109d1
94e1959
4eb7f76
ebb157a
59ec971
e30b92f
ae108a1
57ff453
56adb9b
d2d2d32
4bc8a31
e260149
72f4d4b
28033cd
6d93a57
728ae3d
d54397e
e2bbf0e
0cf7ce1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| cryptography | ||
| requests | ||
| structlog |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,96 +1,124 @@ | ||
| import json | ||
| import logging | ||
| from datetime import datetime | ||
| import logging.config | ||
| from io import StringIO | ||
| import structlog | ||
|
|
||
| configured = False # set to True once setup_logging is called | ||
|
|
||
| class _StructuredMessage: | ||
| def __init__(self, message, **kwargs): | ||
| self.message = message | ||
| self.time = datetime.now().strftime("%d/%m/%Y %H:%M:%S") | ||
| self.kwargs = kwargs | ||
| timestamper = structlog.processors.TimeStamper(fmt="%d/%m/%Y %H:%M:%S") | ||
| pre_chain = [ | ||
| timestamper, | ||
| ] | ||
|
|
||
| def to_dict(self): | ||
| return {**self.kwargs, "message": self.message, "time": self.time} | ||
|
|
||
|
|
||
| class Logger: | ||
| # Stripped down version of structlog.dev.ConsoleRenderer, adding the "component" instead of the level. | ||
| class CustomLogRenderer: | ||
| """ | ||
| The :class:`Logger` is in charge of logging events into the log file. | ||
|
|
||
| Args: | ||
| log_name_prefix (:obj:`str`): the prefix of the logger where the data will be stored in (server, client, ...). | ||
| actor (:obj:`str`): the system actor that is logging the event (e.g. ``Watcher``, ``Cryptographer``, ...). | ||
| Render ``event_dict``. It renders the timestamp, followed by the component within "[]" (unless it's None), | ||
| followed by the event, then any remaining item in ``event_dict`` in the key=value format. | ||
| """ | ||
|
|
||
| def __init__(self, log_name_prefix, actor=None): | ||
| self.actor = actor | ||
| self.f_logger = logging.getLogger("{}_file_log".format(log_name_prefix)) | ||
| self.c_logger = logging.getLogger("{}_console_log".format(log_name_prefix)) | ||
| def _repr(self, val): | ||
| """Returns the representation of *val* if it's not a ``str``.""" | ||
| return val if isinstance(val, str) else repr(val) | ||
|
|
||
| def _add_prefix(self, msg): | ||
| return msg if self.actor is None else "[{}]: {}".format(self.actor, msg) | ||
| def __call__(self, _, __, event_dict): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstrings missing.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a one-liner docstring, not sure there's more to add as it would repeat the class docstring, basically. |
||
| """Returns ``event_dict`` rendered as a string.""" | ||
| sio = StringIO() | ||
|
|
||
| def _create_console_message(self, msg, **kwargs): | ||
| s_message = _StructuredMessage(self._add_prefix(msg), **kwargs).to_dict() | ||
| message = "{} {}".format(s_message["time"], s_message["message"]) | ||
| ts = event_dict.pop("timestamp", None) | ||
| if ts: | ||
| sio.write(str(ts) + " ") | ||
|
|
||
| # s_message will always have at least two items (message and time). | ||
| if len(s_message) > 2: | ||
| params = "".join("{}={}, ".format(k, v) for k, v in s_message.items() if k not in ["message", "time"]) | ||
| component = event_dict.pop("component", None) | ||
| if component: | ||
| sio.write("[" + component + "] ") | ||
|
|
||
| # Remove the extra 2 characters (space and comma) and add all data to the final message. | ||
| message += " ({})".format(params[:-2]) | ||
| event = self._repr(event_dict.pop("event")) | ||
|
|
||
| return message | ||
| sio.write(event) | ||
|
|
||
| @staticmethod | ||
| def _create_file_message(msg, **kwargs): | ||
| return json.dumps(_StructuredMessage(msg, **kwargs).to_dict()) | ||
| # Represent all the key=value elements still in event_dict | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can surround this by parenthesis so it is easier to parse in case we ever need to: ts [component] msg (key1=value1, ...)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, done in e260149. |
||
| key_value_part = " ".join(key + "=" + self._repr(event_dict[key]) for key in sorted(event_dict.keys())) | ||
| if len(key_value_part) > 0: | ||
| sio.write("\t" + key_value_part) | ||
|
|
||
| def info(self, msg, **kwargs): | ||
| """ | ||
| Logs an ``INFO`` level message to stdout and file. | ||
| return sio.getvalue() | ||
|
|
||
| Args: | ||
| msg (:obj:`str`): the message to be logged. | ||
| kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output. | ||
| """ | ||
|
|
||
| self.f_logger.info(self._create_file_message(msg, **kwargs)) | ||
| self.c_logger.info(self._create_console_message(msg, **kwargs)) | ||
|
|
||
| def debug(self, msg, **kwargs): | ||
| """ | ||
| Logs a ``DEBUG`` level message to stdout and file. | ||
|
|
||
| Args: | ||
| msg (:obj:`str`): the message to be logged. | ||
| kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output. | ||
| """ | ||
|
|
||
| self.f_logger.debug(self._create_file_message(msg, **kwargs)) | ||
| self.c_logger.debug(self._create_console_message(msg, **kwargs)) | ||
| def setup_logging(log_file_path, silent=False): | ||
| """ | ||
| Configures the logging options. It must be called only once, before using get_logger. | ||
|
|
||
| def error(self, msg, **kwargs): | ||
| """ | ||
| Logs an ``ERROR`` level message to stdout and file. | ||
| Args: | ||
| log_file_path (:obj:`str`): the path and name of the log file. | ||
| silent (:obj:`str`): if True, only critical errors will be shown to console. | ||
|
|
||
| Args: | ||
| msg (:obj:`str`): the message to be logged. | ||
| kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output. | ||
| """ | ||
| Raises: | ||
| :obj:`RuntimeError` setup_logger had already been called. | ||
| """ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring: Unnecessary empty line at the end
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also some nits: space between parameter and type ( No need of parenthesis fort the Return and Raises
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
|
|
||
| self.f_logger.error(self._create_file_message(msg, **kwargs)) | ||
| self.c_logger.error(self._create_console_message(msg, **kwargs)) | ||
| global configured | ||
|
|
||
| if configured: | ||
| raise RuntimeError("Logging was already configured") | ||
|
|
||
| logging.config.dictConfig({ | ||
| "version": 1, | ||
| "disable_existing_loggers": False, | ||
| "formatters": { | ||
| "plain": { | ||
| "()": structlog.stdlib.ProcessorFormatter, | ||
| "processor": CustomLogRenderer(), | ||
| "foreign_pre_chain": pre_chain, | ||
| }, | ||
| }, | ||
| "handlers": { | ||
| "console": { | ||
| "level": "INFO" if not silent else "CRITICAL", | ||
| "class": "logging.StreamHandler", | ||
| "formatter": "plain", | ||
| }, | ||
| "file": { | ||
| "level": "DEBUG", | ||
| "class": "logging.handlers.WatchedFileHandler", | ||
| "filename": log_file_path, | ||
| "formatter": "plain", | ||
| }, | ||
| }, | ||
| "loggers": { | ||
| "": { | ||
| "handlers": ["console", "file"], | ||
| "level": "DEBUG", | ||
| "propagate": True, | ||
| }, | ||
| } | ||
| }) | ||
|
|
||
| structlog.configure( | ||
| processors=[ | ||
| structlog.stdlib.PositionalArgumentsFormatter(), | ||
| timestamper, | ||
| structlog.stdlib.ProcessorFormatter.wrap_for_formatter, | ||
| ], | ||
| context_class=dict, | ||
| logger_factory=structlog.stdlib.LoggerFactory(), | ||
| wrapper_class=structlog.stdlib.BoundLogger, | ||
| cache_logger_on_first_use=True, | ||
| ) | ||
|
|
||
| configured = True | ||
|
|
||
|
|
||
| def get_logger(component=None): | ||
| """ | ||
| Returns a logger, that has the given `component` in all future log entries. | ||
|
|
||
| def warning(self, msg, **kwargs): | ||
| """ | ||
| Logs a ``WARNING`` level message to stdout and file. | ||
| Returns: | ||
| a proxy obtained from structlog.get_logger with the `component` as bound variable. | ||
|
|
||
| Args: | ||
| msg (:obj:`str`): the message to be logged. | ||
| kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output. | ||
| """ | ||
| Args: | ||
| component(:obj:`str`): the name of the "component" field that will be attached to all the logs issued by this logger. | ||
| """ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring: Unnecessary empty line at the end. The return is also missing.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard to say exactly what's the return value, as it's inherited from structlog. Added a short description without the type. |
||
| return structlog.get_logger(component=component) | ||
|
|
||
| self.f_logger.warning(self._create_file_message(msg, **kwargs)) | ||
| self.c_logger.warning(self._create_console_message(msg, **kwargs)) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,4 +6,5 @@ coincurve | |
| pyzbase32 | ||
| requests | ||
| plyvel | ||
| readerwriterlock | ||
| readerwriterlock | ||
| structlog | ||
Uh oh!
There was an error while loading. Please reload this page.