Logging Notes
Page Contents
Logging
Take a look at the Python Logging Cookbook.
Exceptions
import logging
logger = logging.getLogger("exception")
try:
x = 10 / 0
except ZeroDivisionError:
logger.exception("A divide by zero for some reason!")
Will log the exception with the traceback...
A divide by zero for some reason!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
Log Handlers
Soecify where the logs go.
Common ones:
StreamHandler
- Write to stdout and other streams.FileHandler
RotatingFileHandler
TimedRotatingFileHandler
If using thread or processes:
QueueHandler
QueueListener
Use basicConfig
Does basic configuration for the logging system by creating a StreamHandler or FileHandler with a default Formatter and adding it to the root logger.
import logging
logging.basicConfig(filename='mylogfile.txt', level=logging.DEBUG)
logging.debug(...)
Use One Or More Logging Handlers
Use when creating custom logger.
logger = logging.getLogger(name="test")
...
file_handler = logging.FileHandler("mylog.txt")
stream_handler = logging.StreamHandler()
logger.add_handler(file_handler)
logger.add_handler(stream_handler)
# The following logs to both a file and standard out
logger.debug(...)
Logging handlers provide the following methods to work with threads:
aquire()
release()
Note, though, that the logging API is THREAD SAFE, so you don't have to use these functions in most situations.
Can also use
flush()
close()
Formatters
Responsible for converting a LogRecord to an output string to be interpreted by a human or external system.
logging.Formatter(
fmt=None, # Fornat string using the fiven style param for the logged output
datefmt=None, # How to format dates
style='%', # Determines how format string is interpolated
validate=True, # If true incorrect fmt and style causes ValueError
*,
defaults=None # dict[str, Any] of default values for custom fields
)
import logging
logger = logging.getLogger(name=...)
file_handler = logging.FileHandler(...)
logger.add_handler(file_handler)
formatter = logging.Formatter(
"%(asctime)s - %(names)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(formatter)
logger.debug(...)
The special formatters like asctime
are part of the LogRecord
object that the
logger creates, that get passed to the formatters, which convert LogRecords
to
output strings, consumed by the Handlers.
LogRecords
have the following properties (taken from the docs):
- name (str) – The name of the logger used to log the event represented by this LogRecord. Note that the logger name in the LogRecord will always have this value, even though it may be emitted by a handler attached to a different (ancestor) logger.
- level (int) – The numeric level of the logging event (such as 10 for DEBUG, 20 for INFO, etc). Note that this is converted to two attributes of the LogRecord: levelno for the numeric value and levelname for the corresponding level name.
- pathname (str) – The full string path of the source file where the logging call was made.
- lineno (int) – The line number in the source file where the logging call was made.
- msg (Any) – The event description message, which can be a %-format string with placeholders for variable data, or an arbitrary object (see Using arbitrary objects as messages).
- args (tuple | dict[str, Any]) – Variable data to merge into the msg argument to obtain the event description.
- exc_info (tuple[type[BaseException], BaseException, types.TracebackType] | None) – An exception tuple with the current exception information, as returned by sys.exc_info(), or None if no exception information is available.
- func (str | None) – The name of the function or method from which the logging call was invoked.
- sinfo (str | None) – A text string representing stack information from the base of the stack in the current thread, up to the logging call.
Filters
More sophisticated filtering than can be obtained using log levels.
Create a filter:
class FancyNewFilter(logging.Filter): # Note you do not have to inherit from logging.Filter!
def filter(self, record : LogRecord):
# You have access to the log record so can base your filter
# on anyting in that record...
return True # To say, "yes" emit the log
# Or...
return False # To say, "no" silence the log
# You could even add new attributes to the record if you liked to create
# custom attributes that you could then reference in the fmt string
From Python 3.12 don't even need a class... any callable that accepts a LogRecord
will do.
To apply the filter to a logger or a handler use the .addFilter()
method.
Decorators
Tip: Use a decorator to make logging exceptions in functions easier.
Concurrency
Async
The logging API is blocking. Solve using QueueHandler
and QueueListener
.
from queue import SimpleQueue
import logging.handlers
async def setup_logging():
log_q = SimpleQueue()
root = logging.getLogger()
# Create a NON-blocking handler: The root logger will emit LogRecords to the
# queue, which will not (unless full) block, decoupling the logger from the
# blocking FileHandler defined below...
queue_handler = logging.handlers.QueueHandler(log_q)
root.addHandler(queue_handler)
# Create blocking FileHandler
file_handler = logging.FileHandler(...)
# Create a listener on the other end of the queue that will live in its
# own thread and dequeue items off the queue to handler them. Thus,
# the file_handler appears, from the roots point of view, to be non
# blocking because it is "hidden" behind a queue.
listener = logging.handlers.QueueListener(log_q, file_handler)
try:
listener.start()
... log and work ...
finally:
listener.stop()