32

これは、ログに使用している書式設定文字列です。

'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'

しかし、ログ メッセージを表示するために、もう少し処理を行うラッパーがあります (さまざまなログ レベルをセットアップし、さまざまなログ バックエンドを構成し、カスタム レベルにアクセスするための便利な関数を提供するなど):

class MyLogger(logging.Logger):

    def split_line(self, level, message):
        ....
        self.log.(level, line)

    def progress(self, message):
        self.split_line(PROGRESS, message)

この設定では、何かをログに記録するたびに:

def myfunc():
    log.progress('Hello')

私は得る:

013-10-27 08:47:30,130 - PROGRESS   - split_line - Hello

これは私が望んでいたものではありません。つまり、これは次のとおりです。

013-10-27 08:47:30,130 - PROGRESS   - myfunc     - Hello

関数名に正しいコンテキストを使用するようにロガーに指示するにはどうすればよいですか? これは、実際にはスタックフレームで 2 レベル高くなると思います。

編集

これは、問題を示すテスト プログラムです。

import sys
import logging

PROGRESS = 1000

class MyLogger(logging.Logger):

    PROGRESS = PROGRESS
    LOG_FORMATTER = '%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
    DEF_LOGGING_LEVEL = logging.WARNING

    def __init__(self, log_name, level=None):
        logging.Logger.__init__(self, log_name)
        self.formatter = logging.Formatter(self.LOG_FORMATTER)
        self.initLogger(level)

    def initLogger(self, level=None):
        self.setLevel(level or self.DEF_LOGGING_LEVEL)
        self.propagate = False

    def add_handler(self, log_file, use_syslog):
        if use_syslog : hdlr = logging.handlers.SysLogHandler(address='/dev/log')
        elif log_file : hdlr = logging.FileHandler(log_file)
        else          : hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(self.formatter)
        self.addHandler(hdlr)
        return hdlr

    def addHandlers(self, log_file=None, progress_file=None, use_syslog=False):
        self.logger_hdlr = self.add_handler(log_file, use_syslog)
        if progress_file:
            self.progress_hdlr = self.add_handler(progress_file, use_syslog)
            self.progress_hdlr.setLevel(self.PROGRESS)
        else:
            self.progress_hdlr = None

    def split_line(self, level, txt, *args):
        txt = txt % (args)
        for line in txt.split('\n'):
            self.log(level, line)

    def progress(self, txt, *args):
        self.split_line(self.PROGRESS, txt, *args)

logging.setLoggerClass(MyLogger)
logging.addLevelName(PROGRESS, 'PROGRESS')
logger = logging.getLogger(__name__)
logger.addHandlers()

name = 'John'
logger.progress('Hello %s\nHow are you doing?', name)

プロデュース:

2013-10-27 09:47:39,577 - PROGRESS   - split_line - Hello John
2013-10-27 09:47:39,577 - PROGRESS   - split_line - How are you doing?
4

9 に答える 9

4

基本的に、非難するコードは次のLoggerクラスにあります。

この方法

def findCaller(self):
    """
    Find the stack frame of the caller so that we can note the source
    file name, line number and function name.
    """
    f = currentframe()
    #On some versions of IronPython, currentframe() returns None if
    #IronPython isn't run with -X:Frames.
    if f is not None:
        f = f.f_back
    rv = "(unknown file)", 0, "(unknown function)"
    while hasattr(f, "f_code"):
        co = f.f_code
        filename = os.path.normcase(co.co_filename)
        if filename == _srcfile:
            f = f.f_back
            continue
        rv = (co.co_filename, f.f_lineno, co.co_name)
        break
    return rv

現在のモジュールに属していない呼び出し元のチェーンの最初の関数を返します。

Loggerもう少し複雑なロジックを追加することで、このメソッドをサブクラス化してオーバーライドできます。別のレベルの呼び出し深度をスキップするか、別の条件を追加します。


あなたの非常に特別なケースでは、自動行分割を控えて実行する方がおそらく簡単でしょう

logger.progress('Hello %s', name)
logger.progress('How are you doing?')

またはする

def splitter(txt, *args)
    txt = txt % (args)
    for line in txt.split('\n'):
        yield line

for line in splitter('Hello %s\nHow are you doing?', name):
    logger.progress(line)

そして持っている

def progress(self, txt, *args):
    self.log(self.PROGRESS, txt, *args)

