Last active
March 1, 2024 00:13
-
-
Save joe-sullivan/3ad7f000dfeefcd32b6d1f6c7c8c0a1b to your computer and use it in GitHub Desktop.
Simple logging decorator for python
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
#!/usr/bin/env python3 | |
# +------------------------------------------------------------------------------+ | |
# | HOW TO USE: from verbose import Log | | |
# +=================================== Basics ===================================+ | |
# | <Inputs> | | |
# | msg : used as the main message to output (this is the only required field) | | |
# | tag : used to conceptually group messages or add string identifier | | |
# | | | |
# | <Functions> | | |
# | note() : use instead of print() | | |
# | wrap() : decorator for methods (the message will be shown for each call) | | |
# | write() : writes messages to a log file (with timestamps) | | |
# | hack() : display the source of wrapped function as if it were being typed | | |
# | (this is a novelty wrapper to look like a "movie hacker") | | |
# | | | |
# | <Levels (colors)> | | |
# | DEFAULT : standard (default terminal text color) | | |
# | INFO : grey, use to show general notes | | |
# | DEBUG : blue, use to show debugging info | | |
# | ERROR : red, use to indicate errors | | |
# | SUCCESS : green, use to indicate success | | |
# | WARNING : yellow, use to indicate warnings | | |
# | IMPORTANT : magenta, use to bring attention to but not for errors | | |
# | HEADER : bblack, use to display function wrappers | | |
# | | | |
# +================================== Advanced ==================================+ | |
# | <Inputs> | | |
# | force : always print the log message (default=False) | | |
# | pad : character used to pad message to 80 characters (default=None) | | |
# | fmt : python format string (the following variables can be used) | | |
# | {tag} - the tag field | | |
# | {msg} - the msg field | | |
# | {args} - required function arguments | | |
# | {kwargs} - optional function arguments | | |
# | {pad} - character used to pad to 80 bytes | | |
# | | | |
# | <Temporarily disable logging> | | |
# | Log.ENABLED = False | | |
# | | | |
# | <Print the first argument to a function> | | |
# | @Log.wrap('argument1', fmt='{msg}: {args[0]}') | | |
# | def foo(x): | | |
# | pass # the value of x will now be logged when foo() is called | | |
# | | | |
# +------------------------------------------------------------------------------+ | |
import os, sys | |
import time | |
from functools import wraps | |
# for movie hacker wrapper | |
import inspect | |
try: # pip install Pygments | |
from pygments import highlight | |
from pygments.lexers import PythonLexer | |
from pygments.formatters import TerminalFormatter | |
except ModuleNotFoundError: | |
COLOR = False | |
else: | |
COLOR = True | |
class Log: | |
ENABLED = True | |
FILE_PATH = os.path.curdir # set to None to disable file log | |
FILE_LINE_LIMIT = 1000 # set to 0 for no limit | |
def __make_color(code): | |
return '\033[' + str(code) + 'm' | |
DEFAULT = {'name': 'DEFAULT', 'color': __make_color( 0)} # (terminal text) | |
INFO = {'name': 'INFO', 'color': __make_color( 1)} # grey | |
ERROR = {'name': 'ERROR', 'color': __make_color(31)} # red | |
SUCCESS = {'name': 'SUCCESS', 'color': __make_color(32)} # green | |
WARN = {'name': 'WARN', 'color': __make_color(33)} # yellow | |
DEBUG = {'name': 'DEBUG', 'color': __make_color(34)} # blue | |
IMPORTANT = {'name': 'IMPORTANT', 'color': __make_color(35)} # magenta | |
HEADER = {'name': 'HEADER', 'color': __make_color('30;1')} # bright black (requires 16 color support) | |
@staticmethod | |
def note(msg, tag='>', fmt='{tag} {msg}', args=[], kwargs={}, level=INFO, pad=None, force=False): | |
def color(msg): | |
"""Add color to strings""" | |
if os.name == 'posix': | |
return level['color'] + msg + Log.DEFAULT['color'] | |
else: | |
return msg | |
if Log.ENABLED or force: | |
formatted_msg = fmt.format(tag=tag, msg=msg, args=args, kwargs=kwargs) | |
if pad: | |
formatted_msg += ' ' | |
formatted_msg = formatted_msg.ljust(80, pad) | |
print(color(formatted_msg)) | |
@staticmethod | |
def wrap(msg, tag=None, fmt='[{tag}] {msg}: {args}{kwargs}', level=HEADER): | |
"""Decorator used to display log as '[tag] msg'""" | |
def print_msg(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
Log.note(msg, tag=tag or func.__name__, fmt=fmt, args=args, kwargs=kwargs, level=level) | |
return func(*args, **kwargs) | |
return wrapper | |
return print_msg | |
@staticmethod | |
def write(msg, tag='*', fmt='[{tag}] {msg}', level=INFO, args=[]): | |
"""Write log to file""" | |
if not Log.FILE_PATH: return | |
timestamp = time.strftime('%Y-%m-%d_%H:%M:%S') | |
log_msg = ' - '.join([timestamp, level['name'], fmt.format(tag=tag, msg=msg)]) | |
filename = os.path.join(Log.FILE_PATH, 'log') | |
if not os.path.isfile(filename): | |
Log.note('creating log file...', tag='+') | |
with open(filename, 'w'): pass | |
with open(filename, 'r+') as f: | |
data = f.readlines() | |
data.append(log_msg + os.linesep) | |
f.seek(0) | |
f.truncate() | |
f.writelines(data[-Log.FILE_LINE_LIMIT:]) | |
@staticmethod | |
def hack(func): | |
def print_type(s, speed=0.0003, force=False): | |
if not Log.ENABLED and not force: return | |
if COLOR: | |
s = highlight(s, PythonLexer(), TerminalFormatter(linenos=True)) | |
else: # green text | |
print('\033[32m', end='') | |
line = lambda c: Log.note('', tag='---', fmt='', pad=c) | |
line('~') | |
for c in s: | |
print(c, end='', flush=True) | |
time.sleep(speed) | |
line('~') | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
src = inspect.getsource(func) | |
# TODO: remove @Log.hack from src | |
try: | |
print_type(src) | |
except KeyboardInterrupt: | |
pass | |
finally: | |
print('\033[0m') # reset terminal color | |
return func(*args, **kwargs) | |
return wrapper | |
if __name__ == '__main__': | |
# sample usage | |
Log.ENABLED = True # comment this line to turn off logging | |
@Log.hack | |
@Log.wrap('this is a messages for the method') | |
def hello_world(an_arg, opt=None): | |
Log.note('hello world') | |
Log.note('a padded note', pad='*', level=Log.IMPORTANT) | |
Log.note('this will always be printed', level=Log.DEBUG, force=True) | |
Log.write('done', tag='verbose test', level=Log.SUCCESS) | |
hello_world(5, opt=1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment