#! /usr/bin/python
import argparse, logging, os, shutil, re, subprocess, sys, requests, glob, socket, sensors, datetime, time, operator, premailer
from sys import stdin
from collections import namedtuple, defaultdict
diskstat = namedtuple('diskstat', ['cap', 'alloc', 'free', 'ratio'])
drivetemp = namedtuple('drivetemp', ['name', 'temp', 'units'])
AUTHPATH = "/var/log/auth.log"
CRONPATH = "/var/log/cron.log"
SYSPATH = "/var/log/syslog"
SMBDDIR = "/var/log/samba"
ZFSPATH = "/var/log/zpool.log"
ALLOCPATH = "/tmp/alloc"
POSTFIXPATH = "/var/log/mail.log"
HTTPDSTATUS = "http://localhost/server-status"
HTTPDDIR = "/var/log/apache2"
HOSTNAMEPATH = "/etc/hostname"
DUPATHS = ["/home/andrew", "/mnt/andrew"]
HDDTEMPS = ["/dev/sda", "/dev/sdc", "/dev/sdd", "/dev/sde"]
HDDTEMPPORT = 7634
SUMMARYPATH = "/mnt/andrew/temp/logparse-test.html"
OUTPUTPATH = "/mnt/andrew/temp/logparse-test2.html"
MAILPATH = "/mnt/andrew/temp/log-parse-test-3.html"
HEADERPATH = os.path.dirname(os.path.realpath(__file__)) + "/header.html"
STYLEPATH = os.path.dirname(os.path.realpath(__file__)) + "main.css"
MAILOUT = ""
HTMLOUT = ""
TXTOUT = ""
TITLE = "logparse"
MAXLIST = 10
CMDNO = 3
MAILSUBJECT = "logparse from $hostname$"
VERSION = "v0.1"
# DEG = u'\N{DEGREE SIGN}'.encode('utf-8')
DEG = 'C'
# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('logparse')
# Get arguments
parser = argparse.ArgumentParser(description='grab logs of some common services and send them by email')
parser.add_argument('-t','--to', help='mail recipient (\"to\" address)',required=False)
to = parser.parse_args().to
def __main__():
logger.info("Beginning log analysis at " + str(timenow))
if (to == None):
logger.info("no recipient address provided, outputting to stdout")
else:
logger.info("email will be sent to " + to)
global tempfile
tempfile = open(SUMMARYPATH, 'w+')
tempfile.write(header(HEADERPATH))
opentag('div', 1, 'main')
sshd()
sudo()
cron()
nameget()
httpd()
smbd()
postfix()
zfs()
temp()
du()
for tag in ['div', 'body', 'html']:
closetag(tag, 1)
tempfile.close()
mailprep(SUMMARYPATH, MAILPATH)
if (to != None):
logger.debug("sending email")
ms = subject(MAILSUBJECT)
cmd = "cat " + MAILPATH + " | mail --debug-level=10 -a 'Content-type: text/html' -s '" + ms + "' " + to
logger.debug(cmd)
subprocess.call(cmd, shell=True)
logger.info("sent email")
def writetitle(title):
if (title == '' or '\n' in title):
logger.error("invalid title")
return
logger.debug("writing title for " + title)
tag('h2', 0, title)
def writedata(subtitle, data = None): # write title and data to tempfile
if (subtitle == ""):
loggger.warning("no subtitle provided.. skipping section")
return
if (data == None):
logger.debug("no data provided.. just printing subtitle")
tag('p', 0, subtitle)
else:
logger.debug("received data " + str(data))
subtitle += ':'
if (len(data) == 1):
tag('p', 0, subtitle + ' ' + data[0])
else:
tag('p', 0, subtitle)
opentag('ul', 1)
for datum in data:
logger.debug("printing datum " + datum)
tag('li', 0, datum)
closetag('ul', 1)
def opentag(tag, block = 0, id = None, cl = None): # write html opening tag
if (block == 1):
tempfile.write('\n')
tempfile.write('<' + tag)
if (id != None):
tempfile.write(" id='" + id + "'")
if (cl != None):
tempfile.write(" class='" + cl + "'")
tempfile.write('>')
if (block == 1):
tempfile.write('\n')
def closetag(tag, block = 0): # write html closing tag
if (block == 0):
tempfile.write("" + tag + ">")
else:
tempfile.write("\n" + tag + ">\n")
def tag(tag, block = 0, content = ""): # write html opening tag, content, and html closing tag
opentag(tag, block)
tempfile.write(content)
closetag(tag, block)
def header(template): # return a parsed html header from file
headercontent = open(template, 'r').read()
headercontent = varpattern.sub(lambda m: varfilter[re.escape(m.group(0))], headercontent)
return headercontent
def subject(template):
r = varpattern.sub(lambda m: varfilter[re.escape(m.group(0))], template)
logger.debug("returning subject line " + r)
return r
def hostname(): # get the hostname
hnfile = open(HOSTNAMEPATH, 'r')
hn = re.search('^(.*)\n*', hnfile.read()).group(1)
return hn
def resolve(ip): # try to resolve an ip to hostname
logger.debug("trying to resolve ip " + ip)
try:
socket.inet_aton(ip) # succeeds if text contains ip
hn = socket.gethostbyaddr(ip)[0].split(".")[0] # resolve ip to hostname
logger.debug("found hostname " + hn)
return(hn)
except:
logger.debug("failed to resolve hostname for " + ip)
return(ip) # return ip if no hostname exists
def plural(noun, quantity): # return "1 noun" or "n nouns"
if (quantity == 1):
return(str(quantity) + " " + noun)
else:
return(str(quantity) + " " + noun + "s")
def parsesize(num, suffix='B'): # return human-readable size from number of bytes
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
if abs(num) < 1024.0:
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
def readlog(path = None, mode = 'r'): # read file, substituting known paths
if (path == None):
logger.error("no path provided")
return
else:
path = pathpattern.sub(lambda m: pathfilter[re.escape(m.group(0))], path)
if (os.path.isfile(path) is False):
logger.error(path + " does not exist")
return ''
else:
return open(path, mode).read()
def writelog(path = None, content = "", mode = 'w'): # read file, substituting known paths
if (path == None or content == None):
logger.error("invalid usage of writelog")
return
else:
path = pathpattern.sub(lambda m: pathfilter[re.escape(m.group(0))], path)
file = open(path, mode)
file.write(content)
file.close()
def getusage(path): # Get disk usage statistics
disk = os.statvfs(path)
cap = float(disk.f_bsize*disk.f_blocks) # disk capacity
alloc = float(disk.f_bsize*(disk.f_blocks-disk.f_bfree)) # size of path
free = float(disk.f_bsize*disk.f_bfree) # free space on disk (blocks, not usable space)
ratio = alloc / cap * 100 # percentage used
return diskstat(cap, alloc, free, ratio)
def orderbyfreq(l): # order a list by the frequency of its elements and remove duplicates
temp_l = l[:]
l = list(set(l))
l = [[i, temp_l.count(i)] for i in l] # add count of each element
l.sort(key=lambda x:temp_l.count(x[0])) # sort by count
l = [i[0] + ' (' + str(i[1]) + ')' for i in l] # put element and count into string
l = l[::-1] # reverse
return l
def addtag(l, tag): # add prefix and suffix tags to each item in a list
l2 = ['<' + tag + '>' + i + '' + tag + '>' for i in l]
return l2
def truncl(input, limit): # truncate list
if (len(input) > limit):
more = str(len(input) - limit)
output = input[:limit]
output.append("+ " + more + " more")
return(output)
else:
return(input)
def mailprep(inputpath, outputpath, *stylesheet):
logger.debug("converting stylesheet to inline tags")
old = readlog(inputpath)
pm = premailer.Premailer(old, external_styles=STYLEPATH)
MAILOUT = pm.transform()
logger.info("converted stylesheet to inline tags")
file = open(outputpath, 'w')
file.write(MAILOUT)
file.close()
logger.info("written to temporary mail file")
#
#
#
def sshd():
logger.debug("starting sshd section")
opentag('div', 1, 'sshd', 'section')
matches = re.findall('.*sshd.*Accepted publickey for .* from .*', readlog('auth')) # get all logins
users = [] # list of users with format [username, number of logins] for each item
data = []
num = sum(1 for x in matches) # total number of logins
for match in matches:
entry = re.search('^.*publickey\sfor\s(\w*)\sfrom\s(\S*)', match) # [('user', 'ip')]
user = entry.group(1)
ip = entry.group(2)
userhost = user + '@' + resolve(ip)
exists = [i for i, item in enumerate(users) if re.search(userhost, item[0])]
if (exists == []):
users.append([userhost, 1])
else:
users[exists[0]][1] += 1
writetitle('sshd')
subtitle = plural('login', num) + ' from'
if (len(users) == 1): # if only one user, do not display no of logins for this user
logger.debug("found " + str(len(matches)) + " ssh logins for user " + users[0][0])
subtitle += ' ' + users[0][0]
writedata(subtitle)
else:
for user in users:
data.append(user[0] + ' (' + str(user[1]) + ')')
if len(data) > MAXLIST: # if there are lots of users, truncate them
data.append('+ ' + str(len(users) - MAXLIST - 1) + " more")
break
logger.debug("found " + str(len(matches)) + " ssh logins for users " + str(data))
writedata(subtitle, data)
closetag('div', 1)
logger.info("finished sshd section")
#
#
#
def sudo():
logger.debug("starting sudo section")
opentag('div', 1, 'sudo', 'section')
umatches = re.findall('.*sudo:session\): session opened.*', readlog('auth'))
num = sum(1 for line in umatches) # total number of sessions
users = []
data = []
for match in umatches:
user = re.search('.*session opened for user root by (\S*)\(uid=.*\)', match).group(1)
exists = [i for i, item in enumerate(users) if re.search(user, item[0])]
if (exists == []):
users.append([user, 1])
else:
users[exists[0]][1] += 1
commands = []
cmatches = re.findall('sudo:.*COMMAND\=(.*)', readlog('auth'))
for cmd in cmatches:
commands.append(cmd)
logger.debug("found the following commands: " + str(commands))
writetitle("sudo")
subtitle = plural("sudo session", num) + " for"
if (len(users) == 1):
logger.debug("found " + str(num) + " sudo session(s) for user " + str(users[0]))
subtitle += ' ' + users[0][0]
writedata(subtitle)
else:
for user in users:
data.append(user[0] + ' (' + str(user[1]) + ')')
logger.debug("found " + str(num) + " sudo sessions for users " + str(data))
writedata(subtitle, data)
if (len(commands) > 0):
commands = addtag(commands, 'code')
commands = orderbyfreq(commands)
commands = truncl(commands, CMDNO)
writedata("top sudo commands", [c for c in commands])
closetag('div', 1)
logger.info("finished sudo section")
#
#
#
def cron():
logger.debug("starting cron section")
opentag('div', 1, 'cron', 'section')
matches = re.findall('.*CMD\s*\(\s*(?!.*cd)(.*)\)', readlog('cron'))
num = sum(1 for line in matches)
commands = []
for match in matches:
commands.append(str(match))
# commands.append([str(match)for match in matches])
logger.debug("found cron command " + str(commands))
logger.info("found " + str(num) + " cron jobs")
subtitle = str(num) + " cron jobs run"
writetitle("cron")
writedata(subtitle)
if (matches > 0):
commands = addtag(commands, 'code')
commands = orderbyfreq(commands)
commands = truncl(commands, CMDNO)
writedata("top cron commands", [c for c in commands])
closetag('div', 1)
logger.info("finished cron section")
#
#
#
def nameget():
logger.debug("starting nameget section")
opentag('div', 1, 'nameget', 'section')
syslog = readlog('sys')
failed = re.findall('.*nameget.*downloading of (.*) from .*failed.*', syslog)
n_f = sum(1 for i in failed)
l_f = []
for i in failed:
l_f.append(i)
logger.debug("the following downloads failed: " + str(l_f))
succ = re.findall('.*nameget.*downloaded\s(.*)', syslog)
n_s = sum(1 for i in succ)
l_s = []
for i in succ:
l_s.append(i)
logger.debug("the following downloads succeeded: " + str(l_f))
logger.debug("found " + str(n_s) + " successful downloads, and " + str(n_f) + " failed attempts")
writetitle("nameget")
writedata(str(n_s) + " succeeded", truncl(orderbyfreq(l_s), CMDNO))
writedata(str(n_f) + " failed", truncl(orderbyfreq(l_f), CMDNO))
closetag('div', 1)
logger.info("finished nameget section")
#
#
#
def httpd():
logger.info("starting httpd section")
opentag('div', 1, 'httpd', 'section')
accesslog = readlog("httpd/access.log")
a = len(accesslog.split('\n'))
errorlog = readlog("httpd/error.log")
e = len(errorlog.split('\n'))
data_b = 0
ips = []
files = []
useragents = []
errors = []
notfound = []
unprivileged = []
for line in accesslog.split('\n'):
fields = re.search('^(\S*) .*GET (\/.*) HTTP/\d\.\d\" 200 (\d*) \"(.*)\".*\((.*)\;', line)
try:
ips.append(fields.group(1))
files.append(fields.group(2))
useragents.append(fields.group(5))
logger.debug("transferred " + fields.group(3) + " bytes in this request")
data_b += int(fields.group(3))
logger.debug("data_b is now " + str(data_b))
except Exception as error:
if type(error) is AttributeError:
logger.debug("attributeerrror: " + str(error))
else:
logger.warning("error processing httpd access log: " + str(error))
logger.debug(str(data_b) + " bytes transferred")
data_h = parsesize(data_b)
writetitle("apache")
logger.debug("httpd has transferred " + str(data_b) + " bytes in response to " + str(a) + " requests with " + str(e) + " errors")
if (a > 0):
logger.debug("found the following requests: " + str(files))
files = addtag(files, 'code')
files = orderbyfreq(files)
files = truncl(files, CMDNO)
writedata(str(a) + " requests", files)
if (ips != None):
logger.debug("found the following ips: " + str(ips))
ips = addtag(ips, 'code')
ips = orderbyfreq(ips)
n_ip = str(len(ips))
ips = truncl(ips, CMDNO)
writedata(n_ip + " unique clients", ips)
if (useragents != None):
logger.debug("found the following useragents: " + str(useragents))
useragents = addtag(useragents, 'code')
useragents = orderbyfreq(useragents)
n_ua = str(len(useragents))
useragents = truncl(useragents, CMDNO)
writedata(n_ua + " unique devices", useragents)
writedata(data_h + " transferred")
writedata(str(e) + " errors")
closetag('div', 1)
logger.info("finished httpd section")
#
#
#
def httpdsession():
# logger.debug("starting httpd section")
opentag('div', 1, 'httpd', 'section')
httpdlog = requests.get(HTTPDSTATUS).content
uptime = re.search('.*uptime: (.*)<', httpdlog).group(1)
uptime = re.sub(' minute[s]', 'm', uptime)
uptime = re.sub(' second[s]', 's', uptime)
uptime = re.sub(' day[s]', 's', uptime)
uptime = re.sub(' month[s]', 'mo', uptime)
accesses = re.search('.*accesses: (.*) - .*', httpdlog).group(1)
traffic = re.search('.*Traffic: (.*)', httpdlog).group(1)
return("
httpd session: up " + uptime + ", " + accesses + " requests, " + traffic + " transferred")
closetag('div', 1)
# logger.info("finished httpd section")
#
#
#
def smbd():
logger.debug("starting smbd section")
opentag('div', 1, 'smbd', 'section')
files = glob.glob(SMBDDIR + "/log.*[!\.gz][!\.old]") # find list of logfiles
logger.debug("found log files " + str(files))
n_auths = 0 # total number of logins from all users
sigma_auths = [] # contains users
output = ""
for file in files: # one log file for each client
logger.debug("looking at file " + file)
# find the machine (ip or hostname) that this file represents
ip = re.search('log\.(.*)', file).group(1) # get ip or hostname from file path (/var/log/samba/log.host)
host = resolve(ip)
# count number of logins from each user
matches = re.findall('.*sam authentication for user \[(.*)\] succeeded.*', readlog(file))
for match in matches:
userhost = match + "@" + host
sigma_auths.append(userhost)
# exists = [i for i, item in enumerate(sigma_auths) if re.search(userhost, item[0])]
# if (exists == []):
# sigma_auths.append([userhost, 1])
# else:
# sigma_auths[exists[0]][1] += 1
n_auths += 1
writetitle("samba")
subtitle = plural("login", n_auths) + " from"
if (len(sigma_auths) == 1): # if only one user, do not display no of logins for this user
subtitle += ' ' + sigma_auths[0][0]
writedata(subtitle)
else: # multiple users
sigma_auths = orderbyfreq(sigma_auths)
sigma_auths = truncl(sigma_auths, CMDNO)
logger.debug("found " + str(n_auths) + " samba logins for users " + str(sigma_auths))
writedata(subtitle, sigma_auths)
closetag('div', 1)
logger.info("finished smbd section")
#
#
#
def postfix():
logger.debug("starting postfix section")
opentag('div', 1, 'postfix', 'section')
messages = re.findall('.*from\=<(.*)>, size\=(\d*),.*\n.*to=<(.*)>', readlog('postfix'))
r = []
s = []
size = 0
for message in messages:
r.append(message[2])
s.append(message[0])
size += int(message[1])
# size = sum([int(x) for x in messages])
size = parsesize(size)
n = str(len(messages))
writetitle("postfix")
if (len(r) > 0):
s = list(set(r)) # unique recipients
if (len(s) > 1):
r = orderbyfreq(r)
r = truncl(r, CMDNO)
writedata(n + " messages sent to", r)
else:
writedata(n + " messages sent to " + r[0])
else:
writedata(n + " messages sent")
writedata("total of " + size)
closetag('div', 1)
logger.info("finished postfix section")
#
#
#
def zfs():
logger.debug("starting zfs section")
opentag('div', 1, 'zfs', 'section')
zfslog = readlog('zfs')
logger.debug("zfs log is " + zfslog)
logger.debug("got zfs logfile\n" + zfslog + "---end log---")
pool = re.search('.*---\n(\w*)', zfslog).group(1)
scrub = re.search('.*scrub repaired (\d*) in \d*h\d*m with (\d*) errors on (\S*\s)(\S*)\s(\d+\s)', zfslog)
iostat = re.search('.*---\n\w*\s*(\S*)\s*(\S*)\s', zfslog)
scrubrepairs = scruberrors = scrubdate = None
try:
scrubrepairs = scrub.group(1)
scruberrors = scrub.group(2)
scrubdate = scrub.group(3) + scrub.group(5) + scrub.group(4)
except:
logger.debug("error getting scrub data")
alloc = iostat.group(1)
free = iostat.group(2)
writetitle("zfs")
if (scrubdate != None):
subtitle = "Scrub of " + pool + " on " + scrubdate
data = [scrubrepairs + " repaired", scruberrors + " errors", alloc + " used", free + " free"]
else:
subtitle = pool
data = [alloc + " used", free + " free"]
writedata(subtitle, data)
closetag('div', 1)
logger.info("finished zfs section")
#
#
#
def temp():
logger.debug("starting temp section")
opentag('div', 1, 'temp', 'section')
sensors.init()
coretemps = []
pkgtemp = 0
systemp = 0
try:
print(sensors.iter_detected_chips())
for chip in sensors.iter_detected_chips():
for feature in chip:
if "Core" in feature.label:
coretemps.append([feature.label, feature.get_value()])
logger.debug("found core " + feature.label + " at temp " + str(feature.get_value()))
if "CPUTIN" in feature.label:
pkgtemp = str(feature.get_value())
logger.debug("found cpu package at temperature " + pkgtemp)
if "SYS" in feature.label:
systemp = feature.get_value()
logger.debug("found sys input " + feature.label + " at temp " + str(feature.get_value()))
core_avg = reduce(lambda x, y: x[1] + y[1], coretemps) / len(coretemps)
logger.debug("average cpu temp is " + str(core_avg))
coretemps.append(["avg", str(core_avg)])
coretemps.append(["pkg", pkgtemp])
coretemps = [x[0] + ": " + str(x[1]) + DEG for x in coretemps]
finally:
sensors.cleanup()
# For this to work, `hddtemp` must be running in daemon mode.
# Start it like this (bash): sudo hddtemp -d /dev/sda /dev/sdX...
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost',HDDTEMPPORT))
output = s.recv(4096)
output += s.recv(4096)
s.close()
hddtemps = []
for drive in re.split('\|{2}', output):
try:
fields = re.search('\|*(/dev/sd.)\|.*\|(\d+)\|(.)', drive)
name = fields.group(1)
temp = float(fields.group(2))
units = fields.group(3)
hddtemps.append(drivetemp(name, temp, units))
except:
pass
hddtotal = 0
data = []
for drive in hddtemps:
data.append(drive.name + ': ' + str(drive.temp) + drive.units)
logger.debug("found disk " + drive.name + " at " + str(drive.temp))
hddtotal += drive.temp
logger.debug("found " + str(len(hddtemps)) + " disks")
logger.debug("sum of disk temps is " + str(hddtotal))
hddavg = hddtotal/float(len(hddtemps))
logger.debug("avg disk temp is " + str(hddavg))
data.append("avg: " + str(hddavg))
writetitle("temperatures")
if (systemp != 0):
writedata("sys: " + str(systemp) + DEG)
if (coretemps != ''):
writedata("cores", coretemps)
if (hddtemps != ''):
writedata("disks", data)
closetag('div', 1)
logger.info("finished temp section")
#
#
#
def du():
logger.debug("starting du section")
opentag('div', 1, 'du', 'section')
out = []
content = readlog('alloc')
contentnew = ""
for p in DUPATHS:
alloc_f = getusage(p).alloc
delta = None
try:
alloc_i = re.search(p + '\t(.*)\n', content).group(1)
delta = alloc_f - float(alloc_i)
except:
pass
logger.debug("delta is " + str(delta))
if (delta == None):
out.append([p, "used " + parsesize(alloc_f)])
else:
out.append([p, "used " + parsesize(alloc_f), "delta " + parsesize(delta)])
contentnew += (p + '\t' + str(alloc_f) + '\n')
writelog('alloc', contentnew)
writetitle("du")
logger.debug("disk usage data is " + str(out))
for path in out:
writedata(path[0], [p for p in path[1:]])
closetag('div', 1)
logger.info("finished du section")
#
#
#
timenow = time.strftime("%H:%M:%S")
datenow = time.strftime("%x")
pathfilter = {"auth": AUTHPATH, "cron": CRONPATH, "sys": SYSPATH, "postfix": POSTFIXPATH, "smb": SMBDDIR, "zfs": ZFSPATH, "alloc": ALLOCPATH, "httpd": HTTPDDIR, "header": HEADERPATH}
pathfilter = dict((re.escape(k), v) for k, v in pathfilter.iteritems())
pathpattern = re.compile("|".join(pathfilter.keys()))
varfilter = {"$title$": TITLE, "$date$": datenow, "$time$": timenow, "$hostname$": hostname(), "$version$": VERSION}
varfilter = dict((re.escape(k), v) for k, v in varfilter.iteritems())
varpattern = re.compile("|".join(varfilter.keys()))
__main__()