Skip to content

Instantly share code, notes, and snippets.

@beng
Forked from brendano/autolog.py
Last active April 3, 2023 16:28
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save beng/7817597 to your computer and use it in GitHub Desktop.
Save beng/7817597 to your computer and use it in GitHub Desktop.
modified from the original 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.
# Written by Brendan O'Connor, brenocon@gmail.com, 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])
@Abdelkrim
Copy link

Hi @beng, when I write this piece of code

from autolog import log_method

@log_method
def set_select(self, UIFieldName=None, XPathInXML=None, seconds=1):
    Select(self._driver.find_element_by_id(UIFieldName)).select_by_visible_text(
        self._testData.find(XPathInXML).text)

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

@beng
Copy link
Author

beng commented Sep 10, 2014

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!

@johnnyutahh
Copy link

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

@beng
Copy link
Author

beng commented Jun 16, 2015

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment