CEL = "C"
TIMEFMT = "%X"
DATEFMT = "%x"
+CORNERCHARS_DOUBLE = ['╚', '╝', '╗', '╔']
+CORNERCHARS_SINGLE = ['└', '┘', '┐', '┌']
+LINECHARS_DOUBLE = ['║', '═']
+LINECHARS_SINGLE = ['│', '─']
+
+class Output:
+
+ def __init__(self):
+ self.content = ""
+ self.destination = ""
+
+ def append(self, content):
+ self.content += content
+
+ def write(self, destination=""):
+ if destination == "":
+ destination = self.destination
+ if destination == "":
+ logger.warning("No destination path provided")
+ return 1
+ with open(destination, 'w') as f:
+ f.write(self.content)
+ logger.info("Written output to {}".format(destination))
+
+
+class PlaintextOutput(Output):
+
+ def __init__(self, linewidth=80):
+ self.content = ""
+ self.destination = ""
+ self.linewidth = linewidth;
+
+ def append_header(self, template=''):
+ init_varfilter()
+ box = PlaintextBox(content=Template("$title $version on $hostname\n\n$time $date").safe_substitute(varsubst), vpadding=2, hpadding="\t\t", linewidth=config.prefs['linewidth'])
+ line = PlaintextLine(self.linewidth)
+ self.append(box.draw() + line.draw())
+
+ def append_footer(self):
+ init_varfilter()
+ self.append(PlaintextLine(self.linewidth, vpadding=1).draw())
+ self.append(Template("$hostname $time $date").safe_substitute(varsubst))
+
+ def append_section(self, section):
+ self.append(PlaintextBox(content=section.title, double=False, fullwidth=False, vpadding=0, hpadding=" ").draw())
+ self.append('\n'*2)
+ for data in section.data:
+ self.append(self._fmt_data(data.subtitle, data.items))
+ self.append('\n')
+
+ def _fmt_data(self, subtitle, data = None): # write title and data
+ if (subtitle == ""):
+ logger.warning("No subtitle provided.. skipping section")
+ return
+
+ if (data == None or len(data) == 0):
+ logger.debug("No data provided.. just printing subtitle")
+ return subtitle + '\n'
+ else:
+ logger.debug("Received data " + str(data))
+ subtitle += ':'
+ if (len(data) == 1):
+ return subtitle + ' ' + data[0] + '\n'
+ else:
+ itemoutput = subtitle + '\n'
+ for datum in data:
+ datum = '• ' + datum
+ if len(datum) > config.prefs['linewidth']:
+ words = datum.split()
+ if max(map(len, words)) > config.prefs['linewidth']:
+ raise ValueError("Content width is too small")
+ res, part, others = [], words[0], words[1:]
+ for word in others:
+ if len(' ') + len(word) > config.prefs['linewidth'] - len(part):
+ res.append(part)
+ part = word
+ else:
+ part += ' ' + word
+ if part:
+ res.append(part)
+ datum = '\n'.join(res)
+ itemoutput += datum + '\n'
+ return itemoutput
+
+
+class HtmlOutput(Output):
+
+ def __init__(self):
+ self.content = ""
+ self.destination = ""
+ self.css = ""
+
+ def embed_css(self, css):
+ self.content = mail.mailprep(self.content, css)
+
+ def append_header(self, template): # insert variables into header template file
+ init_varfilter()
+ headercontent = Template(open(template, 'r').read())
+ self.append(headercontent.safe_substitute(varsubst))
+ self.append(opentag('div', id='main'))
+
+ def append_footer(self):
+ self.append(closetag('div') + closetag('body') + closetag('html'))
+
+ def append_section(self, section):
+ self.append(opentag('div', 1, section.title, 'section'))
+ self.append(self._gen_title(section.title))
+ for data in section.data:
+ self.append(self._fmt_data(data.subtitle, data.items))
+ self.append(closetag('div', 1))
+
+ def _gen_title(self, title): # write title for a section
+ if (title == '' or '\n' in title):
+ logger.error("Invalid title")
+ raise ValueError
+ logger.debug("Writing title for " + title)
+ return tag('h2', 0, title)
+
+ def _fmt_data(self, subtitle, data = None): # write title and data
+ if (subtitle == ""):
+ logger.warning("No subtitle provided.. skipping section")
+ return
+
+ if (data == None or len(data) == 0):
+ logger.debug("No data provided.. just printing subtitle")
+ return tag('p', 0, subtitle)
+ else:
+ logger.debug("Received data " + str(data))
+ subtitle += ':'
+ if (len(data) == 1):
+ return tag('p', 0, subtitle + ' ' + data[0])
+ else:
+ output = ""
+ output += tag('p', 0, subtitle)
+ output += opentag('ul', 1)
+ coderegex = re.compile('`(.*)`')
+ for datum in data:
+ if datum == "" or datum == None:
+ continue
+ datum = coderegex.sub(r'<code>{\1}</code>', str(datum))
+ output += tag('li', 0, datum)
+ output += closetag('ul', 1)
+ return output
+
+
+class Section:
+
+ def __init__(self, title):
+ self.title = title
+ self.data = []
+
+ def add_data(self, data):
+ self.data.append(data)
+
+class Data:
+
+ def __init__(self, subtitle, items=None):
+ self.subtitle = subtitle
+ self.items = items
+
+
+class PlaintextLine:
+
+ def __init__(self, linewidth=80, double=True, vpadding=1, hpadding=""):
+ self.linewidth = linewidth
+ self.double = False
+ self.vpadding = vpadding
+ self.hpadding = hpadding
+
+ def draw(self):
+ line = (LINECHARS_DOUBLE[1] if self.double else LINECHARS_SINGLE[1])
+ return "\n" * self.vpadding + self.hpadding + line * (self.linewidth - 2 * len(self.hpadding)) + self.hpadding + "\n" * self.vpadding
+
+class PlaintextBox:
+
+ def __init__(self, content="", double=True, fullwidth=True, linewidth=80, hpadding="\t", vpadding=1):
+ self.content = content
+ self.fullwidth = fullwidth
+ self.linewidth = linewidth
+ self.hpadding = hpadding
+ self.vpadding = vpadding
+ self.double = double
+
+ def draw(self):
+
+ if self.double == True:
+ cornerchars = CORNERCHARS_DOUBLE
+ linechars = LINECHARS_DOUBLE
+ else:
+ cornerchars = CORNERCHARS_SINGLE
+ linechars = LINECHARS_SINGLE
+
+ # Check hpadding has a definite width
+ self.hpadding = self.hpadding.replace("\t", " "*4)
+
+ # Calculate number of characters per line
+ contentlines = self.content.splitlines()
+ contentwidth = int((self.linewidth if self.linewidth > 0 else 80) if self.content.splitlines() else len(max(contentlines, key=len)))
+ logger.debug("Contentwidth is {0}".format(str(contentwidth)))
+ logger.debug("Longest line is {0}".format(len(max(contentlines, key=len))))
+ contentwidth += -2*(len(self.hpadding)+1)
+ if not self.fullwidth:
+ longestline = len(max(contentlines, key=len))
+ if longestline <= self.linewidth - 2*(len(self.hpadding)+1):
+ contentwidth = longestline
+
+ # Split lines that are too long
+ for i, line in enumerate(contentlines):
+ if len(line) > contentwidth:
+ words = line.split()
+ if max(map(len, words)) > contentwidth:
+ raise ValueError("Content width is too small")
+ res, part, others = [], words[0], words[1:]
+ for word in others:
+ if len(' ') + len(word) > contentwidth - len(part):
+ res.append(part)
+ part = word
+ else:
+ part += ' ' + word
+ if part:
+ res.append(part)
+ contentlines[i] = res
+
+ # Flatten list
+ # Note list comprehension doesn't work here, so we must iterate through each item
+ newlines = []
+ for line in contentlines:
+ if isinstance(line, list):
+ for subline in line:
+ newlines.append(subline)
+ else:
+ newlines.append(line)
+ contentlines = newlines
+
+ # Add vertical padding
+ for _ in range(self.vpadding):
+ contentlines.insert(0, ' '*contentwidth)
+ contentlines.append(' '*contentwidth)
+
+ # Insert horizontal padding on lines that are too short
+ contentlines = [linechars[0] + self.hpadding + x + ' '*(self.linewidth-(len(x)+2*len(self.hpadding)+2) if len(x) < contentwidth else 0) + self.hpadding + linechars[0] for x in contentlines]
+ contentlines.insert(0, cornerchars[3] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[2])
+ contentlines.append(cornerchars[0] + linechars[1] * (contentwidth + len(self.hpadding)*2) + cornerchars[1])
+ return ('\n').join(contentlines)
def init_varfilter():
global varfilter
c = closetag(tag, block)
return o + content + c
-def header(template): # return a parsed html header from file
-# try:
-# copyfile(config['css'], config['dest'] + '/' + os.path.basename(config['css']))
-# logger.debug("copied main.css")
-# except Exception as e:
-# logger.warning("could not copy main.css - " + str(e))
- init_varfilter()
- headercontent = Template(open(template, 'r').read())
- return headercontent.safe_substitute(varsubst)
-
def orderbyfreq(l): # order a list by the frequency of its elements and remove duplicates
temp_l = l[:]
l = list(set(l))
argparser.add_argument('-l', '--logs', help='services to analyse', required=False)
argparser.add_argument('-nl', '--ignore-logs', help='skip these services (takes precedence over -l)', required=False)
argparser.add_argument('-es', '--embed-styles', help='make CSS rules inline rather than linking the file', required=False, default=False, action='store_true')
+ argparser.add_argument('-nh', '--plain', help='write/send plain text rather than HTML', required = False, default=False, action='store_true')
# Load config
if argparser.parse_args().config:
logger.info("Beginning log analysis at {0} {1}".format(start.strftime(formatting.DATEFMT), start.strftime(formatting.TIMEFMT)))
logger.debug("This is {0} version {1}, running on Python {2}".format(logparse.__name__, logparse.__version__, sys.version.replace('\n', '')))
- # Write HTML header
+ # Write header
- global output_html
- output_html = formatting.header(prefs['header'])
- output_html += formatting.opentag('div', id='main')
+ global output
+ if argparser.parse_args().plain:
+ output = formatting.PlaintextOutput(linewidth=prefs['linewidth'])
+ else:
+ output = formatting.HtmlOutput()
+
+ output.append_header(prefs['header'])
# Find parsers
logger.debug(str(parser_providers))
for parser in parser_providers:
- output_html += parser.parse_log()
+ output.append_section(parser.parse_log())
# Write HTML footer
-
- output_html += formatting.closetag('div') + formatting.closetag('body') + formatting.closetag('html')
+ output.append_footer()
if argparser.parse_args().printout:
- print(output_html)
+ print(output)
if argparser.parse_args().destination or prefs['output']:
if argparser.parse_args().destination:
dest_path = argparser.parse_args().destination
else:
dest_path = prefs['output']
logger.debug("Outputting to {0}".format(dest_path))
- if argparser.parse_args().embed_styles or prefs['embed-styles']:
- output_html = mail.mailprep(output_html, prefs['css'])
- if not os.path.isfile(dest_path) and not (argparser.parse_args().overwrite or config['overwrite']):
- with open(dest_path, 'w') as f:
- f.write(output_html)
- logger.info("Written output to {}".format(dest_path))
+ if (argparser.parse_args().embed_styles or prefs['embed-styles']) and not (argparser.parse_args.plain or prefs['plain']):
+ output.embed_css(prefs['css'])
+ if (not os.path.isfile(dest_path)) and not (argparser.parse_args().overwrite or config['overwrite']):
+ output.write(dest_path)
else:
logger.warning("Destination file already exists")
if input("Would you like to overwrite {0}? (y/n) [n] ".format(dest_path)) == 'y':
- with open(dest_path, 'w') as f:
- f.write(output_html)
- logger.debug("Written output to {}".format(dest_path))
+ output.write(dest_path)
else:
logger.warning("No output written")
to = argparser.parse_args().to
else:
to = prefs['mail']['to']
- mail.sendmail(mailbin=prefs['mail']['mailbin'], body=mail.mailprep(output_html, prefs['css']), recipient=to, subject=formatting.fsubject(config.prefs['mail']['subject']))
+ if argparser.parse_args().plain or prefs['plain']:
+ mail.sendmail(mailbin=prefs['mail']['mailbin'], body=(output.embed_css(prefs['css']) if isinstance(output, formatting.HtmlOutput) else output.content), recipient=to, subject=formatting.fsubject(config.prefs['mail']['subject']))
# Print end message
finish = datetime.now()