おそらくそれはあなたの頭痛の多くを救うでしょう.

編集 2: いいえ、それは役に立ちません。これでprogress、呼び出し元の関数名として表示されます...

于 2013-10-27T09:11:33.263 に答える
3

まず第一に、あなたのコードによれば、それがなぜ起こるのかは明らかであり、isとisを呼び出すときにlevelnamefuncName所属」します。self.logself.log(level, line)levelnamelevelfuncNameline

あなたには2つのオプションがあります。

  1. モジュールを使用inspectして現在のメソッドを取得し、メッセージ内で配信するには、それを解析して非常に簡単に使用できます。

  2. より良いアプローチはinspect、split_line 内で使用して「父」メソッドを取得することです。次のコードの number(3) を変更して、メソッドの階層で「遊ぶ」ことができます。

inspect を使用して現在のメソッドを取得する例

from inspect import stack

class Foo:
    def __init__(self):
        print stack()[0][3]

f = Foo()
于 2013-10-27T08:51:05.807 に答える
3

@cygnusb と、すでに有用なポインタを提供してくれた他の人たちに感謝します。出発点として、Python 3.4 の Logger.findCaller メソッドを使用することにしました。次のソリューションは、Python 2.7.9 および 3.4.2 でテスト済みです。このコードは、独自のモジュールに配置することを意図しています。ループを 1 回繰り返すだけで正しい答えが得られます。

import io
import sys

def _DummyFn(*args, **kwargs):
    """Placeholder function.

    Raises:
        NotImplementedError
    """
    _, _ = args, kwargs
    raise NotImplementedError()

# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame, by skipping frames whose filename is that of this
# module's source. It therefore should contain the filename of this module's
# source file.
_srcfile = os.path.normcase(_DummyFn.__code__.co_filename)
if hasattr(sys, '_getframe'):
    def currentframe():
        return sys._getframe(3)
else:  # pragma: no cover
    def currentframe():
        """Return the frame object for the caller's stack frame."""
        try:
            raise Exception
        except Exception:
            return sys.exc_info()[2].tb_frame.f_back

class WrappedLogger(logging.Logger):
    """Report context of the caller of the function that issues a logging call.

    That is, if

        A() -> B() -> logging.info()

    Then references to "%(funcName)s", for example, will use A's context
    rather than B's context.

    Usage:
        logging.setLoggerClass(WrappedLogger)
        wrapped_logging = logging.getLogger("wrapped_logging")
    """
    def findCaller(self, stack_info=False):
        """Return the context of the caller's parent.

        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.

        This is based on the standard python 3.4 Logger.findCaller method.
        """
        sinfo = None
        f = currentframe()
        # On some versions of IronPython, currentframe() returns None if
        # IronPython isn't run with -X:Frames.
        if f is not None:
            f = f.f_back

        if sys.version_info.major == 2:
            rv = "(unknown file)", 0, "(unknown function)"
        else:
            rv = "(unknown file)", 0, "(unknown function)", sinfo

        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename == _srcfile or filename == logging._srcfile:
                f = f.f_back
                continue
            # We want the frame of the caller of the wrapped logging function.
            # So jump back one more frame.
            f = f.f_back
            co = f.f_code
            if sys.version_info.major == 2:
            rv = "(unknown file)", 0, "(unknown function)"
        else:
            rv = "(unknown file)", 0, "(unknown function)", sinfo

        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename == _srcfile or filename == logging._srcfile:
                f = f.f_back
                continue
            # We want the frame of the caller of the wrapped logging function.
            # So jump back one more frame.
            f = f.f_back
            co = f.f_code
            if sys.version_info.major == 2:
                rv = co.co_filename, f.f_lineno, co.co_name
            else:
                if stack_info:
                    sio = io.StringIO()
                    sio.write('Stack (most recent call last):\n')
                    traceback.print_stack(f, file=sio)
                    sinfo = sio.getvalue()
                    if sinfo[-1] == '\n':
                        sinfo = sinfo[:-1]
                    sio.close()
                rv = co.co_filename, f.f_lineno, co.co_name, sinfo
            break

        return rv
于 2015-02-06T01:59:35.797 に答える
2

最初の回答で示唆されているように、 Logger クラスをサブクラス化し、 logging.setLoggerClass を使用するとうまくいくはずです。ラップされた関数呼び出しを処理する、変更された findCaller 関数が必要になります。

