#!/usr/bin/env python help = """ === colorize.py === version: 1.2 author: OpenSource@dwaves.de date-creation: 2024-06-18 last-tested: 2024-07-24 description: highlight keywords such as "error" (RED) and "warning" (ORANGE) in console output to easier spot problems in log files usage: # it also accepts /path/to/files.log as arguments /usr/bin/python3 /scripts/colorize.py /path/to/file1.log /path/to/file2.log /path/to/file3.log # live log monitoring all logs under /var/log find /var/log/* -type f \( -name "*" \) ! -path '*.gz*' -exec tail -n0 -f {} + | /usr/bin/python3 /scripts/colorize.py # live log monitoring all files.log in any directory find /* -type f \( -name "*.log" \) ! -path '*.gz*' -exec tail -n0 -f {} + | /usr/bin/python3 /scripts/colorize.py # output last 300 lines of file.log with error and warning highlighting tail -300 /path/to/error.log | /usr/bin/python3 /scripts/colorize.py | less -R # output last 300 lines of all files.log under /var with error and warning highlighting find /var/ -type f -iname "*.log" -exec tail -n 300 {} + | /usr/bin/python3 /scripts/colorize.py | less -R changelog: ok now possible to simply pass /path/to/file.log and it will output colorized ok also highlight success bright green ok does not stumble anymore on changes to binary files """ import sys import os import re import traceback # RESET: END = '\033[0m' = set the color back to default class colors: BLACK = '\033[30m' RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' BLUE = '\033[34m' MAGENTA = '\033[35m' CYAN = '\033[36m' WHITE = '\033[37m' BRIGHT_BLACK = '\033[90m' BRIGHT_RED = '\033[91m' BRIGHT_GREEN = '\033[92m' BRIGHT_YELLOW = '\033[93m' BRIGHT_BLUE = '\033[94m' BRIGHT_MAGENTA = '\033[95m' BRIGHT_CYAN = '\033[96m' BRIGHT_WHITE = '\033[97m' BG_BLACK = '\033[40m' BG_RED = '\033[41m' BG_GREEN = '\033[42m' BG_YELLOW = '\033[43m' BG_BLUE = '\033[44m' BG_MAGENTA = '\033[45m' BG_CYAN = '\033[46m' BG_WHITE = '\033[47m' BG_BRIGHT_BLACK = '\033[100m' BG_BRIGHT_RED = '\033[101m' BG_BRIGHT_GREEN = '\033[102m' BG_BRIGHT_YELLOW = '\033[103m' BG_BRIGHT_BLUE = '\033[104m' BG_BRIGHT_MAGENTA = '\033[105m' BG_BRIGHT_CYAN = '\033[106m' BG_BRIGHT_WHITE = '\033[107m' END = '\033[0m' BOLD = '\033[1m' DIM = '\033[2m' ITALIC = '\033[3m' UNDERLINE = '\033[4m' BLINK = '\033[5m' INVERSE = '\033[7m' HIDDEN = '\033[8m' STRIKETHROUGH = '\033[9m' UNDERLINE_DOUBLE = '\033[21m' OVERLINE = '\033[53m' BRIGHT_BLACK_BG_BLACK = '\033[90;40m' BRIGHT_BLACK_BG_RED = '\033[90;41m' BRIGHT_BLACK_BG_GREEN = '\033[90;42m' BRIGHT_BLACK_BG_YELLOW = '\033[90;43m' BRIGHT_BLACK_BG_BLUE = '\033[90;44m' BRIGHT_BLACK_BG_MAGENTA = '\033[90;45m' BRIGHT_BLACK_BG_CYAN = '\033[90;46m' BRIGHT_BLACK_BG_WHITE = '\033[90;47m' BRIGHT_RED_BG_BLACK = '\033[91;40m' BRIGHT_RED_BG_RED = '\033[91;41m' BRIGHT_RED_BG_GREEN = '\033[91;42m' BRIGHT_RED_BG_YELLOW = '\033[91;43m' BRIGHT_RED_BG_BLUE = '\033[91;44m' BRIGHT_RED_BG_MAGENTA = '\033[91;45m' BRIGHT_RED_BG_CYAN = '\033[91;46m' BRIGHT_RED_BG_WHITE = '\033[91;47m' BRIGHT_GREEN_BG_BLACK = '\033[92;40m' BRIGHT_GREEN_BG_RED = '\033[92;41m' BRIGHT_GREEN_BG_GREEN = '\033[92;42m' BRIGHT_GREEN_BG_YELLOW = '\033[92;43m' BRIGHT_GREEN_BG_BLUE = '\033[92;44m' BRIGHT_GREEN_BG_MAGENTA = '\033[92;45m' BRIGHT_GREEN_BG_CYAN = '\033[92;46m' BRIGHT_GREEN_BG_WHITE = '\033[92;47m' BRIGHT_YELLOW_BG_BLACK = '\033[93;40m' BRIGHT_YELLOW_BG_RED = '\033[93;41m' BRIGHT_YELLOW_BG_GREEN = '\033[93;42m' BRIGHT_YELLOW_BG_YELLOW = '\033[93;43m' BRIGHT_YELLOW_BG_BLUE = '\033[93;44m' BRIGHT_YELLOW_BG_MAGENTA = '\033[93;45m' BRIGHT_YELLOW_BG_CYAN = '\033[93;46m' BRIGHT_YELLOW_BG_WHITE = '\033[93;47m' BRIGHT_BLUE_BG_BLACK = '\033[94;40m' BRIGHT_BLUE_BG_RED = '\033[94;41m' BRIGHT_BLUE_BG_GREEN = '\033[94;42m' BRIGHT_BLUE_BG_YELLOW = '\033[94;43m' BRIGHT_BLUE_BG_BLUE = '\033[94;44m' BRIGHT_BLUE_BG_MAGENTA = '\033[94;45m' BRIGHT_BLUE_BG_CYAN = '\033[94;46m' BRIGHT_BLUE_BG_WHITE = '\033[94;47m' BRIGHT_MAGENTA_BG_BLACK = '\033[95;40m' BRIGHT_MAGENTA_BG_RED = '\033[95;41m' BRIGHT_MAGENTA_BG_GREEN = '\033[95;42m' BRIGHT_MAGENTA_BG_YELLOW = '\033[95;43m' BRIGHT_MAGENTA_BG_BLUE = '\033[95;44m' BRIGHT_MAGENTA_BG_MAGENTA = '\033[95;45m' BRIGHT_MAGENTA_BG_CYAN = '\033[95;46m' BRIGHT_MAGENTA_BG_WHITE = '\033[95;47m' BRIGHT_CYAN_BG_BLACK = '\033[96;40m' BRIGHT_CYAN_BG_RED = '\033[96;41m' BRIGHT_CYAN_BG_GREEN = '\033[96;42m' BRIGHT_CYAN_BG_YELLOW = '\033[96;43m' BRIGHT_CYAN_BG_BLUE = '\033[96;44m' BRIGHT_CYAN_BG_MAGENTA = '\033[96;45m' BRIGHT_CYAN_BG_CYAN = '\033[96;46m' BRIGHT_CYAN_BG_WHITE = '\033[96;47m' BRIGHT_WHITE_BG_BLACK = '\033[97;40m' BRIGHT_WHITE_BG_RED = '\033[97;41m' BRIGHT_WHITE_BG_GREEN = '\033[97;42m' BRIGHT_WHITE_BG_YELLOW = '\033[97;43m' BRIGHT_WHITE_BG_BLUE = '\033[97;44m' BRIGHT_WHITE_BG_MAGENTA = '\033[97;45m' BRIGHT_WHITE_BG_CYAN = '\033[97;46m' BRIGHT_WHITE_BG_WHITE = '\033[97;47m' ORANGE = '\033[38;5;208m' BG_ORANGE = '\033[48;5;208m' # the keywords that the program will react to pattern_file = re.compile(re.escape("==>")) pattern_error = re.compile(re.escape("error"), re.IGNORECASE) pattern_warning = re.compile(re.escape("warning"), re.IGNORECASE) pattern_sshd = re.compile(re.escape("sshd"), re.IGNORECASE) pattern_user_root = re.compile(re.escape("user root"), re.IGNORECASE) pattern_fail = re.compile(re.escape("fail"), re.IGNORECASE) pattern_success = re.compile(re.escape("success"), re.IGNORECASE) # === apache2 related === pattern_http_success = re.compile(re.escape('" 200'), re.IGNORECASE) # apache2 http request 200 = file found pattern_http_moved = re.compile(re.escape('" 301'), re.IGNORECASE) # apache2 http request 301 = moved permanently = redirecting pattern_http_not_found = re.compile(re.escape('" 404'), re.IGNORECASE) # apache2 http request 404 = file not found on webserver def colorize(line): # line = line.strip() # removes leading/trailing whitespace # what color should be applied for what keyword if pattern_file.search(line): line = colors.CYAN + line + colors.END + "\n" elif pattern_error.search(line) or pattern_fail.search(line): line = colors.RED + line + colors.END + "\n" elif pattern_warning.search(line) or pattern_http_not_found.search(line): line = colors.ORANGE + line + colors.END + "\n" elif pattern_sshd.search(line): line = colors.ORANGE + line + colors.END + "\n" elif pattern_user_root.search(line): line = colors.YELLOW + line + colors.END + "\n" elif pattern_success.search(line) or pattern_http_success.search(line): line = colors.BRIGHT_GREEN + line + colors.END + "\n" return line def full_stack(): exc = sys.exc_info()[0] stack = traceback.extract_stack()[:-1] # remove the last one if exc is not None: # i.e. if an exception is present del stack[-1] # remove call of full_stack, the printed exception trc = 'Traceback (most recent call last):\n' stackstr = trc + ''.join(traceback.format_list(stack)) if exc is not None: stackstr += ' ' + traceback.format_exc().lstrip(trc) return stackstr def readFile(PATH_TO_FILE): try: if os.path.exists(PATH_TO_FILE): if os.path.isfile(PATH_TO_FILE): with open(PATH_TO_FILE, 'r') as file: # Loop over each line for line in file: # Process each line as needed print(colorize(line)) elif os.path.isdir(PATH_TO_FILE): print("error: path given is a directory") else: print(f"error: file {PATH_TO_FILE} does not exist") except FileNotFoundError: print(f"error: file {PATH_TO_FILE} does not exist") try: # input from file? (test if argument was passed then read that files) if sys.argv: for i in range(len(sys.argv)): if i == 0: # first element is path of this script continue print(f"==> {sys.argv[i]} <==") readFile(sys.argv[i]) if sys.stdin.buffer: # input piped via stdin line by line, handling decoding errors for raw_line in sys.stdin.buffer: try: # Decode line with error handling line = raw_line.decode('utf-8', errors='replace') # Colorize the line based on patterns line = colorize(line) # Print the colorized line sys.stdout.write(line) sys.stdout.flush() except Exception as e: print(full_stack()) print(f"error processing line: {e}", file=sys.stderr) except KeyboardInterrupt: pass except Exception as e: print(full_stack()) print(f"error: {e}", file=sys.stderr)