1
0

logging: basics

This commit is contained in:
SomberNight
2019-04-26 18:52:26 +02:00
parent 4d64e132d7
commit 3385a94753
68 changed files with 681 additions and 563 deletions

View File

@@ -1,137 +1,118 @@
import logging, datetime, sys, pathlib, os
from . import ELECTRUM_VERSION
# Copyright (C) 2019 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
# How it works:
# *enable logs* (but not stdout < warning )
# *configures* logs >= warning to goto to stderr
# *THEN* config is loaded
# *configures* logging to files using config location
# *IF* verbosity -v is turned on, enable console logs < warning
# Why:
# Enable logs as soon as possible, before config is loaded, as it could
# report information on config / electrum location operations.
# You need config to know where to put file logs.
# Enable stdout logs, if verbosity enabled (also in config)
# This implementation is easy to refactor.
# Why this formatting?:
# "%(asctime)22s | %(levelname)-4s | %(name)s.%(module)s.%(lineno)s | %(message)s"
# UTC ISO8601 timestamp
# LEVEL
# Python Module . Line Number - Super easy to locate things.
# and of course, the message
# USAGE:
# initialization:
# import electrum.logging
# electrum.logging.configure_logging(config)
#
# logging:
# from electrum.logging import Logger
# class Thing(Logger):
# def __init__(*args, **kwargs):
# Logger(self, *args, **kargs)
#
# from electrum.logging import electrum_logger
# electrum_logger.info("Sup")
#
# # include exception traceback in FILE
# electrum_logger.info("Yo. exc_info=True)
import logging
import datetime
import sys
import pathlib
import os
import platform
from typing import Optional
class ISO8601UTCTimeFormatter(logging.Formatter):
converter = datetime.datetime.fromtimestamp
class LogFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
current_time = self.converter(record.created).astimezone(datetime.timezone.utc)
# timestamps follow ISO 8601 UTC
date = datetime.datetime.fromtimestamp(record.created).astimezone(datetime.timezone.utc)
if not datefmt:
datefmt = "%Y%m%dT%H%M%S.%fZ"
return current_time.strftime(datefmt)
return date.strftime(datefmt)
class ExceptionTracebackSquasherFormatter(logging.Formatter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def formatException(self, ei):
return ''
# house multiple formatters as one
class ElectrumConsoleLogFormatter(ISO8601UTCTimeFormatter, ExceptionTracebackSquasherFormatter):
def __init__(self, *args, **kwargs):
ISO8601UTCTimeFormatter.__init__(self, *args, **kwargs)
ExceptionTracebackSquasherFormatter.__init__(self, *args, **kwargs)
class ElectrumFileLogFormatter(ISO8601UTCTimeFormatter):
def __init__(self, *args, **kwargs):
ISO8601UTCTimeFormatter.__init__(self, *args, **kwargs)
# errors won't go to stdout
class StdoutErrorFilter(logging.Filter):
def filter(self, record):
return record.levelno <= logging.INFO
LOG_FORMAT = "%(asctime)22s | %(levelname)8s | %(name)s | %(message)s"
console_formatter = LogFormatter(fmt=LOG_FORMAT)
file_formatter = LogFormatter(fmt=LOG_FORMAT)
# enable logs universally (including for other libraries)
log_format = "%(asctime)22s | %(levelname)8s | %(name)s.%(module)s.%(lineno)s | %(message)s"
date_format = "%Y%m%dT%H%M%S.%fZ"
console_formatter = ElectrumConsoleLogFormatter(fmt=log_format, datefmt=date_format)
file_formatter = ElectrumFileLogFormatter(fmt=log_format, datefmt=date_format)
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.setLevel(logging.WARNING)
# log errors to console (stderr)
# log to stderr; by default only WARNING and higher
console_stderr_handler = logging.StreamHandler(sys.stderr)
console_stderr_handler.setFormatter(console_formatter)
console_stderr_handler.setLevel(logging.WARNING)
root_logger.addHandler(console_stderr_handler)
# creates a logger specifically for electrum library
electrum_logger = logging.getLogger("Electrum")
electrum_logger = logging.getLogger("electrum")
electrum_logger.setLevel(logging.DEBUG)
class Logger:
def __init__(self):
self.log = electrum_logger
def _delete_old_logs(path, keep=10):
files = sorted(list(pathlib.Path(path).glob("electrum_log_*.log")), reverse=True)
for f in files[keep:]:
os.remove(str(f))
def delete_old_logs(path, keep=10):
files = list(pathlib.Path(path).glob("elecrum_*_*.log"))
if len(files) >= keep:
for f in files[keep:]:
os.remove(str(f))
def configure_file_logging(log_directory):
_logfile_path = None
def _configure_file_logging(log_directory: pathlib.Path):
global _logfile_path
assert _logfile_path is None, 'file logging already initialized'
log_directory.mkdir(exist_ok=True)
delete_old_logs(log_directory, 10)
_delete_old_logs(log_directory)
timestamp = datetime.datetime.utcnow().strftime(date_format[:13])
new_log_file = log_directory / f"electrum_{ELECTRUM_VERSION}_{timestamp}.log"
timestamp = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
PID = os.getpid()
_logfile_path = log_directory / f"electrum_log_{timestamp}_{PID}.log"
file_handler = logging.FileHandler(new_log_file)
file_handler = logging.FileHandler(_logfile_path)
file_handler.setFormatter(file_formatter)
file_handler.setLevel(logging.DEBUG)
root_logger.addHandler(file_handler)
electrum_logger.info(f"Electrum - {ELECTRUM_VERSION} - Electrum Technologies GmbH - https://electrum.org")
electrum_logger.info(f"Log: {new_log_file}")
# --- External API
def get_logger(name: str) -> logging.Logger:
if name.startswith("electrum."):
name = name[9:]
return electrum_logger.getChild(name)
_logger = get_logger(__name__)
class Logger:
def __init__(self):
self.logger = self.__get_logger_for_obj()
def __get_logger_for_obj(self) -> logging.Logger:
cls = self.__class__
if cls.__module__:
name = f"{cls.__module__}.{cls.__name__}"
else:
name = cls.__name__
try:
diag_name = self.diagnostic_name()
except Exception as e:
raise Exception("diagnostic name not yet available?") from e
if diag_name:
name += f".[{diag_name}]"
return get_logger(name)
def diagnostic_name(self):
return ''
def configure_logging(config):
log_directory = pathlib.Path(config.path) / "logs"
if config.get('verbosity'):
console_stderr_handler.setLevel(logging.DEBUG)
if config.cmdline_options['verbosity']:
# log to console
console_handler = logging.StreamHandler()
console_handler.setFormatter(console_formatter)
console_handler.addFilter(StdoutErrorFilter())
root_logger.addHandler(console_handler)
is_android = 'ANDROID_DATA' in os.environ
if is_android or config.get('disablefilelogging'):
pass # disable file logging
else:
log_directory = pathlib.Path(config.path) / "logs"
_configure_file_logging(log_directory)
configure_file_logging(log_directory)
from . import ELECTRUM_VERSION
_logger.info(f"Electrum version: {ELECTRUM_VERSION} - https://electrum.org - https://github.com/spesmilo/electrum")
_logger.info(f"Python version: {sys.version}. On platform: {platform.platform()}")
_logger.info(f"Logging to file: {str(_logfile_path)}")
def get_logfile_path() -> Optional[pathlib.Path]:
return _logfile_path