findCaller クラス メソッドは、現在のソース ファイル名ではないファイルから最初の呼び出しを検索するため、次をモジュールに挿入します。

import inspect
import logging
import os

if hasattr(sys, 'frozen'): #support for py2exe
    _srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
elif __file__[-4:].lower() in ['.pyc', '.pyo']:
    _srcfile = __file__[:-4] + '.py'
else:
    _srcfile = __file__
_srcfile = os.path.normcase(_srcfile)

class WrappedLogger(logging.Logger):
    def __init__(self,name):
        logging.Logger.__init__(self, name)

    def findCaller(self):
        """
        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.
        """
        # get all outer frames starting from the current frame
        outer = inspect.getouterframes(inspect.currentframe())
        # reverse the order, to search from out inward
        outer.reverse()
        rv = "(unknown file)", 0, "(unknown function)"    

        pos = 0
        # go through all frames
        for i in range(0,len(outer)):
            # stop if we find the current source filename
            if outer[i][1] == _srcfile:
                # the caller is the previous one
                pos=i-1
                break

        # get the frame (stored in first tuple entry)
        f = outer[pos][0]

        co = f.f_code
        rv = (co.co_filename, f.f_lineno, co.co_name)

        return rv
# Usage:
logging.setLoggerClass(WrappedLogger)
log = logging.getLogger("something")
于 2014-01-08T10:27:48.610 に答える
2

progressメソッドとsplit_lineメソッドをマージできます。

def progress(self, txt, *args, **kwargs):
    if self.isEnabledFor(self.PROGRESS):
        txt = txt % (args)
        for line in txt.split('\n'):
            self._log(self.PROGRESS, line, [], **kwargs)
于 2013-10-27T09:23:44.190 に答える
2

誰かが正しい答えを出しました。まとめをします。

logging.Logger.findCaller() 、オリジナルパッケージのlogging._srcfileでスタック フレームをフィルタリングします。logging

したがって、同じことを行い、独自の logger wrapper をフィルタリングしますmy_log_module._srcfile。ロガー インスタンスのメソッドlogging.Logger.findCaller()を動的に置き換えます。

ところで、 のサブクラスを作成しないでくださいlogging.Loggerloggingパッケージには、findCaller の場合の OOP の設計がありません。

# file: my_log_module.py, Python-2.7, define your logging wrapper here
import sys
import os
import logging
my_logger = logging.getLogger('my_log')

if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
# done filching

#
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
#
_srcfile = os.path.normcase(currentframe.__code__.co_filename)

def findCallerPatch(self):
    """
    Find the stack frame of the caller so that we can note the source
    file name, line number and function name.
    """
    f = currentframe()
    #On some versions of IronPython, currentframe() returns None if
    #IronPython isn't run with -X:Frames.
    if f is not None:
        f = f.f_back
    rv = "(unknown file)", 0, "(unknown function)"
    while hasattr(f, "f_code"):
        co = f.f_code
        filename = os.path.normcase(co.co_filename)
        if filename == _srcfile:
            f = f.f_back
            continue
        rv = (co.co_filename, f.f_lineno, co.co_name)
        break
    return rv

# DO patch
my_logger.findCaller = findCallerPatch

よし、準備万端。他のモジュールでロガーを使用できるようになり、ロギング メッセージ形式を追加できます: lineno、パス、メソッド名、blablabla

# file: app.py
from my_log_module import my_logger
my_logger.debug('I can check right caller now')

または、エレガントな方法を使用できますが、グローバルは使用しないでくださいlogging.setLoggerClass

# file: my_log_modue.py
import logging
my_logger = logging.getLogger('my_log')

class MyLogger(logging.Logger):
    ...

my_logger.__class__ = MyLogger
于 2017-04-08T17:44:47.950 に答える
0

Python 3.8.6、Logging _log 関数には重要なパラメーター「stacklevel」があるため、同じ問題を次のように解決しました。

ラッパー クラス MyLogger:

def __init__(self):
    ...
    self._logger = logging.getLogger(__name__)

def debug(self, msg, *args, **kwargs):
    self._logger.debug(msg, *args, stacklevel=2, **kwargs)

このコードは、デバッグ メッセージに関数名、コード行番号などを正しく表示します

于 2021-02-25T14:41:15.243 に答える