68 changed files with 683 additions and 565 deletions
@ -1,137 +1,118 @@ |
|||||||
import logging, datetime, sys, pathlib, os |
# Copyright (C) 2019 The Electrum developers |
||||||
from . import ELECTRUM_VERSION |
# 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) |
|
||||||
|
|
||||||
|
|
||||||
class ISO8601UTCTimeFormatter(logging.Formatter): |
|
||||||
converter = datetime.datetime.fromtimestamp |
|
||||||
|
|
||||||
def formatTime(self, record, datefmt=None): |
import logging |
||||||
current_time = self.converter(record.created).astimezone(datetime.timezone.utc) |
import datetime |
||||||
if not datefmt: |
import sys |
||||||
datefmt = "%Y%m%dT%H%M%S.%fZ" |
import pathlib |
||||||
return current_time.strftime(datefmt) |
import os |
||||||
|
import platform |
||||||
|
from typing import Optional |
||||||
class ExceptionTracebackSquasherFormatter(logging.Formatter): |
|
||||||
def __init__(self, *args, **kwargs): |
|
||||||
super().__init__(*args, **kwargs) |
|
||||||
|
|
||||||
def formatException(self, ei): |
|
||||||
return '' |
|
||||||
|
|
||||||
|
|
||||||
# house multiple formatters as one |
class LogFormatter(logging.Formatter): |
||||||
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) |
|
||||||
|
|
||||||
|
def formatTime(self, record, datefmt=None): |
||||||
|
# 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 date.strftime(datefmt) |
||||||
|
|
||||||
# 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) |
# 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 = 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 = logging.StreamHandler(sys.stderr) |
||||||
console_stderr_handler.setFormatter(console_formatter) |
console_stderr_handler.setFormatter(console_formatter) |
||||||
console_stderr_handler.setLevel(logging.WARNING) |
console_stderr_handler.setLevel(logging.WARNING) |
||||||
root_logger.addHandler(console_stderr_handler) |
root_logger.addHandler(console_stderr_handler) |
||||||
|
|
||||||
# creates a logger specifically for electrum library |
# 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)) |
|
||||||
|
|
||||||
|
_logfile_path = None |
||||||
def configure_file_logging(log_directory): |
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) |
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]) |
timestamp = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") |
||||||
new_log_file = log_directory / f"electrum_{ELECTRUM_VERSION}_{timestamp}.log" |
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.setFormatter(file_formatter) |
||||||
file_handler.setLevel(logging.DEBUG) |
file_handler.setLevel(logging.DEBUG) |
||||||
root_logger.addHandler(file_handler) |
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): |
def configure_logging(config): |
||||||
log_directory = pathlib.Path(config.path) / "logs" |
if config.get('verbosity'): |
||||||
|
console_stderr_handler.setLevel(logging.DEBUG) |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
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)}") |
||||||
|
|
||||||
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) |
|
||||||
|
|
||||||
configure_file_logging(log_directory) |
def get_logfile_path() -> Optional[pathlib.Path]: |
||||||
|
return _logfile_path |
||||||
|
|||||||
Loading…
Reference in new issue