#!/usr/bin/python
#
# Log slicing utility. Cleanly slices the logs within a time range
# These are real time ranges, it finds the closest possible timeline in the log
# And slices them +/- the range you wish to slice
# It accepts pci and ctrace based timestamps.
# Version 1.0
# Vaithiyanathan Sundaram
# Copyright (c) 2016 by Cisco Systems, Inc.
#
######################################################

from optparse import OptionParser
import datetime
from collections import defaultdict
import sys
import re
import time
import os

epilogue = 'Sleek tool to analyze logs between time ranges. Lets say you pick' \
           'a ctrace. You know something happened at 2016-10-08-14.02.00 and' \
           'you want to know what has happened some time before. Simple' \
           'logslice -t "2016-10-08-14.02.00" -f <file> -r 2M -l ctrace'

parser = OptionParser(version='v1.0', epilog=epilogue)
parser.add_option("-t", "--timsetamp", dest="timestamp",
                  help="Timestamp in syslog format Oct 8 17:49:15, "
                       "use quotes")
parser.add_option("-f", "--file", dest="file", help="file")
parser.add_option("-r", "--range", dest="range", help="Range")
parser.add_option("-l", "--logtype", dest="log_type",
                  help="Log type, use pci, ctrace")

options, args = parser.parse_args()

expressions = \
    {
        'pci': {
            'inlog_pattern': '(\w+\s*\w+\s*\d\d:\d\d:\d\d).*\[\s*(\d+.\d+)\].*',
            'time_pattern': '%b %d %H:%M:%S'
        },

        'ctrace': {
            'inlog_pattern': '(\w+-\w+-\w+:\d\d.\d\d.\d\d).*',
            'time_pattern': '%Y-%m-%d:%H.%M.%S'
        },

        'pcilib': {
            'inlog_pattern': '.*\[(\d\d\/\d\d\/\d\d\s*\d\d:\d\d:\d\d)\].*',
            'time_pattern': '%m/%d/%y %H:%M:%S'
        }
    }

if options.log_type and not options.log_type in expressions.keys():
    print "Unknown log type use the following"
    print '\n'.join(expressions.keys())
    sys.exit(1)

if not options.log_type:
    options.log_type = 'pci'


def fetch_log():
    try:
        timestamp = datetime.datetime.strptime(options.timestamp,
                                               expressions[options.log_type][
                                                   'time_pattern'])
    except Exception as e:
        print "Wrong timestamp format, check how show log shows you have to " \
              "paste that without microseconds"
        if 'unconverted' in e.message:
            print "HINT: Try to see if your input timestamp matches this %s. " \
                  "May be remove the nano seconds" % \
                  expressions[options.log_type]['time_pattern']
        sys.exit(1)

    trange = options.range
    if 'M' in trange:
        print "You asked for %s minutes... converting it!" % trange
        trange = int(trange.replace('M', '')) * 60
    elif 'H' in trange:
        print "You asked for %s hours... converting it!" % trange
        trange = int(trange.replace('M', '')) * 3600
    elif 'D' in trange:
        print "You asked for %s days... converting it!" % trange
        trange = int(trange.replace('M', '')) * 3600 * 24

    all = []
    data = defaultdict(list)
    try:
        with open(options.file, 'r') as f:
            all = f.readlines()
    except IOError:
        print "File %s not found" % options.file
        sys.exit(1)

    for line in all:
        s = re.search(expressions[options.log_type]['inlog_pattern'], line)
        if s:
            clock_time = s.groups()[0]
            # mon_time = s.groups()[1]
            data[int((datetime.datetime.strptime(clock_time,
                     expressions[options.log_type][
                 'time_pattern']) - timestamp).total_seconds())].append(line)

    if not all:
        print "Found nothing, either you are using the wrong log type or " \
              "there is nothing to show"
        sys.exit(1)

    # Slice into two lists
    times = sorted(data.keys())
    direct_match = min(times, key=lambda v: abs(0 - v))
    if direct_match:
        print "Couldn't find a direct match for your time but found " \
              "%d seconds +/-" % direct_match
    else:
        print "Found a direct match for your time"
    zero_idx = times.index(direct_match)
    if times[0:zero_idx]:
        low_delta = min(times[0:zero_idx], key=lambda v: abs(-int(trange) - v))
    else:
        print "There is no event before requested time %s" % options.timestamp
        low_delta = None

    if times[zero_idx + 1:]:
        high_delta = min(times[zero_idx + 1:],
                         key=lambda v: abs(int(trange) - v))
    else:
        print "There is no event after requested time %s" % options.timestamp
        high_delta = None

    print "Approximate range found from log is +%s to %s" % \
          (high_delta if high_delta else 'END',
           low_delta if low_delta else 'START')

    out_str = ''

    if low_delta:
        for each in times[times.index(low_delta)-1:zero_idx]:
            out_str += ''.join(data[each])

    if high_delta:
        for each in times[zero_idx + 1:times.index(high_delta)+1]:
            out_str += ''.join(data[each])

    print "Going to print %s lines - be ready! Last time to press ctrl+c" \
          % len(out_str.split('\n'))
    y = ''
    while y != 'q':
        try:
            y = raw_input(
                'Do you wish to print them? If you wish to write to file, '
                'type m. [y/n/m/q]')
            if y == 'y':
                print out_str
                sys.exit(0)
            elif y == 'n':
                sys.exit(0)
            elif y == 'm':
                fname = raw_input('Enter the absolute path for file')
                try:
                    with open(fname, 'w') as f:
                        f.write(out_str)
                        f.close()
                except IOError:
                    print "File error retry"
                    continue
                sys.exit(0)
            else:
                continue
        except KeyboardInterrupt:
            print "\n\nOkay bye!"
            sys.exit(0)


fetch_log()
