-
-
Save beng/7817597 to your computer and use it in GitHub Desktop.
# Written by Brendan O'Connor, [email protected], www.anyall.org | |
# * Originally written Aug. 2005 | |
# * Posted to gist.github.com/16173 on Oct. 2008 | |
# Copyright (c) 2003-2006 Open Source Applications Foundation | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
import logging | |
import random | |
import re | |
import types | |
"""This file has been *heavily* modified to remove the use of global variables, implement | |
a logging class instead of relying on sys.stdout, remove the function log decorator, remove | |
the module log decorator, allow color changing on any log call, | |
allow indentation level changing on any log call, and PEP-8 formatting. | |
Copyright (C) 2013 Ben Gelb | |
""" | |
BLACK = "\033[0;30m" | |
BLUE = "\033[0;34m" | |
GREEN = "\033[0;32m" | |
CYAN = "\033[0;36m" | |
RED = "\033[0;31m" | |
PURPLE = "\033[0;35m" | |
BROWN = "\033[0;33m" | |
GRAY = "\033[0;37m" | |
BOLDGRAY = "\033[1;30m" | |
BOLDBLUE = "\033[1;34m" | |
BOLDGREEN = "\033[1;32m" | |
BOLDCYAN = "\033[1;36m" | |
BOLDRED = "\033[1;31m" | |
BOLDPURPLE = "\033[1;35m" | |
BOLDYELLOW = "\033[1;33m" | |
WHITE = "\033[1;37m" | |
NORMAL = "\033[0m" | |
class Logger(object): | |
def __init__(self, indent_string=' ', indent_level=0, *args, **kwargs): | |
self.__log = None | |
self.indent_string = indent_string | |
self.indent_level = indent_level | |
@property | |
def __logger(self): | |
if not self.__log: | |
FORMAT = '%(asctime)s - %(levelname)s - %(message)s' | |
self.__log = logging.getLogger(__name__) | |
self.__log.setLevel(logging.DEBUG) | |
handler = logging.StreamHandler() | |
handler.setLevel(logging.DEBUG) | |
handler.setFormatter(logging.Formatter(FORMAT)) | |
self.__log.addHandler(handler) | |
return self.__log | |
def _log_levels(self, level): | |
return { | |
'debug': 10, | |
'info': 20, | |
'warning': 30, | |
'critical': 40, | |
'error': 50 | |
}.get(level, 'info') | |
def update_indent_level(self, val): | |
self.indent_level = val | |
def log(self, message, color=None, log_level='info', indent_level=None, *args, **kwargs): | |
msg_params = { | |
'color': color or NORMAL, | |
'indent': self.indent_string * (indent_level or self.indent_level), | |
'msg': message | |
} | |
_message = "{color} {indent}{msg}".format(**msg_params) | |
self.__logger.log(self._log_levels(log_level), _message) | |
def format_args(args, kwargs): | |
""" | |
makes a nice string representation of all the arguments | |
""" | |
allargs = [] | |
for item in args: | |
allargs.append('%s' % str(item)) | |
for key, item in kwargs.items(): | |
allargs.append('%s=%s' % (key, str(item))) | |
formattedArgs = ', '.join(allargs) | |
if len(formattedArgs) > 150: | |
return formattedArgs[:146] + " ..." | |
return formattedArgs | |
def log_method(method, display_name=None): | |
"""use this for class or instance methods, it formats with the object out front.""" | |
display_name = display_name or method.__name__ | |
def _wrapper(self, *args, **kwargs): | |
arg_str = format_args(args, kwargs) | |
message = "{self_str}.{cls_color}{method_display_name}{method_color} ({arg_str})".format(**{ | |
'self_str': str(self), | |
'cls_color': BROWN, | |
'method_display_name': "{} - {}".format(method.__name__, display_name), | |
'method_color': NORMAL, | |
'arg_str': arg_str | |
}) | |
self.log(message) | |
self.logger_instance.update_indent_level(self.logger_instance.indent_level + 1) | |
returnval = method(self, *args, **kwargs) | |
self.logger_instance.update_indent_level(self.logger_instance.indent_level - 1) | |
return returnval | |
return _wrapper | |
def log_class(cls, log_match=".*", log_no_match="asdfnomatch", display_name=None): | |
display_name = display_name or "%s" % (cls.__name__) | |
names_to_check = cls.__dict__.keys() | |
allow = lambda s: all([ | |
re.match(log_match, s), | |
not re.match(log_no_match, s), | |
s not in ('__str__', '__repr__') | |
]) | |
for name in names_to_check: | |
if not allow(name): | |
continue | |
# unbound methods show up as mere functions in the values of | |
# cls.__dict__,so we have to go through getattr | |
value = getattr(cls, name) | |
if isinstance(value, types.MethodType): | |
# a normal instance method | |
if value.im_self is None: | |
setattr(cls, name, log_method(value, display_name=display_name)) | |
# check for cls method. class & static method are more complex. | |
elif value.im_self == cls: | |
_display_name = "%s.%s" % (cls.__name__, value.__name__) | |
w = log_method(value.im_func, display_name=_display_name) | |
setattr(cls, name, classmethod(w)) | |
else: | |
assert False | |
return cls | |
class WorkflowStep(object): | |
def __init__(self, *args, **kwargs): | |
self.__logger = None | |
@property | |
def logger_instance(self): | |
if not self.__logger: | |
self.__logger = Logger() | |
return self.__logger | |
def log(self, message, *args, **kwargs): | |
self.logger_instance.log(message, *args, **kwargs) | |
@log_class | |
class CleanNumeric(WorkflowStep): | |
@log_method # just for fun | |
def clean_line(self, line, start=0, stop=3, *args, **kwargs): | |
# recursion just so we can experiment with the tracing | |
self.log("Recursing number %s" % start, color=BOLDGREEN) | |
if start > stop: | |
return line | |
return self.randomize(line, start=start+1, stop=stop) | |
def randomize(self, line, start=None, stop=None, *args, **kwargs): | |
# recursion just so we can experiment with the tracing | |
random.shuffle(line) | |
return self.clean_line(line, start=start, stop=stop) | |
cn = CleanNumeric() | |
cn.clean_line([1,2,3,4,5]) |
hey @Abdelkrim, sorry for the delay - i just saw this...
@log_method
needs to be used on class or instance methods. your code snippet makes it seem like you're placing it on a function. could you please clarify. thanks!
Hi @beng,
This looks promising, thanks for putting this together. Unfortunately, I experience many errors when introducing this to a more-complex, stable application. Enough errors that it's difficult to quickly create a useful post. Please message me if you'd like to know more.
In the meantime, have you seen http://ninthtest.net/python-autologging ? For what it's worth, it also generates errors (although not as many) when integrated with my app.
~J
@johnnyutahh - what kind of errors do you experience? what version of python is the app using? fwiw, i barely tested this on python 2.7. i'll need a bit more info from you in order to understand the issues you're having.
Hi @beng, when I write this piece of code
I receive an error message: AttributeError: 'ManageFields' object has no attribute 'log'
could you help me making autolog run smoothly?
PS: autog runs if I run the command:
python autolog.py
Thanks in advance for your help