00001
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
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
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
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
00184
00185
00186
00187
00188
00189
00190
00191
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