Merge pull request #2418 from hroff-1902/logging-syslog
Add logging to syslog and journald
This commit is contained in:
commit
6b142d716f
@ -34,3 +34,59 @@ as the watchdog.
|
|||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a Docker container.
|
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a Docker container.
|
||||||
|
|
||||||
|
## Advanced Logging
|
||||||
|
|
||||||
|
On many Linux systems the bot can be configured to send its log messages to `syslog` or `journald` system services. Logging to a remote `syslog` server is also available on Windows. The special values for the `--logfilename` command line option can be used for this.
|
||||||
|
|
||||||
|
### Logging to syslog
|
||||||
|
|
||||||
|
To send Freqtrade log messages to a local or remote `syslog` service use the `--logfilename` command line option with the value in the following format:
|
||||||
|
|
||||||
|
* `--logfilename syslog:<syslog_address>` -- send log messages to `syslog` service using the `<syslog_address>` as the syslog address.
|
||||||
|
|
||||||
|
The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character.
|
||||||
|
|
||||||
|
So, the following are the examples of possible usages:
|
||||||
|
|
||||||
|
* `--logfilename syslog:/dev/log` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems.
|
||||||
|
* `--logfilename syslog` -- same as above, the shortcut for `/dev/log`.
|
||||||
|
* `--logfilename syslog:/var/run/syslog` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS.
|
||||||
|
* `--logfilename syslog:localhost:514` -- log to local syslog using UDP socket, if it listens on port 514.
|
||||||
|
* `--logfilename syslog:<ip>:514` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server.
|
||||||
|
|
||||||
|
Log messages are send to `syslog` with the `user` facility. So you can see them with the following commands:
|
||||||
|
|
||||||
|
* `tail -f /var/log/user`, or
|
||||||
|
* install a comprehensive graphical viewer (for instance, 'Log File Viewer' for Ubuntu).
|
||||||
|
|
||||||
|
On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfilename syslog` or `--logfilename journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better.
|
||||||
|
|
||||||
|
For `rsyslog` the messages from the bot can be redirected into a separate dedicated log file. To achieve this, add
|
||||||
|
```
|
||||||
|
if $programname startswith "freqtrade" then -/var/log/freqtrade.log
|
||||||
|
```
|
||||||
|
to one of the rsyslog configuration files, for example at the end of the `/etc/rsyslog.d/50-default.conf`.
|
||||||
|
|
||||||
|
For `syslog` (`rsyslog`), the reduction mode can be switched on. This will reduce the number of repeating messages. For instance, multiple bot Heartbeat messages will be reduced to a single message when nothing else happens with the bot. To achieve this, set in `/etc/rsyslog.conf`:
|
||||||
|
```
|
||||||
|
# Filter duplicated messages
|
||||||
|
$RepeatedMsgReduction on
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging to journald
|
||||||
|
|
||||||
|
This needs the `systemd` python package installed as the dependency, which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows.
|
||||||
|
|
||||||
|
To send Freqtrade log messages to `journald` system service use the `--logfilename` command line option with the value in the following format:
|
||||||
|
|
||||||
|
* `--logfilename journald` -- send log messages to `journald`.
|
||||||
|
|
||||||
|
Log messages are send to `journald` with the `user` facility. So you can see them with the following commands:
|
||||||
|
|
||||||
|
* `journalctl -f` -- shows Freqtrade log messages sent to `journald` along with other log messages fetched by `journald`.
|
||||||
|
* `journalctl -f -u freqtrade.service` -- this command can be used when the bot is run as a `systemd` service.
|
||||||
|
|
||||||
|
There are many other options in the `journalctl` utility to filter the messages, see manual pages for this utility.
|
||||||
|
|
||||||
|
On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfilename syslog` or `--logfilename journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better.
|
||||||
|
@ -201,7 +201,7 @@ freqtrade trade -c config.json
|
|||||||
|
|
||||||
#### 7. (Optional) Post-installation Tasks
|
#### 7. (Optional) Post-installation Tasks
|
||||||
|
|
||||||
On Linux, as an optional post-installation task, you can setup the bot to run as a `systemd` service. See [Advanced Post-installation Tasks](advanced-setup.md) for details.
|
On Linux, as an optional post-installation task, you may wish to setup the bot to run as a `systemd` service or configure it to send the log messages to the `syslog`/`rsyslog` or `journald` daemons. See [Advanced Logging](advanced-setup.md#advanced-logging) for details.
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
),
|
),
|
||||||
"logfile": Arg(
|
"logfile": Arg(
|
||||||
'--logfile',
|
'--logfile',
|
||||||
help='Log to the file specified.',
|
help="Log to the file specified. Special values are: 'syslog', 'journald'. "
|
||||||
|
"See the documentation for more details.",
|
||||||
metavar='FILE',
|
metavar='FILE',
|
||||||
),
|
),
|
||||||
"version": Arg(
|
"version": Arg(
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging import Formatter
|
||||||
|
from logging.handlers import RotatingFileHandler, SysLogHandler
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,10 +39,38 @@ def setup_logging(config: Dict[str, Any]) -> None:
|
|||||||
# Log to stderr
|
# Log to stderr
|
||||||
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stderr)]
|
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stderr)]
|
||||||
|
|
||||||
if config.get('logfile'):
|
logfile = config.get('logfile')
|
||||||
log_handlers.append(RotatingFileHandler(config['logfile'],
|
if logfile:
|
||||||
maxBytes=1024 * 1024, # 1Mb
|
s = logfile.split(':')
|
||||||
backupCount=10))
|
if s[0] == 'syslog':
|
||||||
|
# Address can be either a string (socket filename) for Unix domain socket or
|
||||||
|
# a tuple (hostname, port) for UDP socket.
|
||||||
|
# Address can be omitted (i.e. simple 'syslog' used as the value of
|
||||||
|
# config['logfilename']), which defaults to '/dev/log', applicable for most
|
||||||
|
# of the systems.
|
||||||
|
address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else '/dev/log'
|
||||||
|
handler = SysLogHandler(address=address)
|
||||||
|
# No datetime field for logging into syslog, to allow syslog
|
||||||
|
# to perform reduction of repeating messages if this is set in the
|
||||||
|
# syslog config. The messages should be equal for this.
|
||||||
|
handler.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
|
||||||
|
log_handlers.append(handler)
|
||||||
|
elif s[0] == 'journald':
|
||||||
|
try:
|
||||||
|
from systemd.journal import JournaldLogHandler
|
||||||
|
except ImportError:
|
||||||
|
raise OperationalException("You need the systemd python package be installed in "
|
||||||
|
"order to use logging to journald.")
|
||||||
|
handler = JournaldLogHandler()
|
||||||
|
# No datetime field for logging into journald, to allow syslog
|
||||||
|
# to perform reduction of repeating messages if this is set in the
|
||||||
|
# syslog config. The messages should be equal for this.
|
||||||
|
handler.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
|
||||||
|
log_handlers.append(handler)
|
||||||
|
else:
|
||||||
|
log_handlers.append(RotatingFileHandler(logfile,
|
||||||
|
maxBytes=1024 * 1024, # 1Mb
|
||||||
|
backupCount=10))
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO if verbosity < 1 else logging.DEBUG,
|
level=logging.INFO if verbosity < 1 else logging.DEBUG,
|
||||||
|
@ -1387,7 +1387,7 @@ def import_fails() -> None:
|
|||||||
realimport = builtins.__import__
|
realimport = builtins.__import__
|
||||||
|
|
||||||
def mockedimport(name, *args, **kwargs):
|
def mockedimport(name, *args, **kwargs):
|
||||||
if name in ["filelock"]:
|
if name in ["filelock", 'systemd.journal']:
|
||||||
raise ImportError(f"No module named '{name}'")
|
raise ImportError(f"No module named '{name}'")
|
||||||
return realimport(name, *args, **kwargs)
|
return realimport(name, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -19,7 +20,7 @@ from freqtrade.configuration.deprecated_settings import (
|
|||||||
process_temporary_deprecated_settings)
|
process_temporary_deprecated_settings)
|
||||||
from freqtrade.configuration.load_config import load_config_file
|
from freqtrade.configuration.load_config import load_config_file
|
||||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
||||||
from freqtrade.loggers import _set_loggers
|
from freqtrade.loggers import _set_loggers, setup_logging
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from tests.conftest import (log_has, log_has_re,
|
from tests.conftest import (log_has, log_has_re,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
@ -638,6 +639,56 @@ def test_set_loggers() -> None:
|
|||||||
assert logging.getLogger('telegram').level is logging.INFO
|
assert logging.getLogger('telegram').level is logging.INFO
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||||
|
def test_set_loggers_syslog(mocker):
|
||||||
|
logger = logging.getLogger()
|
||||||
|
orig_handlers = logger.handlers
|
||||||
|
logger.handlers = []
|
||||||
|
|
||||||
|
config = {'verbosity': 2,
|
||||||
|
'logfile': 'syslog:/dev/log',
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_logging(config)
|
||||||
|
assert len(logger.handlers) == 2
|
||||||
|
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||||
|
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||||
|
# reset handlers to not break pytest
|
||||||
|
logger.handlers = orig_handlers
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.")
|
||||||
|
def test_set_loggers_journald(mocker):
|
||||||
|
logger = logging.getLogger()
|
||||||
|
orig_handlers = logger.handlers
|
||||||
|
logger.handlers = []
|
||||||
|
|
||||||
|
config = {'verbosity': 2,
|
||||||
|
'logfile': 'journald',
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_logging(config)
|
||||||
|
assert len(logger.handlers) == 2
|
||||||
|
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
|
||||||
|
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||||
|
# reset handlers to not break pytest
|
||||||
|
logger.handlers = orig_handlers
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_loggers_journald_importerror(mocker, import_fails):
|
||||||
|
logger = logging.getLogger()
|
||||||
|
orig_handlers = logger.handlers
|
||||||
|
logger.handlers = []
|
||||||
|
|
||||||
|
config = {'verbosity': 2,
|
||||||
|
'logfile': 'journald',
|
||||||
|
}
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r'You need the systemd python package.*'):
|
||||||
|
setup_logging(config)
|
||||||
|
logger.handlers = orig_handlers
|
||||||
|
|
||||||
|
|
||||||
def test_set_logfile(default_conf, mocker):
|
def test_set_logfile(default_conf, mocker):
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user