mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 01:18:58 +00:00
Allow the inclusion of exc_info
on a per handler basis.
This allows showing the `exc_info` if a specific log level or lower is enabled, but, on a per logging handler basis. For example, if the console hander is at info level and the file log handler is on debug level and we pass `exc_info_on_loglevel` to a log call, the traceback will be included in the log message of the file handler but not on the log message of the console handler. Closes #14870 Closes #14859
This commit is contained in:
parent
ff16094961
commit
bbef61525e
@ -13,10 +13,11 @@ import sys
|
||||
import atexit
|
||||
import logging
|
||||
import threading
|
||||
import logging.handlers
|
||||
|
||||
# Import salt libs
|
||||
from salt._compat import Queue
|
||||
from salt.log.mixins import NewStyleClassMixIn
|
||||
from salt.log.mixins import NewStyleClassMixIn, ExcInfoOnLogLevelFormatMixIn
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -84,3 +85,28 @@ class TemporaryLoggingHandler(logging.NullHandler):
|
||||
# it should not handle the log record
|
||||
continue
|
||||
handler.handle(record)
|
||||
|
||||
|
||||
class StreamHandler(ExcInfoOnLogLevelFormatMixIn, logging.StreamHandler):
|
||||
'''
|
||||
Stream handler which properly handles exc_info on a per handler basis
|
||||
'''
|
||||
|
||||
|
||||
class FileHandler(ExcInfoOnLogLevelFormatMixIn, logging.FileHandler):
|
||||
'''
|
||||
File handler which properly handles exc_info on a per handler basis
|
||||
'''
|
||||
|
||||
|
||||
class SysLogHandler(ExcInfoOnLogLevelFormatMixIn, logging.handlers.SysLogHandler):
|
||||
'''
|
||||
Syslog handler which properly handles exc_info on a per handler basis
|
||||
'''
|
||||
|
||||
|
||||
if sys.version_info > (2, 6):
|
||||
class WatchedFileHandler(ExcInfoOnLogLevelFormatMixIn, logging.handlers.WatchedFileHandler):
|
||||
'''
|
||||
Watched file handler which properly handles exc_info on a per handler basis
|
||||
'''
|
||||
|
@ -12,6 +12,7 @@
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import sys
|
||||
import logging
|
||||
|
||||
|
||||
@ -67,3 +68,56 @@ class NewStyleClassMixIn(object):
|
||||
|
||||
'Cannot create a consistent method resolution order (MRO) for bases'
|
||||
'''
|
||||
|
||||
|
||||
class ExcInfoOnLogLevelFormatMixIn(object):
|
||||
'''
|
||||
Logging handler class mixin to properly handle including exc_info on a pre logging handler basis
|
||||
'''
|
||||
|
||||
def format(self, record):
|
||||
'''
|
||||
Format the log record to include exc_info if the handler is enabled for a specific log level
|
||||
'''
|
||||
formatted_record = super(ExcInfoOnLogLevelFormatMixIn, self).format(record)
|
||||
exc_info_on_loglevel = getattr(record, 'exc_info_on_loglevel', None)
|
||||
if exc_info_on_loglevel is None:
|
||||
return formatted_record
|
||||
|
||||
# If we reached this far it means the log record was created with exc_info_on_loglevel
|
||||
# If this specific handler is enabled for that record, then we should format it to
|
||||
# include the exc_info details
|
||||
if self.level > exc_info_on_loglevel:
|
||||
# This handler is not enabled for the desired exc_info_on_loglevel, don't include exc_info
|
||||
return formatted_record
|
||||
|
||||
# If we reached this far it means we should include exc_info
|
||||
if not record.exc_info_on_loglevel_instance:
|
||||
# This should actually never occur
|
||||
return formatted_record
|
||||
|
||||
if record.exc_info_on_loglevel_formatted is None:
|
||||
# Let's cache the formatted exception to avoid recurring conversions and formatting calls
|
||||
record.exc_info_on_loglevel_formatted = self.formatter.formatException(
|
||||
record.exc_info_on_loglevel_instance
|
||||
)
|
||||
|
||||
# Let's format the record to include exc_info just like python's logging formatted does
|
||||
if formatted_record[-1:] != '\n':
|
||||
formatted_record += '\n'
|
||||
|
||||
try:
|
||||
formatted_record += record.exc_info_on_loglevel_formatted
|
||||
except UnicodeError:
|
||||
# According to the standard library logging formatter comments:
|
||||
#
|
||||
# Sometimes filenames have non-ASCII chars, which can lead
|
||||
# to errors when s is Unicode and record.exc_text is str
|
||||
# See issue 8924.
|
||||
# We also use replace for when there are multiple
|
||||
# encodings, e.g. UTF-8 for the filesystem and latin-1
|
||||
# for a script. See issue 13232.
|
||||
formatted_record += record.record.exc_info_on_loglevel_formatted.decode(sys.getfilesystemencoding(),
|
||||
'replace')
|
||||
|
||||
return formatted_record
|
||||
|
@ -31,7 +31,7 @@ GARBAGE = logging.GARBAGE = 1
|
||||
QUIET = logging.QUIET = 1000
|
||||
|
||||
# Import salt libs
|
||||
from salt.log.handlers import TemporaryLoggingHandler
|
||||
from salt.log.handlers import TemporaryLoggingHandler, StreamHandler, SysLogHandler, WatchedFileHandler
|
||||
from salt.log.mixins import LoggingMixInMeta, NewStyleClassMixIn
|
||||
from salt._compat import string_types
|
||||
|
||||
@ -87,7 +87,7 @@ def is_extended_logging_configured():
|
||||
LOGGING_NULL_HANDLER = TemporaryLoggingHandler(logging.WARNING)
|
||||
|
||||
# Store a reference to the temporary console logger
|
||||
LOGGING_TEMP_HANDLER = logging.StreamHandler(sys.stderr)
|
||||
LOGGING_TEMP_HANDLER = StreamHandler(sys.stderr)
|
||||
|
||||
# Store a reference to the "storing" logging handler
|
||||
LOGGING_STORE_HANDLER = TemporaryLoggingHandler()
|
||||
@ -161,26 +161,66 @@ class SaltLoggingClass(LOGGING_LOGGER_CLASS, NewStyleClassMixIn):
|
||||
pass
|
||||
return instance
|
||||
|
||||
def _log(self, level, msg, args, exc_info=None, extra=None, exc_info_on_loglevel=None):
|
||||
# If both exc_info and exc_info_on_loglevel are both passed, let's fail.
|
||||
if exc_info and exc_info_on_loglevel:
|
||||
raise RuntimeError(
|
||||
'Please only use one of \'exc_info\' and \'exc_info_on_loglevel\', not both'
|
||||
)
|
||||
if exc_info_on_loglevel is not None:
|
||||
if isinstance(exc_info_on_loglevel, string_types):
|
||||
exc_info_on_loglevel = LOG_LEVELS.get(exc_info_on_loglevel, logging.ERROR)
|
||||
elif not isinstance(exc_info_on_loglevel, int):
|
||||
raise RuntimeError(
|
||||
'The value of \'exc_info_on_loglevel\' needs to be a logging level or '
|
||||
'a logging level name, not {0!r}'.format(exc_info_on_loglevel)
|
||||
)
|
||||
if extra is None:
|
||||
extra = {'exc_info_on_loglevel': exc_info_on_loglevel}
|
||||
else:
|
||||
extra['exc_info_on_loglevel'] = exc_info_on_loglevel
|
||||
|
||||
LOGGING_LOGGER_CLASS._log(
|
||||
self, level, msg, args, exc_info=exc_info, extra=extra
|
||||
)
|
||||
|
||||
# pylint: disable=C0103
|
||||
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None,
|
||||
extra=None):
|
||||
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
|
||||
# Let's remove exc_info_on_loglevel from extra
|
||||
exc_info_on_loglevel = extra.pop('exc_info_on_loglevel')
|
||||
if not extra:
|
||||
# If nothing else is in extra, make it None
|
||||
extra = None
|
||||
|
||||
# Let's try to make every logging message unicode
|
||||
if isinstance(msg, string_types) and not isinstance(msg, unicode):
|
||||
try:
|
||||
return LOGGING_LOGGER_CLASS.makeRecord(
|
||||
logrecord = LOGGING_LOGGER_CLASS.makeRecord(
|
||||
self, name, level, fn, lno,
|
||||
msg.decode('utf-8', 'replace'),
|
||||
args, exc_info, func, extra
|
||||
)
|
||||
except UnicodeDecodeError:
|
||||
return LOGGING_LOGGER_CLASS.makeRecord(
|
||||
logrecord = LOGGING_LOGGER_CLASS.makeRecord(
|
||||
self, name, level, fn, lno,
|
||||
msg.decode('utf-8', 'ignore'),
|
||||
args, exc_info, func, extra
|
||||
)
|
||||
return LOGGING_LOGGER_CLASS.makeRecord(
|
||||
self, name, level, fn, lno, msg, args, exc_info, func, extra
|
||||
)
|
||||
else:
|
||||
logrecord = LOGGING_LOGGER_CLASS.makeRecord(
|
||||
self, name, level, fn, lno, msg, args, exc_info, func, extra
|
||||
)
|
||||
|
||||
if exc_info_on_loglevel is not None:
|
||||
# Let's add some custom attributes to the LogRecord class in order to include the exc_info on a per
|
||||
# handler basis. This will allow showing tracebacks on logfiles but not on console if the logfile
|
||||
# handler is enabled for the log level "exc_info_on_loglevel" and console handler is not.
|
||||
logrecord.exc_info_on_loglevel_instance = sys.exc_info()
|
||||
logrecord.exc_info_on_loglevel_formatted = None
|
||||
|
||||
logrecord.exc_info_on_loglevel = exc_info_on_loglevel
|
||||
return logrecord
|
||||
|
||||
# pylint: enable=C0103
|
||||
|
||||
|
||||
@ -301,7 +341,7 @@ def setup_console_logger(log_level='error', log_format=None, date_format=None):
|
||||
# There's already a logging handler outputting to sys.stderr
|
||||
break
|
||||
else:
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
handler = StreamHandler(sys.stderr)
|
||||
handler.setLevel(level)
|
||||
|
||||
# Set the default console formatter config
|
||||
@ -371,7 +411,7 @@ def setup_logfile_logger(log_path, log_level='error', log_format=None,
|
||||
|
||||
if parsed_log_path.scheme in ('tcp', 'udp', 'file'):
|
||||
syslog_opts = {
|
||||
'facility': logging.handlers.SysLogHandler.LOG_USER,
|
||||
'facility': SysLogHandler.LOG_USER,
|
||||
'socktype': socket.SOCK_DGRAM
|
||||
}
|
||||
|
||||
@ -403,7 +443,7 @@ def setup_logfile_logger(log_path, log_level='error', log_format=None,
|
||||
facility_name = 'LOG_USER' # Syslog default
|
||||
|
||||
facility = getattr(
|
||||
logging.handlers.SysLogHandler, facility_name, None
|
||||
SysLogHandler, facility_name, None
|
||||
)
|
||||
if facility is None:
|
||||
# This python syslog version does not know about the user provided
|
||||
@ -436,7 +476,7 @@ def setup_logfile_logger(log_path, log_level='error', log_format=None,
|
||||
|
||||
try:
|
||||
# Et voilá! Finally our syslog handler instance
|
||||
handler = logging.handlers.SysLogHandler(**syslog_opts)
|
||||
handler = SysLogHandler(**syslog_opts)
|
||||
except socket.error as err:
|
||||
logging.getLogger(__name__).error(
|
||||
'Failed to setup the Syslog logging handler: {0}'.format(
|
||||
@ -450,9 +490,7 @@ def setup_logfile_logger(log_path, log_level='error', log_format=None,
|
||||
# Since salt uses YAML and YAML uses either UTF-8 or UTF-16, if a
|
||||
# user is not using plain ASCII, their system should be ready to
|
||||
# handle UTF-8.
|
||||
handler = getattr(
|
||||
logging.handlers, 'WatchedFileHandler', logging.FileHandler
|
||||
)(log_path, mode='a', encoding='utf-8', delay=0)
|
||||
handler = WatchedFileHandler(log_path, mode='a', encoding='utf-8', delay=0)
|
||||
except (IOError, OSError):
|
||||
logging.getLogger(__name__).warning(
|
||||
'Failed to open log file, do you have permission to write to '
|
||||
|
Loading…
Reference in New Issue
Block a user