2019-04-26 18:52:26 +02:00
|
|
|
# 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
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
import logging
|
2021-04-14 07:01:23 +02:00
|
|
|
import logging.handlers
|
2019-04-26 18:52:26 +02:00
|
|
|
import datetime
|
|
|
|
|
import sys
|
|
|
|
|
import pathlib
|
|
|
|
|
import os
|
|
|
|
|
import platform
|
2022-06-12 00:54:35 +02:00
|
|
|
from typing import Optional, TYPE_CHECKING
|
2019-05-02 16:05:26 +02:00
|
|
|
import copy
|
2021-03-10 16:23:49 +01:00
|
|
|
import subprocess
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2022-06-12 00:54:35 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from .simple_config import SimpleConfig
|
|
|
|
|
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-04-27 03:42:31 +02:00
|
|
|
class LogFormatterForFiles(logging.Formatter):
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
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)
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-05-02 16:05:26 +02:00
|
|
|
def format(self, record):
|
|
|
|
|
record = _shorten_name_of_logrecord(record)
|
|
|
|
|
return super().format(record)
|
|
|
|
|
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-04-27 03:42:31 +02:00
|
|
|
file_formatter = LogFormatterForFiles(fmt="%(asctime)22s | %(levelname)8s | %(name)s | %(message)s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LogFormatterForConsole(logging.Formatter):
|
|
|
|
|
|
2022-08-24 11:42:55 +00:00
|
|
|
def formatTime(self, record, datefmt=None):
|
2022-11-06 06:22:50 +00:00
|
|
|
t = record.relativeCreated / 1000
|
2022-08-24 11:42:55 +00:00
|
|
|
return f"{t:6.2f}"
|
|
|
|
|
|
2019-04-27 03:42:31 +02:00
|
|
|
def format(self, record):
|
2022-08-24 11:42:55 +00:00
|
|
|
record = copy.copy(record) # avoid mutating arg
|
2019-05-02 16:05:26 +02:00
|
|
|
record = _shorten_name_of_logrecord(record)
|
2022-08-24 11:42:55 +00:00
|
|
|
if shortcut := getattr(record, 'custom_shortcut', None):
|
|
|
|
|
record.name = f"{shortcut}/{record.name}"
|
2019-05-07 21:07:18 +02:00
|
|
|
text = super().format(record)
|
|
|
|
|
return text
|
2019-04-27 03:42:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# try to make console log lines short... no timestamp, short levelname, no "electrum."
|
2022-08-24 11:42:55 +00:00
|
|
|
console_formatter = LogFormatterForConsole(fmt="%(asctime)s | %(levelname).1s | %(name)s | %(message)s")
|
2019-04-27 03:42:31 +02:00
|
|
|
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-05-02 16:05:26 +02:00
|
|
|
def _shorten_name_of_logrecord(record: logging.LogRecord) -> logging.LogRecord:
|
|
|
|
|
record = copy.copy(record) # avoid mutating arg
|
|
|
|
|
# strip the main module name from the logger name
|
|
|
|
|
if record.name.startswith("electrum."):
|
|
|
|
|
record.name = record.name[9:]
|
|
|
|
|
# manual map to shorten common module names
|
|
|
|
|
record.name = record.name.replace("interface.Interface", "interface", 1)
|
|
|
|
|
record.name = record.name.replace("network.Network", "network", 1)
|
|
|
|
|
record.name = record.name.replace("synchronizer.Synchronizer", "synchronizer", 1)
|
|
|
|
|
record.name = record.name.replace("verifier.SPV", "verifier", 1)
|
|
|
|
|
record.name = record.name.replace("gui.qt.main_window.ElectrumWindow", "gui.qt.main_window", 1)
|
|
|
|
|
return record
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:01:23 +02:00
|
|
|
class TruncatingMemoryHandler(logging.handlers.MemoryHandler):
|
|
|
|
|
"""An in-memory log handler that only keeps the first N log messages
|
|
|
|
|
and discards the rest.
|
|
|
|
|
"""
|
|
|
|
|
target: Optional['logging.Handler']
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
logging.handlers.MemoryHandler.__init__(
|
|
|
|
|
self,
|
|
|
|
|
capacity=1, # note: this is the flushing frequency, ~unused by us
|
|
|
|
|
flushLevel=logging.DEBUG,
|
|
|
|
|
)
|
|
|
|
|
self.max_size = 100 # max num of messages we keep
|
|
|
|
|
self.num_messages_seen = 0
|
|
|
|
|
self.__never_dumped = True
|
|
|
|
|
|
|
|
|
|
# note: this flush implementation *keeps* the buffer as-is, instead of clearing it
|
|
|
|
|
def flush(self):
|
|
|
|
|
self.acquire()
|
|
|
|
|
try:
|
|
|
|
|
if self.target:
|
|
|
|
|
for record in self.buffer:
|
|
|
|
|
if record.levelno >= self.target.level:
|
|
|
|
|
self.target.handle(record)
|
|
|
|
|
finally:
|
|
|
|
|
self.release()
|
|
|
|
|
|
|
|
|
|
def dump_to_target(self, target: 'logging.Handler'):
|
|
|
|
|
self.acquire()
|
|
|
|
|
try:
|
|
|
|
|
self.setTarget(target)
|
|
|
|
|
self.flush()
|
|
|
|
|
self.setTarget(None)
|
|
|
|
|
finally:
|
|
|
|
|
self.__never_dumped = False
|
|
|
|
|
self.release()
|
|
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
|
self.num_messages_seen += 1
|
|
|
|
|
if len(self.buffer) < self.max_size:
|
|
|
|
|
super().emit(record)
|
|
|
|
|
|
|
|
|
|
def close(self) -> None:
|
|
|
|
|
# Check if captured log lines were never to dumped to e.g. stderr,
|
|
|
|
|
# and if so, try to do it now. This is useful e.g. in case of sys.exit().
|
|
|
|
|
if self.__never_dumped:
|
|
|
|
|
_configure_stderr_logging()
|
|
|
|
|
super().close()
|
|
|
|
|
|
|
|
|
|
|
2023-09-06 16:55:04 +00:00
|
|
|
def _delete_old_logs(path, *, num_files_keep: int):
|
2019-04-26 18:52:26 +02:00
|
|
|
files = sorted(list(pathlib.Path(path).glob("electrum_log_*.log")), reverse=True)
|
2023-09-06 16:55:04 +00:00
|
|
|
for f in files[num_files_keep:]:
|
logging: handle "cannot delete old logfile" error
E.g. on Windows, files open in one process cannot be deleted by another process.
With file logging enabled, if an old logfile was open in a text editor,
Electrum could crash during startup.
```
E | __main__ |
Traceback (most recent call last):
File "...\electrum\run_electrum", line 391, in main
handle_cmd(
File "...\electrum\run_electrum", line 403, in handle_cmd
configure_logging(config)
File "...\electrum\electrum\logging.py", line 278, in configure_logging
_configure_file_logging(log_directory)
File "...\electrum\electrum\logging.py", line 107, in _configure_file_logging
_delete_old_logs(log_directory)
File "...\electrum\electrum\logging.py", line 98, in _delete_old_logs
os.remove(str(f))
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: '...\\AppData\\Roaming\\Electrum\\testnet\\logs\\electrum_log_20210414T023751Z_25008.log'
```
2021-04-14 05:10:54 +02:00
|
|
|
try:
|
|
|
|
|
os.remove(str(f))
|
|
|
|
|
except OSError as e:
|
|
|
|
|
_logger.warning(f"cannot delete old logfile: {e}")
|
2019-03-14 14:56:22 -07:00
|
|
|
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
_logfile_path = None
|
2023-09-06 16:55:04 +00:00
|
|
|
def _configure_file_logging(log_directory: pathlib.Path, *, num_files_keep: int):
|
2024-10-07 18:50:26 +00:00
|
|
|
from .util import os_chmod
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
global _logfile_path
|
|
|
|
|
assert _logfile_path is None, 'file logging already initialized'
|
2024-10-07 18:50:26 +00:00
|
|
|
log_directory.mkdir(exist_ok=True, mode=0o700)
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2023-09-06 16:55:04 +00:00
|
|
|
_delete_old_logs(log_directory, num_files_keep=num_files_keep)
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2023-12-24 08:57:57 +00:00
|
|
|
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
2019-04-26 18:52:26 +02:00
|
|
|
PID = os.getpid()
|
|
|
|
|
_logfile_path = log_directory / f"electrum_log_{timestamp}_{PID}.log"
|
2024-10-07 18:50:26 +00:00
|
|
|
# we create the file with restrictive perms, instead of letting FileHandler create it
|
|
|
|
|
with open(_logfile_path, "w+") as f:
|
|
|
|
|
os_chmod(_logfile_path, 0o600)
|
2019-03-14 14:56:22 -07:00
|
|
|
|
logging: make sure file logging uses utf8 encoding
--- Logging error ---
Traceback (most recent call last):
File "...\Python38\lib\logging\__init__.py", line 1084, in emit
stream.write(msg + self.terminator)
File "...\Python38\lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\u26a1' in position 80: character maps to <undefined>
Call stack:
File ".../electrum/run_electrum", line 466, in <module>
main()
File ".../electrum/run_electrum", line 384, in main
handle_cmd(
File ".../electrum/run_electrum", line 402, in handle_cmd
d.run_gui(config, plugins)
File "...\electrum\electrum\daemon.py", line 572, in run_gui
self.gui_object.main()
File "...\electrum\electrum\gui\qt\__init__.py", line 391, in main
self.app.exec_()
File "...\electrum\electrum\gui\qt\channels_list.py", line 308, in new_channel_with_warning
self.new_channel_dialog()
File "...\electrum\electrum\gui\qt\channels_list.py", line 390, in new_channel_dialog
if not d.exec_():
File "...\electrum\electrum\gui\qt\channels_list.py", line 358, in on_suggest
nodeid = bh2u(lnworker.lnrater.suggest_peer() or b'')
File "...\electrum\electrum\lnrater.py", line 257, in suggest_peer
return self.suggest_node_channel_open()[0]
File "...\electrum\electrum\lnrater.py", line 248, in suggest_node_channel_open
self.logger.info(
Message: 'node rating for Bottlepay⚡:\nNodeStats(number_channels=20, total_capacity_msat=167455866000, median_capacity_msat=8460000000.0, mean_capacity_msat=8372793300.0, node_age_block_height=71003, mean_channel_age_block_height=48581.39999999991, blocks_since_last_channel=507, mean_fee_rate=1e-06) (score 0.5034595626052799)'
Arguments: ()
2020-11-16 14:50:22 +01:00
|
|
|
file_handler = logging.FileHandler(_logfile_path, encoding='utf-8')
|
2019-03-14 14:56:22 -07:00
|
|
|
file_handler.setFormatter(file_formatter)
|
|
|
|
|
file_handler.setLevel(logging.DEBUG)
|
|
|
|
|
root_logger.addHandler(file_handler)
|
2021-04-14 07:01:23 +02:00
|
|
|
if _inmemory_startup_logs:
|
|
|
|
|
_inmemory_startup_logs.dump_to_target(file_handler)
|
2019-03-14 14:56:22 -07:00
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
|
2021-04-14 07:01:23 +02:00
|
|
|
console_stderr_handler = None
|
|
|
|
|
def _configure_stderr_logging(*, verbosity=None, verbosity_shortcuts=None):
|
|
|
|
|
# log to stderr; by default only WARNING and higher
|
|
|
|
|
global console_stderr_handler
|
|
|
|
|
if console_stderr_handler is not None:
|
|
|
|
|
_logger.warning("stderr handler already exists")
|
2019-04-26 20:45:23 +02:00
|
|
|
return
|
2021-04-14 07:01:23 +02:00
|
|
|
console_stderr_handler = logging.StreamHandler(sys.stderr)
|
|
|
|
|
console_stderr_handler.setFormatter(console_formatter)
|
|
|
|
|
if not verbosity and not verbosity_shortcuts:
|
|
|
|
|
console_stderr_handler.setLevel(logging.WARNING)
|
|
|
|
|
root_logger.addHandler(console_stderr_handler)
|
|
|
|
|
else:
|
|
|
|
|
console_stderr_handler.setLevel(logging.DEBUG)
|
|
|
|
|
root_logger.addHandler(console_stderr_handler)
|
|
|
|
|
_process_verbosity_log_levels(verbosity)
|
|
|
|
|
_process_verbosity_filter_shortcuts(verbosity_shortcuts, handler=console_stderr_handler)
|
|
|
|
|
if _inmemory_startup_logs:
|
|
|
|
|
_inmemory_startup_logs.dump_to_target(console_stderr_handler)
|
2019-05-07 21:07:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _process_verbosity_log_levels(verbosity):
|
2019-04-26 20:45:23 +02:00
|
|
|
if verbosity == '*' or not isinstance(verbosity, str):
|
|
|
|
|
return
|
|
|
|
|
# example verbosity:
|
|
|
|
|
# debug,network=error,interface=error // effectively blacklists network and interface
|
|
|
|
|
# warning,network=debug,interface=debug // effectively whitelists network and interface
|
|
|
|
|
filters = verbosity.split(',')
|
|
|
|
|
for filt in filters:
|
|
|
|
|
if not filt: continue
|
|
|
|
|
items = filt.split('=')
|
|
|
|
|
if len(items) == 1:
|
|
|
|
|
level = items[0]
|
|
|
|
|
electrum_logger.setLevel(level.upper())
|
|
|
|
|
elif len(items) == 2:
|
|
|
|
|
logger_name, level = items
|
|
|
|
|
logger = get_logger(logger_name)
|
|
|
|
|
logger.setLevel(level.upper())
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(f"invalid log filter: {filt}")
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 07:01:23 +02:00
|
|
|
def _process_verbosity_filter_shortcuts(verbosity_shortcuts, *, handler: 'logging.Handler'):
|
2019-05-07 21:07:18 +02:00
|
|
|
if not isinstance(verbosity_shortcuts, str):
|
|
|
|
|
return
|
|
|
|
|
if len(verbosity_shortcuts) < 1:
|
|
|
|
|
return
|
|
|
|
|
# depending on first character being '^', either blacklist or whitelist
|
|
|
|
|
is_blacklist = verbosity_shortcuts[0] == '^'
|
|
|
|
|
if is_blacklist:
|
|
|
|
|
filters = verbosity_shortcuts[1:]
|
|
|
|
|
else: # whitelist
|
|
|
|
|
filters = verbosity_shortcuts[0:]
|
|
|
|
|
filt = ShortcutFilteringFilter(is_blacklist=is_blacklist, filters=filters)
|
|
|
|
|
# apply filter directly (and only!) on stderr handler
|
|
|
|
|
# note that applying on one of the root loggers directly would not work,
|
|
|
|
|
# see https://docs.python.org/3/howto/logging.html#logging-flow
|
2021-04-14 07:01:23 +02:00
|
|
|
handler.addFilter(filt)
|
2019-05-07 21:07:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ShortcutInjectingFilter(logging.Filter):
|
|
|
|
|
|
|
|
|
|
def __init__(self, *, shortcut: Optional[str]):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.__shortcut = shortcut
|
|
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
|
record.custom_shortcut = self.__shortcut
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ShortcutFilteringFilter(logging.Filter):
|
|
|
|
|
|
|
|
|
|
def __init__(self, *, is_blacklist: bool, filters: str):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.__is_blacklist = is_blacklist
|
|
|
|
|
self.__filters = filters
|
|
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
|
# all errors are let through
|
|
|
|
|
if record.levelno >= logging.ERROR:
|
|
|
|
|
return True
|
|
|
|
|
# the logging module itself is let through
|
|
|
|
|
if record.name == __name__:
|
|
|
|
|
return True
|
|
|
|
|
# do filtering
|
|
|
|
|
shortcut = getattr(record, 'custom_shortcut', None)
|
|
|
|
|
if self.__is_blacklist:
|
|
|
|
|
if shortcut is None:
|
|
|
|
|
return True
|
|
|
|
|
if shortcut in self.__filters:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
else: # whitelist
|
|
|
|
|
if shortcut is None:
|
|
|
|
|
return False
|
|
|
|
|
if shortcut in self.__filters:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2021-04-14 08:12:50 +02:00
|
|
|
# enable logs universally (including for other libraries)
|
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
|
root_logger.setLevel(logging.WARNING)
|
|
|
|
|
|
2021-04-14 07:01:23 +02:00
|
|
|
# Start collecting log messages now, into an in-memory buffer. This buffer is only
|
|
|
|
|
# used until the proper log handlers are fully configured, including their verbosity,
|
|
|
|
|
# at which point we will dump its contents into those, and remove this log handler.
|
|
|
|
|
# Note: this is set up at import-time instead of e.g. as part of a function that is
|
|
|
|
|
# called from run_electrum (the main script). This is to have this run as early
|
|
|
|
|
# as possible.
|
|
|
|
|
# Note: some users might use Electrum as a python library and not use run_electrum,
|
|
|
|
|
# in which case these logs might never get redirected or cleaned up.
|
|
|
|
|
# Also, the python docs recommend libraries not to set a handler, to
|
|
|
|
|
# avoid interfering with the user's logging.
|
|
|
|
|
_inmemory_startup_logs = None
|
|
|
|
|
if getattr(sys, "_ELECTRUM_RUNNING_VIA_RUNELECTRUM", False):
|
|
|
|
|
_inmemory_startup_logs = TruncatingMemoryHandler()
|
|
|
|
|
root_logger.addHandler(_inmemory_startup_logs)
|
2021-04-14 08:12:50 +02:00
|
|
|
|
|
|
|
|
# creates a logger specifically for electrum library
|
|
|
|
|
electrum_logger = logging.getLogger("electrum")
|
|
|
|
|
electrum_logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
# --- 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__)
|
2019-04-26 20:45:23 +02:00
|
|
|
_logger.setLevel(logging.INFO)
|
2019-04-26 18:52:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Logger:
|
2019-05-07 21:07:18 +02:00
|
|
|
|
|
|
|
|
# Single character short "name" for this class.
|
|
|
|
|
# Can be used for filtering log lines. Does not need to be unique.
|
|
|
|
|
LOGGING_SHORTCUT = None # type: Optional[str]
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
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}]"
|
2019-05-07 21:07:18 +02:00
|
|
|
logger = get_logger(name)
|
|
|
|
|
if self.LOGGING_SHORTCUT:
|
|
|
|
|
logger.addFilter(ShortcutInjectingFilter(shortcut=self.LOGGING_SHORTCUT))
|
|
|
|
|
return logger
|
2019-04-26 18:52:26 +02:00
|
|
|
|
|
|
|
|
def diagnostic_name(self):
|
|
|
|
|
return ''
|
2019-03-14 14:56:22 -07:00
|
|
|
|
|
|
|
|
|
2022-06-12 00:54:35 +02:00
|
|
|
def configure_logging(config: 'SimpleConfig', *, log_to_file: Optional[bool] = None) -> None:
|
2022-07-07 19:03:06 +02:00
|
|
|
from .util import is_android_debug_apk
|
|
|
|
|
|
2019-05-07 21:07:18 +02:00
|
|
|
verbosity = config.get('verbosity')
|
|
|
|
|
verbosity_shortcuts = config.get('verbosity_shortcuts')
|
2023-05-24 17:41:44 +00:00
|
|
|
if not verbosity and config.GUI_ENABLE_DEBUG_LOGS:
|
2022-11-04 02:40:04 +00:00
|
|
|
verbosity = '*'
|
2021-04-14 07:01:23 +02:00
|
|
|
_configure_stderr_logging(verbosity=verbosity, verbosity_shortcuts=verbosity_shortcuts)
|
2019-04-26 18:52:26 +02:00
|
|
|
|
2022-06-12 00:54:35 +02:00
|
|
|
if log_to_file is None:
|
2023-05-24 17:41:44 +00:00
|
|
|
log_to_file = config.WRITE_LOGS_TO_DISK
|
2022-07-07 19:03:06 +02:00
|
|
|
log_to_file |= is_android_debug_apk()
|
2020-06-19 01:52:21 +02:00
|
|
|
if log_to_file:
|
2019-04-26 18:52:26 +02:00
|
|
|
log_directory = pathlib.Path(config.path) / "logs"
|
2023-09-06 16:55:04 +00:00
|
|
|
num_files_keep = config.LOGS_NUM_FILES_KEEP
|
|
|
|
|
_configure_file_logging(log_directory, num_files_keep=num_files_keep)
|
2019-04-26 18:52:26 +02:00
|
|
|
|
2021-04-14 07:01:23 +02:00
|
|
|
# clean up and delete in-memory logs
|
|
|
|
|
global _inmemory_startup_logs
|
|
|
|
|
if _inmemory_startup_logs:
|
|
|
|
|
num_discarded = _inmemory_startup_logs.num_messages_seen - _inmemory_startup_logs.max_size
|
|
|
|
|
if num_discarded > 0:
|
|
|
|
|
_logger.warning(f"Too many log messages! Some have been discarded. "
|
|
|
|
|
f"(discarded {num_discarded} messages)")
|
|
|
|
|
_inmemory_startup_logs.close()
|
|
|
|
|
root_logger.removeHandler(_inmemory_startup_logs)
|
|
|
|
|
_inmemory_startup_logs = None
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
from . import ELECTRUM_VERSION
|
2019-07-04 19:13:12 +02:00
|
|
|
from .constants import GIT_REPO_URL
|
|
|
|
|
_logger.info(f"Electrum version: {ELECTRUM_VERSION} - https://electrum.org - {GIT_REPO_URL}")
|
2019-05-06 19:10:29 +02:00
|
|
|
_logger.info(f"Python version: {sys.version}. On platform: {describe_os_version()}")
|
2019-04-26 18:52:26 +02:00
|
|
|
_logger.info(f"Logging to file: {str(_logfile_path)}")
|
2019-05-07 21:07:18 +02:00
|
|
|
_logger.info(f"Log filters: verbosity {repr(verbosity)}, verbosity_shortcuts {repr(verbosity_shortcuts)}")
|
2019-03-14 14:56:22 -07:00
|
|
|
|
|
|
|
|
|
2019-04-26 18:52:26 +02:00
|
|
|
def get_logfile_path() -> Optional[pathlib.Path]:
|
|
|
|
|
return _logfile_path
|
2019-05-06 19:10:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def describe_os_version() -> str:
|
|
|
|
|
if 'ANDROID_DATA' in os.environ:
|
|
|
|
|
import jnius
|
|
|
|
|
bv = jnius.autoclass('android.os.Build$VERSION')
|
|
|
|
|
b = jnius.autoclass('android.os.Build')
|
|
|
|
|
return "Android {} on {} {} ({})".format(bv.RELEASE, b.BRAND, b.DEVICE, b.DISPLAY)
|
|
|
|
|
else:
|
|
|
|
|
return platform.platform()
|
2021-03-10 16:23:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_git_version() -> Optional[str]:
|
|
|
|
|
dir = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
try:
|
|
|
|
|
version = subprocess.check_output(
|
|
|
|
|
['git', 'describe', '--always', '--dirty'], cwd=dir)
|
|
|
|
|
version = str(version, "utf8").strip()
|
|
|
|
|
except Exception:
|
|
|
|
|
version = None
|
|
|
|
|
return version
|