web-dev-qa-db-ja.com

Python:ロギングモジュール-グローバル

私はあなた自身の設定でどこでも使用できるグローバルロガーを実装する方法を疑問に思っていました:

現在、カスタムロガークラスがあります。

class customLogger(logging.Logger):
   ...

このクラスは、いくつかのフォーマッタやその他のものを含む別のファイルにあります。ロガーは単独で完全に動作します。

このモジュールをメインのpythonファイルにインポートし、次のようなオブジェクトを作成します。

self.log = logModule.customLogger(arguments)

しかし、明らかに、コードの他の部分からこのオブジェクトにアクセスすることはできません。間違ったアプローチを使用していますか?これを行うためのより良い方法はありますか?

49
cwoebker

logging.getLogger(name) を使用して、名前付きグローバルロガーを作成します。

main.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

出力

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message
105
koehlma

満足のいく答えが見つからなかったので、Pythonの標準に付属しているloggingライブラリの動作と意図を洞察するために、質問の答えについて少し詳しく説明したいと思います。としょうかん。

OP(元のポスター)のアプローチとは対照的に、ライブラリーはロガーへのインターフェースとロガー自体の構成を明確に分離します。

ハンドラーの構成は、ライブラリーを使用するアプリケーション開発者の特権です。

つまり、notカスタムロガークラスを作成し、構成などを追加して、そのクラス内にロガーを構成する必要があります。

loggingライブラリは、4つのコンポーネントを導入します:loggershandlersfilters、およびformatters

  • ロガーは、アプリケーションコードが直接使用するインターフェイスを公開します。
  • ハンドラーは、ログ記録(ロガーによって作成された)を適切な宛先に送信します。
  • フィルタは、出力するログレコードを決定するための、よりきめ細かい機能を提供します。
  • フォーマッタは、最終出力でログレコードのレイアウトを指定します。

一般的なプロジェクト構造は次のようになります。

_Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...
_

コード内で(module.pyなど)モジュールのロガーインスタンスを参照して、特定のレベルでイベントを記録します。

ロガーに名前を付けるときに使用する適切な規則は、ロギングを使用する各モジュールで、次の名前のモジュールレベルのロガーを使用することです。

_logger = logging.getLogger(__name__)
_

特殊変数___name___はモジュールの名前を参照し、アプリケーションのコード構造に応じて_project.package.module_のようになります。

module.py(および他のクラス)は基本的に次のようになります。

_import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')
_

各モジュールのロガーは、イベントを親ロガーに伝達し、親ロガーはその情報を添付のhandlerに渡します! pythonパッケージ/モジュール構造と同様に、親ロガーは「ドット付きモジュール名」を使用して名前空間によって決定されます。そのため、特別な___name___でロガーを初期化するのが理にかなっています。変数(上記の例ではnameは文字列 "project.package.module")と一致します。

ロガーをグローバルに構成するには、2つのオプションがあります。

  • project.pyでロガーをインスタンス化して、名前が___package___で "project"と等しいこの例では、すべてのサブモジュールのロガーの親ロガーです。適切なハンドラーとフォーマッターをthisロガーに追加するだけです。

  • 最上位パッケージの名前を使用して、実行スクリプト内のハンドラーとフォーマッター(main.pyなど)でロガーをセットアップします。

ロギングを使用するライブラリを開発するときは、ライブラリがロギングを使用する方法(たとえば、使用するロガーの名前)を文書化するように注意する必要があります。

たとえば、main.pyのような実行スクリプトは、最終的に次のようになります。

_import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __== '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()
_

メソッド呼び出しlog.setLevel(...)は、ロガーがhandleを処理する最も低い重大度のログメッセージを指定しますが、必ずしも出力する必要はありません!メッセージの重大度レベルが設定されたものよりも高い(または等しい)限り、メッセージがハンドラーに渡されることを意味します。ただし、handlerは、ログメッセージの処理を処理します(たとえば、印刷または保存することによって)。

したがって、loggingライブラリーは、構造化されたモジュール式のアプローチを提供します。これは、必要に応じて活用するだけです。

ロギングドキュメント

41
Dennis

ログモジュールでcustomLoggerのインスタンスを作成し、シングルトンとして使用します。クラスではなく、インポートされたインスタンスを使用します。

9
Amber

最初のピリオドの前に、共通のサブストリングを持つストリングを渡すことができます。ピリオド( "。")で区切られた文字列の部分は、さまざまなクラス/モジュール/ファイル/などに使用できます。同様に(特にlogger = logging.getLogger(loggerName)部分):

def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
    base = os.path.basename(__file__)
    loggerName = "%s.%s" % (base, name)
    logFileName = os.path.join(logdir, "%s.log" % loggerName)
    logger = logging.getLogger(loggerName)
    logger.setLevel(level)
    i = 0
    while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
        i += 1
        logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
    try:
        #fh = logging.FileHandler(logFileName)
        fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
    except IOError, exc:
        errOut = "Unable to create/open log file \"%s\"." % logFileName
        if exc.errno is 13: # Permission denied exception
            errOut = "ERROR ** Permission Denied ** - %s" % errOut
        Elif exc.errno is 2: # No such directory
            errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
        Elif exc.errno is 24: # Too many open files
            errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
        else:
            errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
        raise LogException(errOut)
    else:
        formatter = logging.Formatter(logformat)
        fh.setLevel(level)
        fh.setFormatter(formatter)
        logger.addHandler(fh)
    return logger

class MainThread:
    def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
        self.logdir = logdir
        logLevel = logging.DEBUG
        logPrefix = "MainThread_TEST" if self.test else "MainThread"
        try:
            self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
        except LogException, exc:
            sys.stderr.write("%s\n" % exc)
            sys.stderr.flush()
            os._exit(0)
        else:
            self.logger.debug("-------------------- MainThread created.  Starting __init__() --------------------")

    def run(self):
        self.logger.debug("Initializing ReportThreads..")
        for (group, cfg) in self.config.items():
            self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------     " % group)
            for k2, v2 in cfg.items():
                self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
            try:
                rt = ReportThread(self, group, cfg, self.logdir, self.test)
            except LogException, exc:
                sys.stderr.write("%s\n" % exc)
                sys.stderr.flush()
                self.logger.exception("Exception when creating ReportThread (%s)" % group)
                logging.shutdown()
                os._exit(1)
            else:
                self.threads.append(rt)
        self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
        for t in self.threads:
            t.Start()
        if not self.test:
            self.loop()


class ReportThread:
    def __init__(self, mainThread, name, config, logdir, test):
        self.mainThread = mainThread
        self.name = name
        logLevel = logging.DEBUG
        self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
        self.logger.info("init database...")
        self.initDB()
        # etc....

if __== "__main__":
    # .....
    MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
3
chown