ich habe die letzten Abende an einer Erweiterung für den standard-Python-Logger gesessen, der mir nicht nur sagt, von welcher Funktion er aufgerufen wurde, sondern auch von welcher Klasse. Ausserdem erlaubt er, das Logging Packet/Modul/Klassen/Methodenweise zu konfigurieren, ohne dabei tausend logger-Instanzen handlen zu müssen. Mehr Infos stehen im __doc__

Der im Code angegebene FormatString erzeugt Ausgaben im folgenden Format:
Der Code ist noch nicht final, tuts aber schon ganz gut. Und entschuldigt die deutschen Kommentare[INFO] ClassName.MethodName() =>Was die Methode zu sagen hat!

Verbesserungsvorschläge, vor allem was die Funktion _analyze_frame() (ganz unten) angeht, sind herzlich willikommen!
Code: Alles auswählen
#!/usr/bin/python
# -*- coding: ISO-8859-1 -*-
import logging as original_logging
import inspect, types
"""
Dieses Modul ist als Erweiterung zum Standard-Python-Logging-Modul
konzipiert.
============================================
Änderungen im Verhalten des Standard-Loggers
============================================
Wenn eine der Logging-Methoden (debug...critical) aus einer
Klassen-Methode herraus aufgerufen wird, wird der String
'Packagename.Modulename.Klassenname.Methodenname' als Name
für die Logging-Klasse verwendet.
Im Format-String für Logging-Ausgaben stehen zusätzlich die
Keys
%(classname)s
%(functionname)s und
%(packagename)s
zur Verfügung. Wenn keine Klasse ermittelt werden konnte,
enthält %(classname)s den String 'NoClass'.
Der Standard-Key %(funcName)s liefert leider immer _do_log
aus diesem Modul zurück.
=============================================================
Vorteile gegenüber dem Standard-Logger ohne diese Erweiterung
=============================================================
Dies ermöglicht, dass das Logging Package-, Module-, Klassen-
und/oder Methodenweise gefiltert werden kann, ohne dass jede
Klasse seinen eigenen Logger bereithalten muss.
===================================
Kompatiblität zum Standard-Logger
Migration von bestehendem Quellcode
===================================
Scripte, die über
import logging
logging.info("Hello World!")
geloggt haben, können sehr einfach angepasst werden, indem der
Import auf
import contextlogging as logging
geändert wird. Sollten auch Zugriffe auf das original-Logging-Modul
nötig sein, kann dies z.B. als
import logging as original_logging
importiert werden. So können beispielsweise folgende Aufrufe
umgeleitet werden:
original_logging.basicConfig(level=original_logging.DEBUG,
format='[%(levelname)s] %(classname)s.%(functionname)s() => %(message)s')
# Setzt das Logging für das gesammte Package auf Warn
original_logging.getLogger('testpackage').setLevel(old_logging.WARN)
# Setzt das Logging für diese einzelne Klasse auf Debug
original_logging.getLogger('testpackage.testpackage.TestClass').
setLevel(old_logging.DEBUG)
=====
TODOs
=====
Funktion _analyze_frame(frame)
Der packagename sollte auch zurückgegeben werden, wenn keine Klasse
ermittelt werden konnte.
"""
def critical(msg, *args, **kwargs):
"""Wrapper für logging.critical"""
frame = inspect.currentframe().f_back
_do_log(frame, original_logging.CRITICAL, msg, *args, **kwargs)
del frame # see Doku 26.10.4
fatal = critical
def error(msg, *args, **kwargs):
"""Wrapper für logging.error"""
frame = inspect.currentframe().f_back
_do_log(frame, original_logging.ERROR, msg, *args, **kwargs)
del frame # see Doku 26.10.4
def exception(msg, *args):
"""Wrapper für logging.exception"""
frame = inspect.currentframe().f_back
logger = _get_logger(frame)
logger.exception(msg, *args)
del frame # see Doku 26.10.4
def warning(msg, *args, **kwargs):
"""Wrapper für logging.warning"""
frame = inspect.currentframe().f_back
_do_log(frame, original_logging.WARNING, msg, *args, **kwargs)
del frame # see Doku 26.10.4
warn = warning
def info(msg, *args, **kwargs):
"""Wrapper für logging.info"""
frame = inspect.currentframe().f_back
_do_log(frame, original_logging.INFO, msg, *args, **kwargs)
del frame # see Doku 26.10.4
def debug(msg, *args, **kwargs):
"""Wrapper für logging.debug"""
frame = inspect.currentframe().f_back
_do_log(frame, original_logging.DEBUG, msg, *args, **kwargs)
del frame # see Doku 26.10.4
def log(level, msg, *args, **kwargs):
"""Wrapper für logging.log"""
frame = inspect.currentframe().f_back
_do_log(frame, level, msg, *args, **kwargs)
del frame # see Doku 26.10.4
def setLevel(level):
"""Ermittelt anhand des Frames den passenden Logger und setzt
dessen Logging-Level
"""
frame = inspect.currentframe().f_back
logger = _get_logger(frame)
logger.setLevel(level)
del frame # see Doku 26.10.4
# ==== PRIVATE FUNKTIONEN ===================================================
def _get_logger(frame):
"""
Ermittelt anhand des Frames den passenden Logger.
Als Loggername wird Classname.Functionname verwendet.
Wenn der Classname None ist, wird der RootLogger zurückgegeben.
"""
(classname, function, packagename) = _analyze_frame(frame)
area = ""
if packagename:
area += packagename
if classname:
area = '.'.join((area, classname))
if area != "":
area = '.'.join((area, function))
logger = original_logging.getLogger(area)
else:
logger = original_logging.root
return logger
def _do_log(frame, level, msg, *args, **kwargs):
"""
Ermittelt anhand des Frames den passenden Logger und ruft dessen
Log-Methode auf.
"""
logger = _get_logger(frame)
# classname und packagename im Formatstring verfügbar machen
(classname, function, packagename) = _analyze_frame(frame)
if 'extra' not in kwargs.keys():
kwargs['extra'] = {}
if not classname:
classname = 'NoClass'
kwargs['extra'].update({'classname': classname,
'functionname': function,
'packagename': packagename})
logger.log(level, msg, *args, **kwargs )
def _analyze_frame(frame):
"""
Diese Methode extrahiert Funktionsname, Klassenname und Packagename aus dem
übergebenen Frame.
Wenn die Funktion keine Methode oder eine static-Methode ist, ist der Klassenname
None.
Dieser Hack hat folgende Probleme:
- Klappt nicht für statische Methoden
- Klappt nicht, wenn der erste Parameter nicht self heißt
- Klappt nicht, wenn ich eine einfache Funktion schreibe, deren erster Parameter self heißt.
- Ist weder sauber noch schön.
ToDo: Der packagename sollte auch zurückgegeben werden, wenn keien
Klasse ermittelt werden kann.
"""
klass = None
# Pruefe, ob die aufgerufene Funktion mindestens einen Parameter uebernimmt
if len(frame.f_code.co_varnames) > 0:
# Pruefe, ob der erste Parameter 'self' heisst
if frame.f_code.co_varnames[0] == 'self':
o = frame.f_locals['self']
# Pruefe, ob self eine Instanz ist
if type(o) == types.InstanceType:
klass = o.__class__
if klass:
classname = klass.__name__
packagename = inspect.getmodule(klass).__name__
else:
classname = None
packagename = None
info = inspect.getframeinfo(frame)
function = info[2]
return (classname, function, packagename)