私はあなた自身の設定でどこでも使用できるグローバルロガーを実装する方法を疑問に思っていました:
現在、カスタムロガークラスがあります。
class customLogger(logging.Logger):
...
このクラスは、いくつかのフォーマッタやその他のものを含む別のファイルにあります。ロガーは単独で完全に動作します。
このモジュールをメインのpythonファイルにインポートし、次のようなオブジェクトを作成します。
self.log = logModule.customLogger(arguments)
しかし、明らかに、コードの他の部分からこのオブジェクトにアクセスすることはできません。間違ったアプローチを使用していますか?これを行うためのより良い方法はありますか?
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
満足のいく答えが見つからなかったので、Pythonの標準に付属しているlogging
ライブラリの動作と意図を洞察するために、質問の答えについて少し詳しく説明したいと思います。としょうかん。
OP(元のポスター)のアプローチとは対照的に、ライブラリーはロガーへのインターフェースとロガー自体の構成を明確に分離します。
ハンドラーの構成は、ライブラリーを使用するアプリケーション開発者の特権です。
つまり、notカスタムロガークラスを作成し、構成などを追加して、そのクラス内にロガーを構成する必要があります。
logging
ライブラリは、4つのコンポーネントを導入します:loggers、handlers、filters、および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
ライブラリーは、構造化されたモジュール式のアプローチを提供します。これは、必要に応じて活用するだけです。
ログモジュールでcustomLogger
のインスタンスを作成し、シングルトンとして使用します。クラスではなく、インポートされたインスタンスを使用します。
最初のピリオドの前に、共通のサブストリングを持つストリングを渡すことができます。ピリオド( "。")で区切られた文字列の部分は、さまざまなクラス/モジュール/ファイル/などに使用できます。同様に(特に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)