-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
227 additions
and
115 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,112 @@ | ||
#! /usr/bin/env python3 | ||
|
||
import re | ||
import os | ||
import re | ||
import sys | ||
import glob | ||
import signal | ||
import logging | ||
import argparse | ||
import datetime | ||
|
||
from table import Table | ||
|
||
|
||
def human(s): | ||
if isinstance(s, datetime.timedelta): | ||
log.info(f'timedelta: {s!s}') | ||
secs = s.total_seconds() | ||
(days, secs) = divmod(secs, 24*60*60) | ||
(hours, secs) = divmod(secs, 60*60) | ||
(minutes, secs) = divmod(secs, 60) | ||
s = f'{int(hours):02}:{int(minutes):02}:{int(secs):02}' | ||
if days: | ||
s = f'{int(days)}-{s}' | ||
|
||
return s | ||
|
||
|
||
def extract_name_name_and_network(path): | ||
components = path[1:].split('/') | ||
return [log_extension_regexp.sub('', components[-1]), components[-2]] | ||
|
||
|
||
parser = argparse.ArgumentParser(description=sys.argv[0]) | ||
parser.add_argument('regexp', nargs='?', help='Regular expression to search log messages') | ||
parser.add_argument('-a', '--age', type=float, help='Number of days old') | ||
parser.add_argument('-n', '--name-regexp', help='Regular expression to search chat name') | ||
parser.add_argument('-i', '--ignore-case', action='store_true', help='Perform case-insensitive regular expression matching') | ||
parser.add_argument('-l', '--list', action='store_true', help='List users') | ||
import bruno_tools | ||
|
||
def log_files(dir): | ||
ret = list() | ||
for filename in os.listdir(os.path.expanduser(dir)): | ||
path = os.path.join(dir, filename) | ||
if os.path.isdir(path): | ||
ret += log_files(path) | ||
elif path.endswith('.log') and not filename.startswith('#'): | ||
ret.append(path) | ||
return ret | ||
|
||
parser = argparse.ArgumentParser(description='Search hexchat logs') | ||
parser.add_argument('regexp', nargs='?', help='Regular expression for which to search') | ||
parser.add_argument( | ||
'-d', '--days', | ||
type=float, | ||
help='Restrict messages to X days (negative for less than or equal, positive for greater than or equal)' | ||
) | ||
parser.add_argument('-v', '--verbose', action='count', help='Enable debugging') | ||
args = parser.parse_args() | ||
|
||
logging.basicConfig(format='%(asctime)s %(levelname)s %(pathname)s:%(lineno)d %(msg)s') | ||
log = logging.getLogger() | ||
log = logging.getLogger(sys.argv[0]) | ||
log.setLevel(logging.WARNING - (args.verbose or 0)*10) | ||
|
||
signal.signal(signal.SIGPIPE, lambda signum, stack_frame: exit(0)) | ||
|
||
""" | ||
[mrbruno@bruno-meerkat ~]$ head -30 /home/mrbruno/.config/hexchat/logs/undernet/blah.log | ||
**** BEGIN LOGGING AT Fri Mar 27 15:39:22 2020 | ||
. | ||
. | ||
. | ||
**** ENDING LOGGING AT Fri Mar 27 16:38:56 2020 | ||
*** BEGIN LOGGING AT Fri Nov 10 11:55:41 2023 | ||
[them has address ~user@host] | ||
Nov 10 11:55:43 * [them] (~ident@host): description | ||
Nov 10 11:55:43 * [them] #channel | ||
Nov 10 11:55:43 * [them] *.undernet.org :The Undernet Underworld | ||
Nov 10 11:55:43 * [them] idle 00:00:13, signon: Fri Nov 10 11:55:28 | ||
Nov 10 11:55:43 * [them] End of WHOIS list. | ||
Nov 10 11:55:48 <me> message | ||
Nov 10 11:56:29 <me> message | ||
Nov 10 11:56:49 <them> message | ||
Nov 10 11:56:57 * me action | ||
Nov 10 11:57:03 <me> message | ||
Nov 10 11:57:11 <them> message | ||
Nov 10 11:57:16 <me> message | ||
Nov 10 11:57:29 <me> messages | ||
""" | ||
|
||
now = datetime.datetime.now() | ||
channel_regexp = re.compile('^#') | ||
older = args.age is not None and args.age > 0 | ||
desired_age = datetime.timedelta(days=abs(args.age)) if args.age is not None else None | ||
msg_regexp = re.compile(args.regexp, flags=re.IGNORECASE if args.ignore_case else 0) if args.regexp is not None else None | ||
name_regexp = re.compile(args.name_regexp, flags=re.IGNORECASE) if args.name_regexp is not None else None # filenames are folded to lowercase | ||
begin_regexp = re.compile(r'^\*\*\*.+BEGIN\s+LOGGING\s+AT\s+\S{3}\s+\S{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+(\d{4})$') | ||
address_regexp = re.compile(r'^\[\S+ has address ([^]]+)\]$') | ||
common_timestamp = re.compile(r'^(\S{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+(.+)$') | ||
|
||
logging_regexp = re.compile(r'^\*\*\*\* (BEGIN|ENDING) LOGGING AT (\w{3}\s+\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s*$') | ||
timestamp_regexp = re.compile('^(\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2})\s+([<*].*)$') | ||
log_extension_regexp = re.compile(r'\.log$') | ||
|
||
start_time, start_pos = (None, None) | ||
end_time, end_pos = (None, None) | ||
last_time = None | ||
|
||
table = Table('Name', 'Network', 'Start', 'Duration') | ||
|
||
root = os.path.expanduser('~/.config/hexchat/logs') | ||
paths = glob.glob(os.path.join(root, '*/*.log')) | ||
log.info(f'{len(paths)} files from glob.glob()') | ||
for path in paths: | ||
done = False | ||
basename = os.path.basename(path) | ||
if len(set(extract_name_name_and_network(path))) == 2 and \ | ||
not channel_regexp.search(basename) and \ | ||
(not args.name_regexp or name_regexp.search(basename)): | ||
log.debug(f'Reading {path}') | ||
with open(path) as stream: | ||
lines = stream.read().splitlines() | ||
for pos in range(len(lines)): | ||
line = lines[pos] | ||
match = logging_regexp.search(line) | ||
if match: | ||
timestamp = datetime.datetime.strptime(match.group(2), '%a %b %d %H:%M:%S %Y') | ||
if match.group(1) == 'BEGIN': | ||
start_time, start_pos = (timestamp, pos) | ||
end_time = timestamp | ||
elif start_time is not None: | ||
end_pos = pos | ||
if end_time is None: | ||
end_time = timestamp | ||
now = datetime.datetime.now() | ||
|
||
if args.age is None or \ | ||
(older and now-start_time > desired_age) or \ | ||
(not older and now-end_time < desired_age): | ||
log.info(f'{start_time!s} {end_time!s} {path}') | ||
for curr in range(start_pos+1, end_pos): | ||
line = lines[curr] | ||
if msg_regexp is None or msg_regexp.search(line): | ||
match = timestamp_regexp.search(line) | ||
table = bruno_tools.Table('User', 'Network', 'Date', 'Message') | ||
|
||
for path in sorted(log_files(os.path.expanduser('~/hexchat/logs'))): | ||
tokens = path.split('/') | ||
filename = tokens[-1] | ||
user = filename[:-4] | ||
network = tokens[-2] | ||
if network != user: | ||
year = None | ||
address = None | ||
with open(path) as stream: | ||
for line in stream.read().splitlines(): | ||
# get year | ||
match = begin_regexp.search(line) | ||
if match: | ||
timestamp = datetime.datetime.strptime(match.group(1) + f' {start_time.year}', '%b %d %H:%M:%S %Y') | ||
end_time = timestamp | ||
year = match.group(1) | ||
else: | ||
timestamp = None | ||
|
||
if args.list: | ||
table.append(*(extract_name_name_and_network(path) + [str(start_time), human(end_time-start_time)])) | ||
done = True | ||
else: | ||
if timestamp: | ||
line = f'{timestamp.year}-{timestamp.month:02}-{timestamp.day:02} {timestamp.hour:02}:{timestamp.minute:02}:{timestamp.second:02} {match.group(2)}' | ||
print(f'{path}: {line}') | ||
if done: | ||
break | ||
|
||
start_time, start_pos = (None, None) | ||
end_time, end_pos = (None, None) | ||
else: | ||
# the did not denote start/end of logging | ||
match = timestamp_regexp.search(line) | ||
if match and start_time is not None: | ||
end_time = datetime.datetime.strptime(match.group(1) + f' {start_time.year}', '%b %d %H:%M:%S %Y') | ||
|
||
if done: | ||
break | ||
if done: | ||
continue | ||
|
||
if args.list: | ||
table.root.sort(key=lambda row: row['Start'], reverse=True) | ||
print(str(table), end='') | ||
# get address | ||
match = address_regexp.search(line) | ||
if match: | ||
address = match.group(1) | ||
else: | ||
# get common timestamp | ||
match = common_timestamp.search(line) | ||
if match: | ||
timestamp = datetime.datetime.strptime( | ||
year + ' ' + match.group(1), | ||
'%Y %b %d %H:%M:%S' | ||
) | ||
if args.days is not None: | ||
age = now - timestamp | ||
if args.days < 0: | ||
if age > datetime.timedelta(days=-args.days): | ||
continue | ||
elif age < datetime.timedelta(days=args.days): | ||
continue | ||
remain = match.group(2) | ||
if not remain.startswith('*'): | ||
message = remain | ||
if args.regexp and not re.search(args.regexp, message): | ||
continue | ||
table.add( | ||
user, | ||
network, | ||
timestamp.strftime('%Y-%m-%d %H:%M:%S'), | ||
message, | ||
) | ||
|
||
table.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#! /usr/bin/env python3 | ||
|
||
import re | ||
import os | ||
import sys | ||
import glob | ||
import signal | ||
import logging | ||
import argparse | ||
import datetime | ||
|
||
from table import Table | ||
|
||
|
||
def human(s): | ||
if isinstance(s, datetime.timedelta): | ||
log.info(f'timedelta: {s!s}') | ||
secs = s.total_seconds() | ||
(days, secs) = divmod(secs, 24*60*60) | ||
(hours, secs) = divmod(secs, 60*60) | ||
(minutes, secs) = divmod(secs, 60) | ||
s = f'{int(hours):02}:{int(minutes):02}:{int(secs):02}' | ||
if days: | ||
s = f'{int(days)}-{s}' | ||
|
||
return s | ||
|
||
|
||
def extract_name_name_and_network(path): | ||
components = path[1:].split('/') | ||
return [log_extension_regexp.sub('', components[-1]), components[-2]] | ||
|
||
|
||
parser = argparse.ArgumentParser(description=sys.argv[0]) | ||
parser.add_argument('regexp', nargs='?', help='Regular expression to search log messages') | ||
parser.add_argument('-a', '--age', type=float, help='Number of days old') | ||
parser.add_argument('-n', '--name-regexp', help='Regular expression to search chat name') | ||
parser.add_argument('-i', '--ignore-case', action='store_true', help='Perform case-insensitive regular expression matching') | ||
parser.add_argument('-l', '--list', action='store_true', help='List users') | ||
parser.add_argument('-v', '--verbose', action='count', help='Enable debugging') | ||
args = parser.parse_args() | ||
|
||
logging.basicConfig(format='%(asctime)s %(levelname)s %(pathname)s:%(lineno)d %(msg)s') | ||
log = logging.getLogger() | ||
log.setLevel(logging.WARNING - (args.verbose or 0)*10) | ||
|
||
signal.signal(signal.SIGPIPE, lambda signum, stack_frame: exit(0)) | ||
|
||
""" | ||
[mrbruno@bruno-meerkat ~]$ head -30 /home/mrbruno/.config/hexchat/logs/undernet/blah.log | ||
**** BEGIN LOGGING AT Fri Mar 27 15:39:22 2020 | ||
. | ||
. | ||
. | ||
**** ENDING LOGGING AT Fri Mar 27 16:38:56 2020 | ||
""" | ||
|
||
now = datetime.datetime.now() | ||
channel_regexp = re.compile('^#') | ||
older = args.age is not None and args.age > 0 | ||
desired_age = datetime.timedelta(days=abs(args.age)) if args.age is not None else None | ||
msg_regexp = re.compile(args.regexp, flags=re.IGNORECASE if args.ignore_case else 0) if args.regexp is not None else None | ||
name_regexp = re.compile(args.name_regexp, flags=re.IGNORECASE) if args.name_regexp is not None else None # filenames are folded to lowercase | ||
|
||
logging_regexp = re.compile(r'^\*\*\*\* (BEGIN|ENDING) LOGGING AT (\w{3}\s+\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s*$') | ||
timestamp_regexp = re.compile('^(\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2})\s+([<*].*)$') | ||
log_extension_regexp = re.compile(r'\.log$') | ||
|
||
start_time, start_pos = (None, None) | ||
end_time, end_pos = (None, None) | ||
last_time = None | ||
|
||
table = Table('Name', 'Network', 'Start', 'Duration') | ||
|
||
root = os.path.expanduser('~/.config/hexchat/logs') | ||
paths = glob.glob(os.path.join(root, '*/*.log')) | ||
log.info(f'{len(paths)} files from glob.glob()') | ||
for path in paths: | ||
done = False | ||
basename = os.path.basename(path) | ||
if len(set(extract_name_name_and_network(path))) == 2 and \ | ||
not channel_regexp.search(basename) and \ | ||
(not args.name_regexp or name_regexp.search(basename)): | ||
log.debug(f'Reading {path}') | ||
with open(path) as stream: | ||
lines = stream.read().splitlines() | ||
for pos in range(len(lines)): | ||
line = lines[pos] | ||
match = logging_regexp.search(line) | ||
if match: | ||
timestamp = datetime.datetime.strptime(match.group(2), '%a %b %d %H:%M:%S %Y') | ||
if match.group(1) == 'BEGIN': | ||
start_time, start_pos = (timestamp, pos) | ||
end_time = timestamp | ||
elif start_time is not None: | ||
end_pos = pos | ||
if end_time is None: | ||
end_time = timestamp | ||
|
||
if args.age is None or \ | ||
(older and now-start_time > desired_age) or \ | ||
(not older and now-end_time < desired_age): | ||
log.info(f'{start_time!s} {end_time!s} {path}') | ||
for curr in range(start_pos+1, end_pos): | ||
line = lines[curr] | ||
if msg_regexp is None or msg_regexp.search(line): | ||
match = timestamp_regexp.search(line) | ||
if match: | ||
timestamp = datetime.datetime.strptime(match.group(1) + f' {start_time.year}', '%b %d %H:%M:%S %Y') | ||
end_time = timestamp | ||
else: | ||
timestamp = None | ||
|
||
if args.list: | ||
table.append(*(extract_name_name_and_network(path) + [str(start_time), human(end_time-start_time)])) | ||
done = True | ||
else: | ||
if timestamp: | ||
line = f'{timestamp.year}-{timestamp.month:02}-{timestamp.day:02} {timestamp.hour:02}:{timestamp.minute:02}:{timestamp.second:02} {match.group(2)}' | ||
print(f'{path}: {line}') | ||
if done: | ||
break | ||
|
||
start_time, start_pos = (None, None) | ||
end_time, end_pos = (None, None) | ||
else: | ||
# the did not denote start/end of logging | ||
match = timestamp_regexp.search(line) | ||
if match and start_time is not None: | ||
end_time = datetime.datetime.strptime(match.group(1) + f' {start_time.year}', '%b %d %H:%M:%S %Y') | ||
|
||
if done: | ||
break | ||
if done: | ||
continue | ||
|
||
if args.list: | ||
table.root.sort(key=lambda row: row['Start'], reverse=True) | ||
print(str(table), end='') |