mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Merge pull request #48660 from garethgreenaway/jids_in_logs
[develop] Adding jids in master & minion logs
This commit is contained in:
commit
8c5b369be0
@ -238,6 +238,19 @@ at the ``debug`` level, and sets a custom module to the ``all`` level:
|
|||||||
'salt.modules': 'debug'
|
'salt.modules': 'debug'
|
||||||
'salt.loader.saltmaster.ext.module.custom_module': 'all'
|
'salt.loader.saltmaster.ext.module.custom_module': 'all'
|
||||||
|
|
||||||
|
.. conf_log:: log_fmt_jid
|
||||||
|
|
||||||
|
``log_fmt_jid``
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Default: ``[JID: %(jid)s]``
|
||||||
|
|
||||||
|
The format of the JID when added to logging messages.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
log_fmt_jid: '[JID: %(jid)s]'
|
||||||
|
|
||||||
External Logging Handlers
|
External Logging Handlers
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -873,3 +873,25 @@ for viewing minions, runners, and jobs as well as running execution modules
|
|||||||
and runners of a running Salt system through a REST API that returns JSON.
|
and runners of a running Salt system through a REST API that returns JSON.
|
||||||
See Salt-API_ documentation.
|
See Salt-API_ documentation.
|
||||||
.. _Salt-API: https://docs.saltstack.com/en/latest/topics/netapi/index.html
|
.. _Salt-API: https://docs.saltstack.com/en/latest/topics/netapi/index.html
|
||||||
|
|
||||||
|
Logging Changes
|
||||||
|
===============
|
||||||
|
|
||||||
|
Include Job ID (JID) in Minion and Master Logs
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
The Job ID (JID) can now be optionally included in both the minion and master logs
|
||||||
|
by including ``jid`` in either the ``log_fmt_console`` or ``log_fmt_logfile``
|
||||||
|
configuration option:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
log_fmt_console: "[%(levelname)-8s] %(jid)s %(message)s"
|
||||||
|
|
||||||
|
The will cause the JID to be included in any log entries that are related to a
|
||||||
|
particular Salt job. The JID will be included using the default format,
|
||||||
|
``[JID: %(jid)s]`` but can be overriden with the ``log_fmt_jid`` configuration item.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
log_fmt_jid: "[JID: %(jid)s]"
|
||||||
|
@ -53,6 +53,7 @@ _DFLT_LOG_FMT_CONSOLE = '[%(levelname)-8s] %(message)s'
|
|||||||
_DFLT_LOG_FMT_LOGFILE = (
|
_DFLT_LOG_FMT_LOGFILE = (
|
||||||
'%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(levelname)-8s][%(process)d] %(message)s'
|
'%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(levelname)-8s][%(process)d] %(message)s'
|
||||||
)
|
)
|
||||||
|
_DFLT_LOG_FMT_JID = "[JID: %(jid)s]"
|
||||||
_DFLT_REFSPECS = ['+refs/heads/*:refs/remotes/origin/*', '+refs/tags/*:refs/tags/*']
|
_DFLT_REFSPECS = ['+refs/heads/*:refs/remotes/origin/*', '+refs/tags/*:refs/tags/*']
|
||||||
DEFAULT_INTERVAL = 60
|
DEFAULT_INTERVAL = 60
|
||||||
|
|
||||||
@ -1381,6 +1382,7 @@ DEFAULT_MINION_OPTS = {
|
|||||||
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
||||||
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
||||||
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
||||||
|
'log_fmt_jid': _DFLT_LOG_FMT_JID,
|
||||||
'log_granular_levels': {},
|
'log_granular_levels': {},
|
||||||
'log_rotate_max_bytes': 0,
|
'log_rotate_max_bytes': 0,
|
||||||
'log_rotate_backup_count': 0,
|
'log_rotate_backup_count': 0,
|
||||||
@ -1710,6 +1712,7 @@ DEFAULT_MASTER_OPTS = {
|
|||||||
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
||||||
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
||||||
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
||||||
|
'log_fmt_jid': _DFLT_LOG_FMT_JID,
|
||||||
'log_granular_levels': {},
|
'log_granular_levels': {},
|
||||||
'log_rotate_max_bytes': 0,
|
'log_rotate_max_bytes': 0,
|
||||||
'log_rotate_backup_count': 0,
|
'log_rotate_backup_count': 0,
|
||||||
@ -1904,6 +1907,7 @@ DEFAULT_CLOUD_OPTS = {
|
|||||||
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
'log_datefmt_logfile': _DFLT_LOG_DATEFMT_LOGFILE,
|
||||||
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
'log_fmt_console': _DFLT_LOG_FMT_CONSOLE,
|
||||||
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
'log_fmt_logfile': _DFLT_LOG_FMT_LOGFILE,
|
||||||
|
'log_fmt_jid': _DFLT_LOG_FMT_JID,
|
||||||
'log_granular_levels': {},
|
'log_granular_levels': {},
|
||||||
'log_rotate_max_bytes': 0,
|
'log_rotate_max_bytes': 0,
|
||||||
'log_rotate_backup_count': 0,
|
'log_rotate_backup_count': 0,
|
||||||
|
@ -48,6 +48,7 @@ from salt.log.handlers import (TemporaryLoggingHandler,
|
|||||||
QueueHandler)
|
QueueHandler)
|
||||||
from salt.log.mixins import LoggingMixInMeta, NewStyleClassMixIn
|
from salt.log.mixins import LoggingMixInMeta, NewStyleClassMixIn
|
||||||
|
|
||||||
|
from salt.utils.ctx import RequestContext
|
||||||
|
|
||||||
LOG_LEVELS = {
|
LOG_LEVELS = {
|
||||||
'all': logging.NOTSET,
|
'all': logging.NOTSET,
|
||||||
@ -305,6 +306,18 @@ class SaltLoggingClass(six.with_metaclass(LoggingMixInMeta, LOGGING_LOGGER_CLASS
|
|||||||
def _log(self, level, msg, args, exc_info=None, extra=None, # pylint: disable=arguments-differ
|
def _log(self, level, msg, args, exc_info=None, extra=None, # pylint: disable=arguments-differ
|
||||||
exc_info_on_loglevel=None):
|
exc_info_on_loglevel=None):
|
||||||
# If both exc_info and exc_info_on_loglevel are both passed, let's fail
|
# If both exc_info and exc_info_on_loglevel are both passed, let's fail
|
||||||
|
if extra is None:
|
||||||
|
extra = {}
|
||||||
|
|
||||||
|
current_jid = RequestContext.current.get('data', {}).get('jid', None)
|
||||||
|
log_fmt_jid = RequestContext.current.get('opts', {}).get('log_fmt_jid', None)
|
||||||
|
|
||||||
|
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 exc_info and exc_info_on_loglevel:
|
if exc_info and exc_info_on_loglevel:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
'Only one of \'exc_info\' and \'exc_info_on_loglevel\' is '
|
'Only one of \'exc_info\' and \'exc_info_on_loglevel\' is '
|
||||||
@ -335,6 +348,12 @@ class SaltLoggingClass(six.with_metaclass(LoggingMixInMeta, LOGGING_LOGGER_CLASS
|
|||||||
func=None, extra=None, sinfo=None):
|
func=None, extra=None, sinfo=None):
|
||||||
# Let's remove exc_info_on_loglevel from extra
|
# Let's remove exc_info_on_loglevel from extra
|
||||||
exc_info_on_loglevel = extra.pop('exc_info_on_loglevel')
|
exc_info_on_loglevel = extra.pop('exc_info_on_loglevel')
|
||||||
|
|
||||||
|
jid = extra.pop('jid', '')
|
||||||
|
if jid:
|
||||||
|
log_fmt_jid = extra.pop('log_fmt_jid')
|
||||||
|
jid = log_fmt_jid % {'jid': jid}
|
||||||
|
|
||||||
if not extra:
|
if not extra:
|
||||||
# If nothing else is in extra, make it None
|
# If nothing else is in extra, make it None
|
||||||
extra = None
|
extra = None
|
||||||
@ -393,6 +412,7 @@ class SaltLoggingClass(six.with_metaclass(LoggingMixInMeta, LOGGING_LOGGER_CLASS
|
|||||||
logrecord.exc_info_on_loglevel_formatted = None
|
logrecord.exc_info_on_loglevel_formatted = None
|
||||||
|
|
||||||
logrecord.exc_info_on_loglevel = exc_info_on_loglevel
|
logrecord.exc_info_on_loglevel = exc_info_on_loglevel
|
||||||
|
logrecord.jid = jid
|
||||||
return logrecord
|
return logrecord
|
||||||
|
|
||||||
# pylint: enable=C0103
|
# pylint: enable=C0103
|
||||||
|
@ -8,6 +8,7 @@ involves preparing the three listeners and the workers needed by the master.
|
|||||||
from __future__ import absolute_import, with_statement, print_function, unicode_literals
|
from __future__ import absolute_import, with_statement, print_function, unicode_literals
|
||||||
import copy
|
import copy
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -90,6 +91,9 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_HALITE = False
|
HAS_HALITE = False
|
||||||
|
|
||||||
|
from tornado.stack_context import StackContext
|
||||||
|
from salt.utils.ctx import RequestContext
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -1106,7 +1110,15 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
|
|||||||
if self.opts['master_stats']:
|
if self.opts['master_stats']:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.stats[cmd]['runs'] += 1
|
self.stats[cmd]['runs'] += 1
|
||||||
ret = self.aes_funcs.run_func(data['cmd'], data)
|
|
||||||
|
def run_func(data):
|
||||||
|
return self.aes_funcs.run_func(data['cmd'], data)
|
||||||
|
|
||||||
|
with StackContext(functools.partial(RequestContext,
|
||||||
|
{'data': data,
|
||||||
|
'opts': self.opts})):
|
||||||
|
ret = run_func(data)
|
||||||
|
|
||||||
if self.opts['master_stats']:
|
if self.opts['master_stats']:
|
||||||
self._post_stats(start, cmd)
|
self._post_stats(start, cmd)
|
||||||
return ret
|
return ret
|
||||||
|
@ -4,6 +4,7 @@ Routines to set up a minion
|
|||||||
'''
|
'''
|
||||||
# Import python libs
|
# Import python libs
|
||||||
from __future__ import absolute_import, print_function, with_statement, unicode_literals
|
from __future__ import absolute_import, print_function, with_statement, unicode_literals
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -33,6 +34,8 @@ else:
|
|||||||
from salt.ext.six.moves import range
|
from salt.ext.six.moves import range
|
||||||
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO
|
from salt.utils.zeromq import zmq, ZMQDefaultLoop, install_zmq, ZMQ_VERSION_INFO
|
||||||
|
|
||||||
|
from salt.utils.ctx import RequestContext
|
||||||
|
|
||||||
# pylint: enable=no-name-in-module,redefined-builtin
|
# pylint: enable=no-name-in-module,redefined-builtin
|
||||||
import tornado
|
import tornado
|
||||||
|
|
||||||
@ -1541,11 +1544,16 @@ class Minion(MinionBase):
|
|||||||
get_proc_dir(opts['cachedir'], uid=uid)
|
get_proc_dir(opts['cachedir'], uid=uid)
|
||||||
)
|
)
|
||||||
|
|
||||||
with tornado.stack_context.StackContext(minion_instance.ctx):
|
def run_func(minion_instance, opts, data):
|
||||||
if isinstance(data['fun'], tuple) or isinstance(data['fun'], list):
|
if isinstance(data['fun'], tuple) or isinstance(data['fun'], list):
|
||||||
Minion._thread_multi_return(minion_instance, opts, data)
|
return Minion._thread_multi_return(minion_instance, opts, data)
|
||||||
else:
|
else:
|
||||||
Minion._thread_return(minion_instance, opts, data)
|
return Minion._thread_return(minion_instance, opts, data)
|
||||||
|
|
||||||
|
with tornado.stack_context.StackContext(functools.partial(RequestContext,
|
||||||
|
{'data': data, 'opts': opts})):
|
||||||
|
with tornado.stack_context.StackContext(minion_instance.ctx):
|
||||||
|
run_func(minion_instance, opts, data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _thread_return(cls, minion_instance, opts, data):
|
def _thread_return(cls, minion_instance, opts, data):
|
||||||
|
52
salt/utils/ctx.py
Normal file
52
salt/utils/ctx.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Import python libs
|
||||||
|
from __future__ import absolute_import, with_statement, print_function, unicode_literals
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class ClassProperty(property):
|
||||||
|
'''
|
||||||
|
Use a classmethod as a property
|
||||||
|
http://stackoverflow.com/a/1383402/1258307
|
||||||
|
'''
|
||||||
|
def __get__(self, cls, owner):
|
||||||
|
return self.fget.__get__(None, owner)() # pylint: disable=no-member
|
||||||
|
|
||||||
|
|
||||||
|
class RequestContext(object):
|
||||||
|
'''
|
||||||
|
A context manager that saves some per-thread state globally.
|
||||||
|
Intended for use with Tornado's StackContext.
|
||||||
|
https://gist.github.com/simon-weber/7755289
|
||||||
|
Simply import this class into any module and access the current request handler by this
|
||||||
|
class's class method property 'current'. If it returns None, there's no active request.
|
||||||
|
.. code:: python
|
||||||
|
from raas.utils.ctx import RequestContext
|
||||||
|
current_request_handler = RequestContext.current
|
||||||
|
'''
|
||||||
|
|
||||||
|
_state = threading.local()
|
||||||
|
_state.current_request = {}
|
||||||
|
|
||||||
|
def __init__(self, current_request):
|
||||||
|
self._current_request = current_request
|
||||||
|
|
||||||
|
@ClassProperty
|
||||||
|
@classmethod
|
||||||
|
def current(cls):
|
||||||
|
if not hasattr(cls._state, 'current_request'):
|
||||||
|
return {}
|
||||||
|
return cls._state.current_request
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._prev_request = self.__class__.current
|
||||||
|
self.__class__._state.current_request = self._current_request
|
||||||
|
|
||||||
|
def __exit__(self, *exc):
|
||||||
|
self.__class__._state.current_request = self._prev_request
|
||||||
|
del self._prev_request
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return self
|
@ -5,7 +5,8 @@ Jinja-specific decorators
|
|||||||
from __future__ import absolute_import, print_function, unicode_literals
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
# Import Python libs
|
# Import Python libs
|
||||||
import logging
|
# Ensure we're using the custom logging from Salt
|
||||||
|
import salt.log.setup as logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
1
tests/integration/logging/__init__.py
Normal file
1
tests/integration/logging/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
34
tests/integration/logging/test_jid_logging.py
Normal file
34
tests/integration/logging/test_jid_logging.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Import Python libs
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
# Import Salt Testing libs
|
||||||
|
from tests.support.case import ModuleCase
|
||||||
|
from tests.support.unit import skipIf
|
||||||
|
from tests.support.helpers import TestsLoggingHandler
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import salt.ext.six as six
|
||||||
|
|
||||||
|
|
||||||
|
@skipIf(six.PY3, 'Runtest Log Hander Disabled for PY3, #41836')
|
||||||
|
class LoggingJIDsTest(ModuleCase):
|
||||||
|
'''
|
||||||
|
Validate that JIDs appear in LOGs
|
||||||
|
'''
|
||||||
|
def setUp(self):
|
||||||
|
'''
|
||||||
|
Set up
|
||||||
|
'''
|
||||||
|
log_format = '[%(levelname)-8s] %(jid)s %(message)s'
|
||||||
|
self.handler = TestsLoggingHandler(format=log_format,
|
||||||
|
level=logging.DEBUG)
|
||||||
|
|
||||||
|
def test_jid_in_logs(self):
|
||||||
|
'''
|
||||||
|
Test JID in log_format
|
||||||
|
'''
|
||||||
|
with self.handler:
|
||||||
|
self.run_function('test.ping')
|
||||||
|
assert any('JID' in s for s in self.handler.messages) is True, 'JID not found in log messages'
|
@ -182,6 +182,9 @@ TEST_SUITES = {
|
|||||||
'sdb':
|
'sdb':
|
||||||
{'display_name': 'Sdb',
|
{'display_name': 'Sdb',
|
||||||
'path': 'integration/sdb'},
|
'path': 'integration/sdb'},
|
||||||
|
'logging':
|
||||||
|
{'display_name': 'Logging',
|
||||||
|
'path': 'integration/logging'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -513,6 +516,13 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||||||
default=False,
|
default=False,
|
||||||
help='Run scheduler integration tests'
|
help='Run scheduler integration tests'
|
||||||
)
|
)
|
||||||
|
self.test_selection_group.add_option(
|
||||||
|
'--logging',
|
||||||
|
dest='logging',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Run logging integration tests'
|
||||||
|
)
|
||||||
|
|
||||||
def validate_options(self):
|
def validate_options(self):
|
||||||
if self.options.cloud_provider or self.options.external_api:
|
if self.options.cloud_provider or self.options.external_api:
|
||||||
|
Loading…
Reference in New Issue
Block a user