Creating a debugging log

A debugging log is usually enabled by a developer to monitor a program under development. It's often narrowly focused on specific features, modules, or classes. Consequently, we'll often enable and disable loggers by name. A configuration file might set the level of a few loggers to DEBUG, leaving others at INFO, or possibly even a WARNING level.

We'll often design debugging information into our classes. Indeed, we might use the debugging ability as a specific quality feature for a class design. This may mean introducing a rich set of logging requests. For example, we might have a complex calculation for which the class state is essential information as follows:

from Chapter_16.ch16_ex1 import LoggedClass

class BettingStrategy(LoggedClass):
def bet(self) -> int:
raise NotImplementedError("No bet method")

def record_win(self) -> None:
pass

def record_loss(self) -> None:
pass

class OneThreeTwoSix(BettingStrategy):
def __init__(self) -> None:
self.wins = 0

def _state(self) -> Dict[str, int]:
return dict(wins=self.wins)

def bet(self) -> int:
bet = {0: 1, 1: 3, 2: 2, 3: 6}[self.wins % 4]
self.logger.debug(f"Bet {self._state()}; based on {bet}")
return bet

def record_win(self) -> None:
self.wins += 1
self.logger.debug(f"Win: {self._state()}")

def record_loss(self) -> None:
self.wins = 0
self.logger.debug(f"Loss: {self._state()}")

In these class definitions, we defined a superclass, BettingStrategy, which provides some features of a betting strategy. Specifically, this class defines methods for getting a bet amount, recording a win, or recording a loss. The idea behind this class is the common fallacy that the modification of bets can somehow mitigate losses in a game of chance.

The concrete implementation, OneThreeTwoSix, created a _state() method that exposes the relevant internal state. This method is only used to support debugging. We've avoided using self.__dict__ because this often has too much information to be helpful. We can then audit the changes to the self._state information in several places in our method functions.

In many of the previous examples, we relied on the logger's use of %r and %s formatting. We might have used a line like self.logger.info("template with %r and %r", some_item, another_variable). This kind of line provides a message with fields that pass through the filters before being handled by the formatter. A great deal of control can be exercised over lines like this. 

In this example, we've used self.logger.debug(f"Win: {self._state()}"), which uses an f-string. The logging package's filter and formatter cannot be used to provide fine-grained control over this output. In the cases of audit and security logs, the logging % style formatting, controlled by the logger, is preferred. It allows a log filter to redact sensitive information in a consistent way. For informal log entries, f-strings can be handy. It's important, however, to be very careful of what information is placed in a log using f-string.

Debugging output is often selectively enabled by editing the configuration file to enable and disable debugging in certain places. We might make a change such as this to the logging configuration file:

loggers: 
    betting.OneThreeTwoSix: 
       handlers: [console] 
       level: DEBUG 
       propagate: False 

We identified the logger for a particular class based on the qualified name for the class. This example assumes that there's a handler named console already defined. Also, we've turned off the propagation to prevent the debugging messages from being duplicated into the root logger.

Implicit in this design is the idea that debugging is not something we want to simply enable from the command line via a simplistic -D option or a --DEBUG option. In order to perform effective debugging, we'll often want to enable selected loggers via a configuration file. We'll look at command-line issues in Chapter 18, Coping with the Command Line.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset