3

I'm new to logging in python and I would like to save, other than the outcome of a long pipeline in the log file, also the parameters/class attributes of some of the instance created in the pipeline.

Ideally this should not pollute too much the code where the class is implemented.

Even better if the solution takes into account only the instance of the class and writes its attribute in the log file without touching at all the class implementation.

Any suggestion, or god practice advice?

--- Edit:

An unpolished and simplified version initial attempt (as asked in the comment) is the most obvious I could think of, and consist in adding a method that queries the attribute of the class in a string to be returned when the method is called:

In a python package with 2 modules main.py and a_class.py written as follows:

>> cat main.py

import logging
from a_class import MyClass


logging.basicConfig(filename='example.log',level=logging.DEBUG)

logging.warning('Print this to the console and save it to the log')
logging.info('Print this to the console')

o = MyClass()
o.attribute_1 = 1
o.attribute_2 = 3
o.attribute_3 = 'Spam'

logging.info(o.print_attributes())

and

>> cat a_class.py

class MyClass():
    def __init__(self):
        self.attribute_1 = 0
        self.attribute_2 = 0
        self.attribute_3 = 0

    def print_attributes(self):
        msg = '\nclass.attribute_1 {}\n'.format(self.attribute_1)
        msg += 'class.attribute_2 {}\n'.format(self.attribute_2)
        msg += 'class.attribute_3 {}\n'.format(self.attribute_3)
        return msg

The example.log contains what I wanted, which is:

WARNING:root:Print this to the console and save it to the log
INFO:root:Print this to the console
INFO:root:
class.attribute_1 1
class.attribute_2 3
class.attribute_3 Spam

In reformulating the question, is there a way of doing the same query to the attribute of the class and send it to the log without adding any kind of print_attributes method in the class itself?

SeF
  • 3,864
  • 2
  • 28
  • 41
  • 1
    Did you try anything? Do you have some kind of example code to show what you are trying to do? – SRC Oct 24 '17 at 14:33
  • 1
    In general, in order to instrument some method call with logging, there's no easy way to just make it happen magically. One thing you could do is create a decorator that wraps a method call with a write to the log, but then you'd have to put this decorator explicitly on every method you want logged. Alternatively you could write a class decorator that automatically adds this to every method on the class, or even a metaclass but that's asking for trouble. It sounds like mainly you just want this for the `__init__` method though. – Iguananaut Oct 24 '17 at 14:35
  • @SRC please see the edit. – SeF Oct 24 '17 at 15:30

3 Answers3

4

Use the inbuilt __dict__

class MyClass():
    def __init__(self):
        self.attribute_1 = 0
        self.attribute_2 = 0
        self.attribute_3 = 0

o = MyClass()
print o.__dict__

Outputs:

{'attribute_2': 0, 'attribute_3': 0, 'attribute_1': 0}

Use it in logging as you want to.

Arunmozhi
  • 1,034
  • 8
  • 17
  • 3
    What you wanted is also known as [`vars()`](https://docs.python.org/2/library/functions.html#vars). – 9000 Oct 25 '17 at 14:54
2

I'd suggest to implement __str__ or __repr__ for your class so that it would nicely show all the salient attribute values.

Then you can log instances as simple values: log.info("Now foo is %s", foo_instance).

A complete example:

class Donut(object):
    def __init__(self, filling, icing):
        self.filling = filling
        self.icing = icing

    def __repr__(self):
        return 'Donut(filling=%r, icing=%r)' % (self.filling, self.icing)


donut = Donut('jelly', 'glaze')


import logging

logging.basicConfig()

logging.getLogger().warn('Carbs overload: one %s too much', donut)

Output:

2017-10-25 10:59:05,302 9265 WARNING Carbs overload: one Donut(filling='jelly', icing='glaze') too much
9000
  • 39,899
  • 9
  • 66
  • 104
  • Can you elaborate more please on the point you mentioned? That will be helpful. Thanks. – SRC Oct 25 '17 at 10:56
  • Added a working example. Feel free to make your `__repr__` more elaborate if need be. – 9000 Oct 25 '17 at 15:01
  • 3
    IIRC, `__repr__` should be implemented such that `eval(repr(x)) == x`, and `__str__` should be the more "human-readable" form. I.e., your `__repr__` should be `__str__`, and `__repr__` should actually return `Donut(%s, %s) % (self.filling, self.icing)`. – mkrieger1 Oct 25 '17 at 15:10
  • @mkrieger1: Indeed! Updating the answer. – 9000 Oct 25 '17 at 16:57
2

I agree with @Iguananaut that there is no magical way of doing this. However, the following may do the trick. It is better than the print_attributes method you wrote, IMO.

import logging

logging.basicConfig()
logger = logging.getLogger('ddd')

logger.setLevel(logging.DEBUG)

class A(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __str__(self):
        return "\n".join(["{} is {}".format(k, v) 
                         for k, v in self.__dict__.iteritems()])


a = A(1, 2, 3)

logger.debug(a)

The result looks like this -

{12:43}~ ➭ python logging_attrib.py
DEBUG:ddd:a is 1
c is 3
b is 2

Please let me know what you think

SRC
  • 2,123
  • 3
  • 31
  • 44