def setup_logfile_logger(log_path, log_level='error', log_format=None, date_format=None, max_bytes=0, backup_count=0): ''' Setup the logfile logger 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> If you're 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 when 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_configured(): logging.getLogger(__name__).warning( 'Logfile logging already configured') return if log_path is None: logging.getLogger(__name__).warning( 'log_path setting is set to `None`. Nothing else to do') return # Remove the temporary logging handler __remove_temp_logging_handler() if log_level is None: log_level = 'warning' level = LOG_LEVELS.get(log_level.lower(), logging.ERROR) parsed_log_path = urlparse(log_path) root_logger = logging.getLogger() 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: facility_name = parsed_log_path.path.split(os.sep)[-1].upper() if not facility_name.startswith('LOG_'): # The user is not specifying a syslog facility facility_name = 'LOG_USER' # Syslog default syslog_opts['address'] = parsed_log_path.path else: # The user has set a syslog facility, let's update the path to # the logging socket syslog_opts['address'] = os.sep.join( parsed_log_path.path.split(os.sep)[:-1]) elif parsed_log_path.path: # In case of udp or tcp with a facility specified facility_name = parsed_log_path.path.lstrip(os.sep).upper() if not facility_name.startswith('LOG_'): # Logging facilities start with LOG_ if this is not the case # fail right now! raise RuntimeError( 'The syslog facility \'{0}\' 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 RuntimeError( 'The syslog facility \'{0}\' is not known'.format( facility_name)) syslog_opts['facility'] = facility if parsed_log_path.scheme == 'tcp': # tcp syslog support was only added on python versions >= 2.7 if sys.version_info < (2, 7): raise RuntimeError( 'Python versions lower than 2.7 do not support logging ' 'to syslog using tcp sockets') syslog_opts['socktype'] = socket.SOCK_STREAM 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 sys.version_info < (2, 7) or parsed_log_path.scheme == 'file': # There's not socktype support on python versions lower than 2.7 syslog_opts.pop('socktype', None) try: # Et voilá! Finally our syslog handler instance handler = SysLogHandler(**syslog_opts) except socket.error as err: logging.getLogger(__name__).error( 'Failed to setup the Syslog logging handler: %s', err) shutdown_multiprocessing_logging_listener() sys.exit(2) else: # make sure, the logging directory exists and attempt to create it if necessary log_dir = os.path.dirname(log_path) if not os.path.exists(log_dir): logging.getLogger(__name__).info( 'Log directory not found, trying to create it: %s', log_dir) try: os.makedirs(log_dir, mode=0o700) except OSError as ose: logging.getLogger(__name__).warning( 'Failed to create directory for log file: %s (%s)', log_dir, ose) return 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 (IOError, OSError): logging.getLogger(__name__).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(level) # Set the default console formatter config if not log_format: log_format = '%(asctime)s [%(name)-15s][%(levelname)-8s] %(message)s' if not date_format: date_format = '%Y-%m-%d %H:%M:%S' formatter = logging.Formatter(log_format, datefmt=date_format) handler.setFormatter(formatter) root_logger.addHandler(handler) global __LOGFILE_CONFIGURED global __LOGGING_LOGFILE_HANDLER __LOGFILE_CONFIGURED = True __LOGGING_LOGFILE_HANDLER = handler
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