Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/68410.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add support for minion_id in log formats

Adds support for including `%(minion_id)s` in log formats. Where id is available log messages on the master will have that data added to allow easier correlation of messages to minions.
31 changes: 29 additions & 2 deletions doc/ref/configuration/logging/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ formatting matches those used in :py:func:`time.strftime`.

Default: ``[%(levelname)-8s] %(message)s``

Recommended: ``[%(levelname)-8s]%(jid)s%(minion_id)s %(message)s``

The format of the console logging messages. All standard python logging
:py:class:`~logging.LogRecord` attributes can be used. Salt also provides these
custom LogRecord attributes to colorize console log output:
Expand All @@ -205,13 +207,20 @@ custom LogRecord attributes to colorize console log output:

log_fmt_console: '[%(levelname)-8s] %(message)s'

.. note::

It is recommended to include ``%(jid)s`` and ``%(minion_id)s`` in the log
format to identify messages that relate to specific jobs and minions.

.. conf_log:: log_fmt_logfile

``log_fmt_logfile``
-------------------

Default: ``%(asctime)s,%(msecs)03d [%(name)-17s][%(levelname)-8s] %(message)s``

Recommended: ``%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(levelname)-8s][%(process)d]%(jid)s%(minion_id)s %(message)s``

The format of the log file logging messages. All standard python logging
:py:class:`~logging.LogRecord` attributes can be used. Salt also provides
these custom LogRecord attributes that include padding and enclosing brackets
Expand All @@ -227,6 +236,11 @@ these custom LogRecord attributes that include padding and enclosing brackets

log_fmt_logfile: '%(asctime)s,%(msecs)03d [%(name)-17s][%(levelname)-8s] %(message)s'

.. note::

It is recommended to include ``%(jid)s`` and ``%(minion_id)s`` in the log
format to identify messages that relate to specific jobs and minions.

.. conf_log:: log_granular_levels

``log_granular_levels``
Expand All @@ -245,12 +259,11 @@ at the ``debug`` level, and sets a custom module to the ``all`` level:
'salt.modules': 'debug'
'salt.loader.saltmaster.ext.module.custom_module': 'all'

.. conf_log:: log_fmt_jid

You can determine what log call name to use here by adding ``%(module)s`` to the
log format. Typically, it is the path of the file which generates the log
without the trailing ``.py`` and with path separators replaced with ``.``

.. conf_log:: log_fmt_jid

``log_fmt_jid``
-------------------
Expand All @@ -263,6 +276,20 @@ The format of the JID when added to logging messages.

log_fmt_jid: '[JID: %(jid)s]'

.. conf_log:: log_fmt_minion_id

``log_fmt_minion_id``
----------------------

Default: ``[%(minion_id)s]``

The format of the minion ID when added to logging messages.

.. code-block:: yaml

log_fmt_minion_id: '[%(minion_id)s]'


External Logging Handlers
-------------------------

Expand Down
1 change: 1 addition & 0 deletions salt/_logging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DFLT_LOG_FMT_CONSOLE,
DFLT_LOG_FMT_JID,
DFLT_LOG_FMT_LOGFILE,
DFLT_LOG_FMT_MINION_ID,
LOG_COLORS,
LOG_LEVELS,
LOG_VALUES_TO_LEVELS,
Expand Down
33 changes: 27 additions & 6 deletions salt/_logging/impl.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
salt._logging.impl
~~~~~~~~~~~~~~~~~~
salt._logging.impl
~~~~~~~~~~~~~~~~~~

