def setup_logfile_handler( log_path, log_level=None, log_format=None, date_format=None, max_bytes=0, backup_count=0, user=None, ): """ Setup the log file handler Since version 0.10.6 we support logging to syslog, some examples: tcp://localhost:514/LOG_USER tcp://localhost/LOG_DAEMON udp://localhost:5145/LOG_KERN udp://localhost file:///dev/log file:///dev/log/LOG_SYSLOG file:///dev/log/LOG_DAEMON The above examples are self explanatory, but: <file|udp|tcp>://<host|socketpath>:<port-if-required>/<log-facility> Thinking on doing remote logging you might also be thinking that you could point Salt's logging to the remote syslog. **Please Don't!** An issue has been reported when doing this over TCP where the logged lines get concatenated. See #3061. The preferred way to do remote logging is setup a local syslog, point Salt's logging to the local syslog(unix socket is much faster) and then have the local syslog forward the log messages to the remote syslog. """ if is_logfile_handler_configured(): log.warning("Logfile logging already configured") return atexit.register(shutdown_logfile_handler) log.trace( "Setting up log file logging: %s", dict( log_path=log_path, log_level=log_level, log_format=log_format, date_format=date_format, max_bytes=max_bytes, backup_count=backup_count, user=user, ), ) if log_path is None: log.warning("log_path setting is set to `None`. Nothing else to do") return if log_level is None: log_level = logging.WARNING log_level = get_logging_level_from_string(log_level) parsed_log_path = urllib.parse.urlparse(log_path) if parsed_log_path.scheme in ("tcp", "udp", "file"): syslog_opts = { "facility": SysLogHandler.LOG_USER, "socktype": socket.SOCK_DGRAM, } if parsed_log_path.scheme == "file" and parsed_log_path.path: path = pathlib.Path(parsed_log_path.path) facility_name = path.stem.upper() try: if not facility_name.startswith("LOG_"): # The user is not specifying a syslog facility facility_name = "LOG_USER" # Syslog default syslog_opts["address"] = str(path.resolve()) else: # The user has set a syslog facility, let's update the path to # the logging socket syslog_opts["address"] = str(path.resolve().parent) except OSError as exc: raise LoggingRuntimeError( "Failed to setup the Syslog logging handler: {}".format( exc)) from exc elif parsed_log_path.path: # In case of udp or tcp with a facility specified path = pathlib.Path(parsed_log_path.path) facility_name = path.stem.upper() if not facility_name.startswith("LOG_"): # Logging facilities start with LOG_ if this is not the case # fail right now! raise LoggingRuntimeError( "The syslog facility '{}' is not known".format( facility_name)) else: # This is the case of udp or tcp without a facility specified facility_name = "LOG_USER" # Syslog default facility = getattr(SysLogHandler, facility_name, None) if facility is None: # This python syslog version does not know about the user provided # facility name raise LoggingRuntimeError( "The syslog facility '{}' is not known".format(facility_name)) syslog_opts["facility"] = facility if parsed_log_path.scheme in ("tcp", "udp"): syslog_opts["address"] = ( parsed_log_path.hostname, parsed_log_path.port or logging.handlers.SYSLOG_UDP_PORT, ) if parsed_log_path.scheme == "tcp": syslog_opts["socktype"] = socket.SOCK_STREAM elif parsed_log_path.scheme == "file": syslog_opts.pop("socktype", None) try: # Et voilá! Finally our syslog handler instance handler = SysLogHandler(**syslog_opts) except OSError as exc: raise LoggingRuntimeError( "Failed to setup the Syslog logging handler: {}".format( exc)) from exc else: # make sure, the logging directory exists and attempt to create it if necessary if user is None: import salt.utils.user user = salt.utils.user.get_user() import salt.utils.files import salt.utils.verify # Logfile is not using Syslog, verify with salt.utils.files.set_umask(0o027): salt.utils.verify.verify_log_files([log_path], user) try: # Logfile logging is UTF-8 on purpose. # 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. if max_bytes > 0: handler = RotatingFileHandler( log_path, mode="a", maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8", delay=0, ) else: handler = WatchedFileHandler(log_path, mode="a", encoding="utf-8", delay=0) except OSError: log.warning( "Failed to open log file, do you have permission to write to %s?", log_path, ) # Do not proceed with any more configuration since it will fail, we # have the console logging already setup and the user should see # the error. return handler.setLevel(log_level) if not log_format: log_format = DFLT_LOG_FMT_LOGFILE if not date_format: date_format = DFLT_LOG_DATEFMT_LOGFILE formatter = logging.Formatter(log_format, datefmt=date_format) handler.setFormatter(formatter) logging.root.addHandler(handler) setup_logfile_handler.__handler__ = handler
def _log( self, level, msg, args, exc_info=None, extra=None, # pylint: disable=arguments-differ stack_info=False, stacklevel=1, exc_info_on_loglevel=None, ): if extra is None: extra = {} # pylint: disable=no-member current_jid = RequestContext.current.get("data", {}).get("jid", None) log_fmt_jid = RequestContext.current.get("opts", {}).get("log_fmt_jid", None) # pylint: enable=no-member 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 both exc_info and exc_info_on_loglevel are both passed, let's fail if exc_info and exc_info_on_loglevel: raise LoggingRuntimeError( "Only one of 'exc_info' and 'exc_info_on_loglevel' is permitted" ) if exc_info_on_loglevel is not None: if isinstance(exc_info_on_loglevel, str): 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 '{}'".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 if sys.version_info < (3, ): LOGGING_LOGGER_CLASS._log(self, level, msg, args, exc_info=exc_info, extra=extra) elif sys.version_info < (3, 8): LOGGING_LOGGER_CLASS._log( self, level, msg, args, exc_info=exc_info, extra=extra, stack_info=stack_info, ) else: LOGGING_LOGGER_CLASS._log( self, level, msg, args, exc_info=exc_info, extra=extra, stack_info=stack_info, stacklevel=stacklevel, )
def _log( self, level, msg, args, exc_info=None, extra=None, # pylint: disable=arguments-differ stack_info=False, stacklevel=1, exc_info_on_loglevel=None): if extra is None: extra = {} # pylint: disable=no-member current_jid = RequestContext.current.get('data', {}).get('jid', None) log_fmt_jid = RequestContext.current.get('opts', {}).get('log_fmt_jid', None) # pylint: enable=no-member 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 both exc_info and exc_info_on_loglevel are both passed, let's fail if exc_info and exc_info_on_loglevel: raise LoggingRuntimeError( 'Only one of \'exc_info\' and \'exc_info_on_loglevel\' is ' 'permitted') if exc_info_on_loglevel is not None: if isinstance(exc_info_on_loglevel, six.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 \'{}\''.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 try: logging._acquireLock() if sys.version_info < (3, ): LOGGING_LOGGER_CLASS._log(self, level, msg, args, exc_info=exc_info, extra=extra) elif sys.version_info < (3, 8): LOGGING_LOGGER_CLASS._log(self, level, msg, args, exc_info=exc_info, extra=extra, stack_info=stack_info) else: LOGGING_LOGGER_CLASS._log(self, level, msg, args, exc_info=exc_info, extra=extra, stack_info=stack_info, stacklevel=stacklevel) except: pass finally: logging._releaseLock()