Implement /logs endpoints in telegram and restAPI

This commit is contained in:
Matthias 2020-08-14 15:44:36 +02:00
parent b989ba0f82
commit 5f79caa307
5 changed files with 70 additions and 4 deletions

View File

@ -1,7 +1,7 @@
import logging import logging
import queue import queue
from logging import Formatter from logging import Formatter
from logging.handlers import RotatingFileHandler, SysLogHandler from logging.handlers import RotatingFileHandler, SysLogHandler, BufferingHandler
from typing import Any, Dict from typing import Any, Dict
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -10,6 +10,10 @@ logger = logging.getLogger(__name__)
log_queue = queue.Queue(-1) log_queue = queue.Queue(-1)
LOGFORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' LOGFORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
# Initialize bufferhandler - will be used for /log endpoints
bufferHandler = BufferingHandler(1000)
bufferHandler.setFormatter(Formatter(LOGFORMAT))
def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None:
""" """
@ -51,6 +55,7 @@ def setup_logging(config: Dict[str, Any]) -> None:
""" """
# Log level # Log level
verbosity = config['verbosity'] verbosity = config['verbosity']
logging.root.addHandler(bufferHandler)
logfile = config.get('logfile') logfile = config.get('logfile')
if logfile: if logfile:

View File

@ -186,6 +186,7 @@ class ApiServer(RPC):
self.app.add_url_rule(f'{BASE_URI}/count', 'count', view_func=self._count, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/count', 'count', view_func=self._count, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/daily', 'daily', view_func=self._daily, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/daily', 'daily', view_func=self._daily, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/edge', 'edge', view_func=self._edge, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/edge', 'edge', view_func=self._edge, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/logs', 'log', view_func=self._get_logs, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/profit', 'profit', self.app.add_url_rule(f'{BASE_URI}/profit', 'profit',
view_func=self._profit, methods=['GET']) view_func=self._profit, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/performance', 'performance', self.app.add_url_rule(f'{BASE_URI}/performance', 'performance',
@ -348,6 +349,18 @@ class ApiServer(RPC):
return self.rest_dump(stats) return self.rest_dump(stats)
@require_login
@rpc_catch_errors
def _get_logs(self):
"""
Returns latest logs
get:
param:
limit: Only get a certain number of records
"""
limit = int(request.args.get('limit', 0)) or None
return self.rest_dump(self._rpc_get_logs(limit))
@require_login @require_login
@rpc_catch_errors @rpc_catch_errors
def _edge(self): def _edge(self):

View File

@ -11,9 +11,9 @@ from typing import Any, Dict, List, Optional, Tuple, Union
import arrow import arrow
from numpy import NAN, mean from numpy import NAN, mean
from freqtrade.exceptions import (ExchangeError, from freqtrade.exceptions import ExchangeError, PricingError
PricingError)
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
from freqtrade.loggers import bufferHandler
from freqtrade.misc import shorten_date from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@ -633,6 +633,24 @@ class RPC:
} }
return res return res
def _rpc_get_logs(self, limit: Optional[int]) -> Dict[str, List]:
"""Returns the last X logs"""
if limit:
buffer = bufferHandler.buffer[-limit:]
else:
buffer = bufferHandler.buffer
records = [[r.asctime, r.created, r.name, r.levelname, r.message] for r in buffer]
return {'log_count': len(records), 'logs': records}
def _rpc_get_logs_as_string(self, limit: Optional[int]) -> Dict[str, List]:
"""Returns the last X logs"""
if limit:
buffer = bufferHandler.buffer[-limit:]
else:
buffer = bufferHandler.buffer
return [bufferHandler.format(r) for r in buffer]
def _rpc_edge(self) -> List[Dict[str, Any]]: def _rpc_edge(self) -> List[Dict[str, Any]]:
""" Returns information related to Edge """ """ Returns information related to Edge """
if not self._freqtrade.edge: if not self._freqtrade.edge:

View File

@ -103,6 +103,7 @@ class Telegram(RPC):
CommandHandler('stopbuy', self._stopbuy), CommandHandler('stopbuy', self._stopbuy),
CommandHandler('whitelist', self._whitelist), CommandHandler('whitelist', self._whitelist),
CommandHandler('blacklist', self._blacklist), CommandHandler('blacklist', self._blacklist),
CommandHandler('logs', self._logs),
CommandHandler('edge', self._edge), CommandHandler('edge', self._edge),
CommandHandler('help', self._help), CommandHandler('help', self._help),
CommandHandler('version', self._version), CommandHandler('version', self._version),
@ -637,6 +638,34 @@ class Telegram(RPC):
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
@authorized_only
def _logs(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /logs
Shows the latest logs
"""
try:
try:
limit = int(context.args[0])
except (TypeError, ValueError, IndexError):
limit = 10
logs = self._rpc_get_logs_as_string(limit)
msg = ''
message_container = "<pre>{}</pre>"
for logrec in logs:
if len(msg + logrec) + 10 >= MAX_TELEGRAM_MESSAGE_LENGTH:
# Send message immediately if it would become too long
self._send_msg(message_container.format(msg), parse_mode=ParseMode.HTML)
msg = logrec + '\n'
else:
# Append message to messages to send
msg += logrec + '\n'
if msg:
self._send_msg(message_container.format(msg), parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e))
@authorized_only @authorized_only
def _edge(self, update: Update, context: CallbackContext) -> None: def _edge(self, update: Update, context: CallbackContext) -> None:
""" """
@ -682,6 +711,7 @@ class Telegram(RPC):
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n"
"*/reload_config:* `Reload configuration file` \n" "*/reload_config:* `Reload configuration file` \n"
"*/show_config:* `Show running configuration` \n" "*/show_config:* `Show running configuration` \n"
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/whitelist:* `Show current whitelist` \n" "*/whitelist:* `Show current whitelist` \n"
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "
"to the blacklist.` \n" "to the blacklist.` \n"

View File

@ -76,7 +76,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', " "['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], " "'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]") "['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']]")
assert log_has(message_str, caplog) assert log_has(message_str, caplog)