Salt's logging implementation classes/functionality
Salt's logging implementation classes/functionality
"""

import atexit
Expand Down Expand Up @@ -100,6 +100,7 @@

# Default logging formatting options
DFLT_LOG_FMT_JID = "[JID: %(jid)s]"
DFLT_LOG_FMT_MINION_ID = "[%(minion_id)s]"
DFLT_LOG_DATEFMT = "%H:%M:%S"
DFLT_LOG_DATEFMT_LOGFILE = "%Y-%m-%d %H:%M:%S"
DFLT_LOG_FMT_CONSOLE = "[%(levelname)-8s] %(message)s"
Expand Down Expand Up @@ -247,21 +248,35 @@ def _log(
if extra is None:
extra = {}

current_jid = (
salt.utils.ctx.get_request_context().get("data", {}).get("jid", None)
)
current_jid = salt.utils.ctx.get_request_context().get("data", {}).get("jid")
log_fmt_jid = (
salt.utils.ctx.get_request_context()
.get("opts", {})
.get("log_fmt_jid", None)
)

current_minion_id = (
salt.utils.ctx.get_request_context().get("data", {}).get("id")
)

log_fmt_minion_id = (
salt.utils.ctx.get_request_context()
.get("opts", {})
.get("log_fmt_minion_id")
)

if current_jid is not None:
extra["jid"] = current_jid

if log_fmt_jid is not None:
extra["log_fmt_jid"] = log_fmt_jid

if current_minion_id is not None:
extra["minion_id"] = current_minion_id

if log_fmt_minion_id is not None:
extra["log_fmt_minion_id"] = log_fmt_minion_id

# If both exc_info and exc_info_on_loglevel are both passed, let's fail
if exc_info and exc_info_on_loglevel:
raise LoggingRuntimeError(
Expand Down Expand Up @@ -336,6 +351,11 @@ def makeRecord(
log_fmt_jid = extra.pop("log_fmt_jid")
jid = log_fmt_jid % {"jid": jid}

minion_id = extra.pop("minion_id", "")
if minion_id:
log_fmt_minion_id = extra.pop("log_fmt_minion_id")
minion_id = log_fmt_minion_id % {"minion_id": minion_id}

if not extra:
# If nothing else is in extra, make it None
extra = None
Expand Down Expand Up @@ -385,6 +405,7 @@ def makeRecord(

logrecord.exc_info_on_loglevel = exc_info_on_loglevel
logrecord.jid = jid
logrecord.minion_id = minion_id
return logrecord


Expand Down
8 changes: 8 additions & 0 deletions salt/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
DFLT_LOG_FMT_CONSOLE,
DFLT_LOG_FMT_JID,
DFLT_LOG_FMT_LOGFILE,
DFLT_LOG_FMT_MINION_ID,
)

try:
Expand Down Expand Up @@ -350,6 +351,10 @@ def _gather_buffer_space():
"log_fmt_console": str,
# The format for a given log file
"log_fmt_logfile": (tuple, str),
# The format for JIDs prior to formatting into log lines as %(jid)s
"log_fmt_jid": (type(None), str),
# The format for minion_ids prior to formatting into log lines as %(minion_id)s
"log_fmt_minion_id": (type(None), str),
# A dictionary of logging levels
"log_granular_levels": dict,
# The maximum number of bytes a single log file may contain before
Expand Down Expand Up @@ -1205,6 +1210,7 @@ def _gather_buffer_space():
"log_fmt_console": DFLT_LOG_FMT_CONSOLE,
"log_fmt_logfile": DFLT_LOG_FMT_LOGFILE,
"log_fmt_jid": DFLT_LOG_FMT_JID,
"log_fmt_minion_id": DFLT_LOG_FMT_MINION_ID,
"log_granular_levels": {},
"log_rotate_max_bytes": 0,
"log_rotate_backup_count": 0,
Expand Down Expand Up @@ -1538,6 +1544,7 @@ def _gather_buffer_space():
"log_fmt_console": DFLT_LOG_FMT_CONSOLE,
"log_fmt_logfile": DFLT_LOG_FMT_LOGFILE,
"log_fmt_jid": DFLT_LOG_FMT_JID,
"log_fmt_minion_id": DFLT_LOG_FMT_MINION_ID,
"log_granular_levels": {},
"log_rotate_max_bytes": 0,
"log_rotate_backup_count": 0,
Expand Down Expand Up @@ -1750,6 +1757,7 @@ def _gather_buffer_space():
"log_fmt_console": DFLT_LOG_FMT_CONSOLE,
"log_fmt_logfile": DFLT_LOG_FMT_LOGFILE,
"log_fmt_jid": DFLT_LOG_FMT_JID,
"log_fmt_minion_id": DFLT_LOG_FMT_MINION_ID,
"log_granular_levels": {},
"log_rotate_max_bytes": 0,
"log_rotate_backup_count": 0,
Expand Down
123 changes: 93 additions & 30 deletions tests/pytests/integration/_logging/test_jid_logging.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,95 @@
import logging
import pathlib

import pytest
from saltfactories.utils import random_string

from salt._logging import DFLT_LOG_FMT_JID
from tests.support.helpers import PRE_PYTEST_SKIP

# Using the PRE_PYTEST_SKIP decorator since this test still fails on some platforms.
# Will investigate later.


@PRE_PYTEST_SKIP
def test_jid_in_logs(caplog, salt_call_cli):
"""
Test JID in log_format
"""
jid_formatted_str = DFLT_LOG_FMT_JID.split("%", maxsplit=1)[0]
formatter = logging.Formatter(fmt="%(jid)s %(message)s")
with caplog.at_level(logging.DEBUG):
previous_formatter = caplog.handler.formatter
try:
caplog.handler.setFormatter(formatter)
ret = salt_call_cli.run("test.ping")
assert ret.returncode == 0
assert ret.data is True

assert_error_msg = (
"'{}' not found in log messages:\n>>>>>>>>>{}\n<<<<<<<<<".format(
jid_formatted_str, caplog.text
)
)
assert jid_formatted_str in caplog.text, assert_error_msg
finally:
caplog.handler.setFormatter(previous_formatter)
from salt._logging.impl import DFLT_LOG_FMT_MINION_ID


@pytest.fixture(scope="module")
def log_field_marker():
"""
Marker to make identifying log fields possible without risk of matching
other instances of jid or minion_id in the log messages
"""
return "EXTRA_LOG_FIELD:"


@pytest.fixture(scope="module")
def logging_master(salt_master_factory, log_field_marker):
"""
A logging master fixture with JID and minion_id in log format
"""
log_format = (
f"{log_field_marker}%(jid)s {log_field_marker}%(minion_id)s %(message)s"
)
config_overrides = {
"log_level_logfile": "debug",
"log_fmt_logfile": log_format,
}
logging_master_factory = salt_master_factory.salt_master_daemon(
random_string("master-logging-"),
overrides=config_overrides,
extra_cli_arguments_after_first_start_failure=["--log-level=info"],
)
with logging_master_factory.started():
yield logging_master_factory


@pytest.fixture(scope="module")
def logging_master_logfile(logging_master):
"""
The logging master log file path
"""
assert logging_master.is_running()
return pathlib.Path(logging_master.config["log_file"])


@pytest.fixture(scope="module")
def salt_cli(logging_master):
"""
A ``salt``` CLI fixture
"""
assert logging_master.is_running()
return logging_master.salt_cli(timeout=30)


@pytest.fixture(scope="module")
def logging_minion_id(logging_master):
"""
Random minion id for a salt-minion fixture
"""
return random_string("minion-logging-")


@pytest.fixture
def logging_minion(logging_master, logging_minion_id):
"""
A running salt-minion fixture connected to the logging master
"""
assert logging_master.is_running()
salt_minion_factory = logging_master.salt_minion_daemon(
logging_minion_id,
)
with salt_minion_factory.started():
yield salt_minion_factory


def test_jid_minion_id_in_logs(
logging_master_logfile, log_field_marker, salt_cli, logging_minion
):
"""
Test JID and minion_id appear in master log file in the expected format
"""
ret = salt_cli.run("test.ping", "-v", minion_tgt=logging_minion.id)
assert ret.returncode == 0
assert "Executing job with jid" in ret.stdout

jid_str = DFLT_LOG_FMT_JID % {"jid": ret.stdout.splitlines()[0].split()[-1]}
minion_id_str = DFLT_LOG_FMT_MINION_ID % {"minion_id": logging_minion.id}

log_file_text = logging_master_logfile.read_text()

assert f"{log_field_marker}{jid_str}" in log_file_text
assert f"{log_field_marker}{minion_id_str}" in log_file_text