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
|
||||
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
|
||||
|
||||
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',
|
||||
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',
|
||||
),
|
||||
"version": Arg(
|
||||
|
@ -1,9 +1,12 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from logging import Formatter
|
||||
from logging.handlers import RotatingFileHandler, SysLogHandler
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from freqtrade import OperationalException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -36,10 +39,38 @@ def setup_logging(config: Dict[str, Any]) -> None:
|
||||
# Log to stderr
|
||||
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stderr)]
|
||||
|
||||
if config.get('logfile'):
|
||||
log_handlers.append(RotatingFileHandler(config['logfile'],
|
||||
maxBytes=1024 * 1024, # 1Mb
|
||||
backupCount=10))
|
||||
logfile = config.get('logfile')
|
||||
if logfile:
|
||||
s = logfile.split(':')
|
||||
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(
|
||||
level=logging.INFO if verbosity < 1 else logging.DEBUG,
|
||||
|
@ -1387,7 +1387,7 @@ def import_fails() -> None:
|
||||
realimport = builtins.__import__
|
||||
|
||||
def mockedimport(name, *args, **kwargs):
|
||||
if name in ["filelock"]:
|
||||
if name in ["filelock", 'systemd.journal']:
|
||||
raise ImportError(f"No module named '{name}'")
|
||||
return realimport(name, *args, **kwargs)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import warnings
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
@ -19,7 +20,7 @@ from freqtrade.configuration.deprecated_settings import (
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.load_config import load_config_file
|
||||
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 tests.conftest import (log_has, log_has_re,
|
||||
patched_configuration_load_config_file)
|
||||
@ -638,6 +639,56 @@ def test_set_loggers() -> None:
|
||||
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):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user