logging_utils.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 from __future__ import print_function
00003 import errno
00004 import sys
00005 import os
00006 import json
00007 import logging
00008 import logging.config
00009 import re
00010 import socket
00011 import platform
00012 
00013 log_level = {
00014     'CRITICAL': logging.CRITICAL,
00015     'critical': logging.CRITICAL,
00016     'DEBUG': logging.DEBUG,
00017     'debug': logging.DEBUG,
00018     'ERROR': logging.ERROR,
00019     'error': logging.ERROR,
00020     'FATAL': logging.FATAL,
00021     'fatal': logging.FATAL,
00022     'INFO': logging.INFO,
00023     'info': logging.INFO,
00024     'WARN': logging.WARNING,
00025     'warn': logging.WARNING,
00026     'WARNING': logging.WARNING,
00027     'warning': logging.WARNING
00028 }
00029 
00030 log_file_path = os.path.join(os.path.expanduser("~"), '.log')
00031 
00032 
00033 def get_log_config_file_path():
00034     directory = os.path.dirname(os.path.realpath(__file__))
00035     path = os.path.join(directory, 'python_logging.conf')
00036     return path
00037 
00038 
00039 def configure_common_logging(level='INFO', handlers=["syslog", "console"], file_path=None):
00040     """
00041     Configure the default logging scheme, with just a few override options.
00042 
00043     @type level string
00044     @param level log level of the root handler as a string.
00045 
00046     @type handlers list-of-strings
00047     @param handlers List of named handlers.  Valid handlers are "syslog", "console", "file", or "socket".
00048 
00049     @type file_path string
00050     @param file_path Path to the log file, if the 'file' appender is used.  If file_path is just
00051         a file name, the logfile will be placed in the default location (/home/$USER/.log/<file_path>).
00052         Otherwise the entire path will be used.
00053     """
00054     global log_file_path
00055 
00056     config = get_common_logging_configuration()
00057     config['root']['level'] = level
00058 
00059     config['root']['handlers'] = []
00060     myplatform = platform.system()
00061     for handler in handlers:
00062         # syslog is not supported on windows
00063         if myplatform != 'Linux' and handler == 'syslog':
00064             print('Warning: syslog is not a valid handler on {}'.format(myplatform), file=sys.stderr)
00065             continue
00066         config['root']['handlers'].append(handler)
00067 
00068     if file_path is not None:
00069         # If the file_path is just a filename, put the log file in the default directory
00070         if os.path.dirname(file_path) == '':
00071             file_path = os.path.join(log_file_path, file_path)
00072 
00073         config['handlers']['file']['filename'] = file_path
00074 
00075     logging.config.dictConfig(config)
00076     logging.info("Logging to {}".format(log_file_path))
00077     return config
00078 
00079 
00080 def get_common_logging_configuration():
00081     """
00082     Return the common logging configuration as a dictionary.
00083 
00084     When invoked, this method will create the folder ~/.log, if it does not exist
00085     """
00086     log_file_path = os.path.expanduser(os.path.join('~', '.log'))
00087 
00088     hostname = socket.gethostname()
00089 
00090     try:
00091         os.makedirs(log_file_path)
00092     except OSError as e:
00093         if e.errno != errno.EEXIST:
00094             raise e
00095 
00096     config = {
00097         "version": 1,
00098         "disable_existing_loggers": False,
00099         "formatters": {
00100             "brief": {
00101                 "format": "[%(name)s][%(levelname)-8s] %(message)s"
00102             },
00103             "precise": {
00104                 "format": "%(asctime)s {} ncl_py: [%(levelname)-8s] [%(name)-15s] %(message)s".format(hostname),
00105                 "datefmt": "%Y-%m-%d %H:%M:%S"
00106             },
00107             "syslog": {
00108                 "format": "ncl_py: [%(levelname)-8s] [%(name)-15s] %(message)s"
00109             }
00110         },
00111         "handlers": {
00112             "console": {
00113                 "class": "logging.StreamHandler",
00114                 "formatter": "precise",
00115                 "stream": "ext://sys.stdout"
00116             },
00117             "file": {
00118                 "class": "logging.handlers.RotatingFileHandler",
00119                 "formatter": "precise",
00120                 "filename": "{}".format(os.path.join(log_file_path, "nasa_common_logging.log")),
00121                 "maxBytes": 1000000,
00122                 "backupCount": 3
00123             },
00124             "socket": {
00125                 "class": "logging.handlers.SocketHandler",
00126                 "formatter": "precise",
00127                 "host": "localhost",
00128                 "port": 9020
00129             },
00130         },
00131         "root": {
00132             "level": "NOTSET",
00133             "handlers": ["console"],
00134             "propagate": "no"
00135         }
00136     }
00137 
00138     # the syslog handler is only valid in Linux, not in macOS or Windows
00139     if platform.system() == 'Linux':
00140         config['handlers']['syslog'] = {
00141                 "class": "logging.handlers.SysLogHandler",
00142                 "formatter": "syslog",
00143                 "address": "/dev/log",
00144                 "facility": "local5"
00145             }
00146     return config
00147 
00148 
00149 def configure_custom_logging(config):
00150     """
00151     Configure the root logger with a custom config.
00152 
00153     NOTE: One could call get_common_logging_configuration(), modify the contents, and simply
00154     call this method as an easy means of changing the logging configuration
00155     """
00156     logging.config.dictConfig(config)
00157 
00158 
00159 def configure_custom_logging_file(config_file):
00160     """
00161     Configure the root logger from a JSON-format logging config file.
00162     """
00163     with open(config_file, 'r') as fp:
00164         config = json.load(fp)
00165         fp.close()
00166     logging.config.dictConfig(config)
00167 
00168 
00169 """
00170 The following lines contain the regular expressions needed to parse from the syslog
00171 """
00172 re_fill  = '.*?'
00173 re_syslog_time_stamp = '[A-Z][a-z]{2,}\s+\d{1,2} \d{2}:\d{2}:\d{2}'
00174 re_ncl_time_stamp    =  '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
00175 
00176 re_timestamp = '({}|{})'.format(re_syslog_time_stamp,re_ncl_time_stamp)
00177 re_host      = '\s*([^\s]+)\s*'
00178 re_source    = '\s*([^\s]+)\s*\\:'
00179 re_level     = '\\[(.*?)\s*\\]'
00180 re_category  = '\\[(.*?)\s*\\]'
00181 re_msg       = '((?:.*\n?)(?:^(?!{}).*\n?)*)'.format(re_timestamp)
00182 
00183 #   to explain:
00184 #      ".*\n"- match everything up to and including a new-line
00185 #      "(?: <expr>)" - match <expr>, but don't put it in a group
00186 #
00187 #   Therefore:
00188 #      1) "(?:.*\n)" - match the rest of the first line, but don't put it in it's own group
00189 #      2) "(?:^(?!{}).*\n)*" - match 0 or more other lines that DON'T start with a timestamp
00190 #
00191 #   Then, by enclosing 1) and 2) in a set of parentheses, the entire message will be grouped together.
00192 
00193 re_ncl_log_entry = '^' + re_timestamp + re_fill + re_host + re_fill + re_source + re_fill + re_level + re_fill + re_category + '\s*' + re_msg
00194 rg_ncl_log_entry = re.compile(re_ncl_log_entry,re.MULTILINE)
00195 
00196 re_generic_log_entry = '^' + re_timestamp + re_fill + re_host + re_fill + re_source + '\s*' + re_msg
00197 rg_generic_log_entry = re.compile(re_generic_log_entry,re.MULTILINE)
00198 
00199 def parse_log_buffer(buffer):
00200     """
00201     Parse log entries from a buffer.  This buffer could be a portion, or an entire log file.  This method will only
00202     detect and properly parse log messages that are formatted using the nasa_common_logging default log format.  For
00203     syslog, this is logs using the "syslog" formatter or the "precise" formatter.  For formatter definitions, see
00204     "get_common_logging_configuration"
00205 
00206     Args:
00207         buffer: A single string from a nasa_common_logging formatted log file.
00208 
00209     Returns:
00210         List of log entries.  Each entry is a tuple: (timestamp, source, tag, loglevel, message)
00211     """
00212     entries = []
00213     matched = False
00214 
00215     matches = rg_ncl_log_entry.finditer(buffer)
00216     for m in matches:
00217         if len(m.groups()) == 7:
00218             t = m.group(1)
00219             host = m.group(2)
00220             src = m.group(3)
00221             lvl = m.group(4)
00222             cat = m.group(5)
00223             msg = m.group(6).rstrip('\n')
00224             entries.append((t, host, src, lvl, cat, msg))
00225             matched = True
00226     if matched:
00227         return entries
00228 
00229     matches = rg_generic_log_entry.finditer(buffer)
00230     for m in matches:
00231         if len(m.groups()) == 5:
00232             t = m.group(1)
00233             host = m.group(2)
00234             src = m.group(3)
00235             lvl = None
00236             cat = None
00237             msg = m.group(4).rstrip('\n')
00238             entries.append((t, host, src, lvl, cat, msg))
00239 
00240     return entries
00241 
00242 class PreciseFormatter(logging.Formatter):
00243 
00244     def __init__(self, fmt=None, datefmt=None):
00245 
00246         hostname = socket.gethostname()
00247         format = "%(asctime)s {} ncl_py: [%(levelname)-8s] [%(name)-15s] %(message)s".format(hostname)
00248         datefmt= "%Y-%m-%d %H:%M:%S"
00249         super(PreciseFormatter, self).__init__(format,datefmt)
00250         


nasa_common_logging
Author(s):
autogenerated on Sun Feb 3 2019 03:42:09