Created
September 10, 2020 21:38
-
-
Save JohnL4/e8bf5a253003aa0458471f448af9b302 to your computer and use it in GitHub Desktop.
Complex logging configuration in python, using the "logging" module and yaml configuration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: 1 | |
formatters: | |
# Note that the following formats may use the 'hostname' attribute, which is non-standard. Be sure to use an adapter | |
# or a filter to get that info into the LogRecord context or ugliness will result. | |
# See: | |
# - mem_cleaner2.HostnameLoggerAdapter | |
# - https://docs.python.org/3/howto/logging-cookbook.html#adding-contextual-information-to-your-logging-output | |
# | |
oneLine: | |
format: "%(asctime)s %(levelname)-8s -- %(message)s -- %(module)s.%(funcName)s:%(lineno)d -- %(pathname)s" | |
multiLine: | |
format: "%(asctime)s -- %(levelname)s\n\n%(message)s\n\n%(hostname)s -- %(module)s.%(funcName)s:%(lineno)d -- %(pathname)s" | |
filters: | |
smtpWorthy: | |
(): __main__.SmtpWorthy # Some magic filter w/code I write to sent "restart" messages unconditionally (by | |
# matching regex, I guess) and critical-failure exceptions every two hours | |
# (between x:00 and x:10, where x is an even number). | |
handlers: | |
console: | |
class: logging.StreamHandler | |
stream: ext://sys.stdout | |
level: NOTSET # Let logger set level | |
formatter: oneLine | |
file: | |
class: logging.handlers.RotatingFileHandler | |
filename: "Logs/mem_cleaner2.log" # Directory must exist ahead of time or logging will fail (i.e., not auto-created). | |
maxBytes: 10485760 # 1048576 is a megabyte | |
backupCount: 10 | |
level: NOTSET # Let logger set level | |
formatter: oneLine | |
smtp: | |
class: logging.handlers.SMTPHandler | |
mailhost: smtp.ur-company.com | |
fromaddr: [email protected] | |
toaddrs: | |
# - [email protected] | |
# - [email protected] | |
- [email protected] | |
subject: "mem_cleaner2.py restarted a cube (or saw a critical error)" | |
timeout: 15.0 # In seconds, I assume | |
level: NOTSET # Let logger set level | |
formatter: multiLine | |
filters: [smtpWorthy] | |
# Unless there are other loggers declared (probably not necessary for a script this simple), there's only the root logger. | |
root: | |
level: DEBUG | |
handlers: | |
- console | |
- file | |
- smtp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Excerpted from my running production code. | |
# Probably don't need all this garbage, but oh well. | |
import sys | |
import os | |
import socket | |
import io | |
import traceback | |
import logging, logging.config | |
import re | |
import time | |
from ruamel.yaml import YAML # Not built-in | |
# ... blah blah blah ... | |
def configure(): | |
# Get settings | |
if getattr(sys, 'frozen', False): | |
# PyInstaller .exe | |
application_path = os.path.dirname(sys.executable) | |
elif __file__: | |
# "Normal" Python script | |
application_path = sys.path[0] | |
# 'safe' loader as opposed to 'rt' (round-trip; but we won't be writing any YAML, so no need); | |
# pure Python yaml-parsing code as opposed to whatever C/C++ parser the module finds | |
yamlReader = YAML(typ='safe', pure=True) | |
print( 'Attempt logging config') | |
try: | |
logconfig = yamlReader.load( open( application_path + r'\log-config.yaml', 'r')) | |
logging.config.dictConfig( logconfig) | |
except: | |
logging.exception( 'Exception configuring logging') | |
logger = HostnameLoggerAdapter( logging.getLogger('__main__'), {'hostname': socket.gethostname()}) | |
logger.debug( 'Logging configured') | |
class SmtpWorthy(logging.Filter): | |
"""Decides whether a particular log record is worthy of an SMTP message.""" | |
def filter(self, aLogRecord): | |
if re.match( 'MAI-WORKSTATION-NAME', socket.gethostname()): | |
# Don't be spamming people from your dev workstation. | |
retval = False | |
elif aLogRecord.levelno == logging.CRITICAL: | |
# Top priority | |
retval = True | |
elif re.search('Full cube info:', aLogRecord.message): | |
# Filter this out because it produces a false match on the regex below. | |
# It's a huge blob of text that just means the cube isn't running (it might be just sleeping). | |
retval = False | |
else: | |
retval = re.search('Restarting|Detaching', aLogRecord.message) | |
return retval | |
class HostnameLoggerAdapter( logging.LoggerAdapter): | |
""" | |
Looks like a logger; adds current hostname (as 'hostname') to LogRecord context info. | |
NOTE: Inherited constructor seems to set 'extra' attribute, so we just need to override processing. | |
""" | |
def process( self, msg, kwargs): | |
"""Less-destructive handling of 'extra' keyword.""" | |
if 'extra' in kwargs: | |
kwargs['extra'].update( self.extra) | |
else: | |
kwargs['extra'] = self.extra | |
return msg, kwargs | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Credit to https://gist.github.com/kingspp/9451566a5555fb022215ca2b7b802f19
and https://gist.github.com/kingspp/9451566a5555fb022215ca2b7b802f19#gistcomment-3078537