Specialized logging for control, debugging, audit, and security

There are many kinds of logging; we'll focus on these four varieties:

  • Errors and Control: Basic errors and the control of an application leads to a main log that helps users confirm that the program really is doing what it's supposed to do. This would include enough error information with which users can correct their problems and rerun the application. If a user enables verbose logging, it will amplify this main error and control the log with additional user-friendly details.
  • Debugging: This is used by developers and maintainers; it can include rather complex implementation details. We'll rarely want to enable blanket debugging, but will often enable debugging for specific modules or classes.
  • Audit: This is a formal confirmation that tracks the transformations applied to data so that we can be sure that processing was done correctly.
  • Security: This can be used to show us who has been authenticated; it can help confirm that the authorization rules are being followed. It can also be used to detect some kinds of attacks that involve repeated password failures.

We often have different formatting and handling requirements for each of these kinds of logs. Also, some of these are enabled and disabled dynamically. The main error and control log is often built from non-debug messages. We might have an application with a structure like the following code:

from collections import Counter
from Chapter_16.ch16_ex1 import LoggedClass

class Main(LoggedClass):

def __init__(self) -> None:
self.counts: Counter[str] = collections.Counter()

def run(self) -> None:
self.logger.info("Start")

# Some processing in and around the counter increments
self.counts["input"] += 2000
self.counts["reject"] += 500
self.counts["output"] += 1500

self.logger.info("Counts %s", self.counts)

We used the LoggedClass class to create a logger with a name that matches the class qualified name (Main). We've written informational messages to this logger to show you that our application started normally and finished normally. In this case, we used Counter to accumulate some balance information that can be used to confirm that the right amount of data was processed.

In some cases, we'll have more formal balance information displayed at the end of the processing. We might do something like this to provide a slightly easier-to-read display:

    for k in self.counts:
self.logger.info(
f"{k:.<16s} {self.counts[k]:>6,d}")

This version will show us the keys and values on separate lines in the log. The errors and control log often uses the simplest format; it might show us just the message text with little or no additional context. A formatter like this might be used:

formatters: 
  control: 
    style: "{" 
    format: "{levelname:s}:{message:s}" 

This configures formatter to show us the level name (INFO, WARNING, ERROR, or CRITICAL) along with the message text. This eliminates a number of details, providing just the essential facts for the benefit of the users. We called the formatter control.

In the following code, we associate the formatter with the handler:

handlers: 
  console: 
    class: logging.StreamHandler 
    stream: ext://sys.stderr 
    formatter: control 

This will use the control formatter with the console handler.

It's important to note that loggers will be created when the Main class, is created. This is long before the logging configuration is applied. In order to be sure the loggers that are defined as part of the classes are honored properly, the configuration must include the following:

disable_existing_loggers: False

This will guarantee that loggers created as part of the class definition will be preserved when logging.config.dictConfig() is used to set the configuration.

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

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