Skip to content
This repository was archived by the owner on Sep 26, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a595055
Added structlog and config; removed setup_logging from common.tools (…
bigspider Jul 6, 2020
46418ea
Better docs
bigspider Jul 6, 2020
6b8af2e
Added custom Renderer for logs; removed unused log processors
bigspider Jul 6, 2020
ce109d1
Removed wrong import
bigspider Jul 6, 2020
94e1959
Removed wrong unused processors; added separator for the key-value pa…
bigspider Jul 6, 2020
4eb7f76
Fix typos
bigspider Jul 7, 2020
ebb157a
Added structlog as requirements for teos_cli
bigspider Jul 7, 2020
59ec971
Addressing some comments from the PR; changed "actor" to "component"
bigspider Jul 10, 2020
e30b92f
Moved logger to class member wherever possible
bigspider Jul 10, 2020
ae108a1
Fixed missing import after rebase
bigspider Jul 10, 2020
57ff453
Added tests for CustomLogRenderer
bigspider Jul 10, 2020
56adb9b
Update error message as per PR review
bigspider Jul 10, 2020
d2d2d32
Improvements from PR review
bigspider Jul 13, 2020
4bc8a31
Fix typo in Gatekeeper's docstring
bigspider Jul 13, 2020
e260149
Add commas and parentheses to extra arguments in console logs
bigspider Jul 13, 2020
72f4d4b
Fix typo in docs
bigspider Jul 13, 2020
28033cd
Added test for get_logger
bigspider Jul 13, 2020
6d93a57
common - fix formatting issues
sr-gi Jul 13, 2020
728ae3d
test - fix formatting issues
sr-gi Jul 13, 2020
d54397e
Filter out logs that are not from teos
bigspider Jul 13, 2020
e2bbf0e
teos - removes unnecesary log setup in API after d54397e297faad46e4b0…
sr-gi Jul 13, 2020
0cf7ce1
common + test - changes tab for two spaces for logger key_value_part
sr-gi Jul 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

DATA_DIR = os.path.expanduser("~/.teos_cli/")
CONF_FILE_NAME = "teos_cli.conf"
LOG_PREFIX = "cli"

# Load config fields
DEFAULT_CONF = {
Expand Down
1 change: 1 addition & 0 deletions cli/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
cryptography
requests
structlog
10 changes: 5 additions & 5 deletions cli/teos_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL

from cli.exceptions import TowerResponseError
from cli import DEFAULT_CONF, DATA_DIR, CONF_FILE_NAME, LOG_PREFIX
from cli import DEFAULT_CONF, DATA_DIR, CONF_FILE_NAME
from cli.help import show_usage, help_add_appointment, help_get_appointment, help_register, help_get_all_appointments

from common import constants
from common.logger import Logger
from common.logger import get_logger, setup_logging
import common.receipts as receipts
from common.appointment import Appointment
from common.config_loader import ConfigLoader
from common.cryptographer import Cryptographer
from common.tools import setup_logging, setup_data_folder
from common.tools import setup_data_folder
from common.exceptions import InvalidKey, InvalidParameter, SignatureError
from common.tools import is_256b_hex_str, is_locator, compute_locator, is_compressed_pk

logger = Logger(actor="Client", log_name_prefix=LOG_PREFIX)
logger = get_logger(component="Client")


def register(user_id, teos_id, teos_url):
Expand Down Expand Up @@ -426,7 +426,7 @@ def main(command, args, command_line_conf):
config = config_loader.build_config()

setup_data_folder(DATA_DIR)
setup_logging(config.get("LOG_FILE"), LOG_PREFIX)
setup_logging(config.get("LOG_FILE"))

# Set the teos url
teos_url = "{}:{}".format(config.get("API_CONNECT"), config.get("API_PORT"))
Expand Down
175 changes: 101 additions & 74 deletions common/logger.py
Original file line number Diff line number Diff line change
@@ -1,96 +1,123 @@
import json
import logging
from datetime import datetime
import logging.config
Comment thread
sr-gi marked this conversation as resolved.
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 _add_prefix(self, msg):
return msg if self.actor is None else "[{}]: {}".format(self.actor, msg)

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"])

# 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"])

# Remove the extra 2 characters (space and comma) and add all data to the final message.
message += " ({})".format(params[:-2])
def _repr(self, val):
"""Returns the representation of *val* if it's not a ``str``."""
return val if isinstance(val, str) else repr(val)

return message
def __call__(self, _, __, event_dict):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstrings missing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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()

@staticmethod
def _create_file_message(msg, **kwargs):
return json.dumps(_StructuredMessage(msg, **kwargs).to_dict())
ts = event_dict.pop("timestamp", None)
if ts:
sio.write(str(ts) + " ")

def info(self, msg, **kwargs):
"""
Logs an ``INFO`` level message to stdout and file.
component = event_dict.pop("component", None)
if component:
sio.write("[" + component + "] ")

Args:
msg (:obj:`str`): the message to be logged.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
event = self._repr(event_dict.pop("event"))

self.f_logger.info(self._create_file_message(msg, **kwargs))
self.c_logger.info(self._create_console_message(msg, **kwargs))
sio.write(event)

def debug(self, msg, **kwargs):
"""
Logs a ``DEBUG`` level message to stdout and file.
# Represent all the key=value elements still in event_dict
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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, ...)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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(" (" + key_value_part + ")")

Args:
msg (:obj:`str`): the message to be logged.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
return sio.getvalue()

self.f_logger.debug(self._create_file_message(msg, **kwargs))
self.c_logger.debug(self._create_console_message(msg, **kwargs))

def error(self, msg, **kwargs):
"""
Logs an ``ERROR`` level message to stdout and file.
def setup_logging(log_file_path, silent=False):
"""
Configures the logging options. It must be called only once, before using get_logger.

Args:
msg (:obj:`str`): the message to be logged.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
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.

self.f_logger.error(self._create_file_message(msg, **kwargs))
self.c_logger.error(self._create_console_message(msg, **kwargs))
Raises:
:obj:`RuntimeError` setup_logger had already been called.
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring: Unnecessary empty line at the end

Copy link
Copy Markdown
Member

@sr-gi sr-gi Jul 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also some nits: space between parameter and type (param (:obj: ...))

No need of parenthesis fort the Return and Raises

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


def warning(self, msg, **kwargs):
"""
Logs a ``WARNING`` level message to stdout and file.
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,
}
},
"filters": { # filter out logs that do not come from teos
"onlyteos": {"()": logging.Filter, "name": "teos"}
},
"handlers": {
"console": {
"level": "INFO" if not silent else "CRITICAL",
"class": "logging.StreamHandler",
"formatter": "plain",
"filters": ["onlyteos"],
},
"file": {
"level": "DEBUG",
"class": "logging.handlers.WatchedFileHandler",
"filename": log_file_path,
"formatter": "plain",
"filters": ["onlyteos"],
},
},
"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.

Args:
msg (:obj:`str`): the message to be logged.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
Returns:
a proxy obtained from structlog.get_logger with the `component` as bound variable.

self.f_logger.warning(self._create_file_message(msg, **kwargs))
self.c_logger.warning(self._create_console_message(msg, **kwargs))
Args:
component(:obj:`str`): the value of the "component" field that will be attached to all the logs issued by this
logger.
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring: Unnecessary empty line at the end. The return is also missing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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("teos", component=component)
42 changes: 0 additions & 42 deletions common/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,45 +78,3 @@ def setup_data_folder(data_folder):
"""

Path(data_folder).mkdir(parents=True, exist_ok=True)


def setup_logging(log_file_path, log_name_prefix):
"""
Setups a couple of loggers (console and file) given a prefix and a file path.

The log names are:

prefix | _file_log
prefix | _console_log

Args:
log_file_path (:obj:`str`): the path of the file to output the file log.
log_name_prefix (:obj:`str`): the prefix to identify the log.
"""

if not isinstance(log_file_path, str):
print(log_file_path)
raise ValueError("Wrong log file path")

if not isinstance(log_name_prefix, str):
raise ValueError("Wrong log file name")

# Create the file logger
f_logger = logging.getLogger("{}_file_log".format(log_name_prefix))
f_logger.setLevel(logging.DEBUG)

fh = logging.FileHandler(log_file_path)
fh.setLevel(logging.DEBUG)
fh_formatter = logging.Formatter("%(message)s")
fh.setFormatter(fh_formatter)
f_logger.addHandler(fh)

# Create the console logger
c_logger = logging.getLogger("{}_console_log".format(log_name_prefix))
c_logger.setLevel(logging.INFO)

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch_formatter = logging.Formatter("%(message)s.", "%Y-%m-%d %H:%M:%S")
ch.setFormatter(ch_formatter)
c_logger.addHandler(ch)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ coincurve
pyzbase32
requests
plyvel
readerwriterlock
readerwriterlock
structlog
1 change: 0 additions & 1 deletion teos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

DATA_DIR = os.path.expanduser("~/.teos/")
CONF_FILE_NAME = "teos.conf"
LOG_PREFIX = "teos"

# Default conf fields
DEFAULT_CONF = {
Expand Down
Loading