複数のモジュールでのロギングの使用


257

次の構造を持つ小さなpythonプロジェクトがあります-

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

デフォルトのロギングモジュールを使用して、メッセージをstdoutおよびログファイルに出力する予定です。ロギングモジュールを使用するには、いくつかの初期化が必要です-

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

現在、メッセージのロギングを開始する前に、すべてのモジュールでこの初期化を実行しています。プロジェクト全体にログを記録することで同じ設定が再利用されるように、この初期化を1か所で1回だけ実行することは可能ですか?


3
私の回答に対するあなたのコメントへの応答:それらのすべてにロジックがfileConfigない限り、ロギングを行うすべてのモジュールを呼び出す必要はありませんif __name__ == '__main__'。パッケージがライブラリである場合は、prostの回答は適切ではありませんが、うまくいく可能性がありますNullHandler
Vinay Sajip 2013

1
prostは、すべてのモジュールでインポートとロガーのstmtを呼び出す必要があり、メインモジュールのfileconfig stmtのみを呼び出す必要があることを示唆しています。あなたが言っていることに似ていませんか?
Quest Monger 2013

6
prostは、ロギング構成コードをに配置する必要があると述べていますpackage/__init__.py。これは通常、if __name__ == '__main__'コードを配置する場所ではありません。また、prostの例は、インポート時に無条件に構成コードを呼び出すように見えますが、私には正しく見えません。通常、設定コードのロギングは1か所で行う必要があり、__ main__をインポートする場合を除いて、インポートの副作用として発生しないようにする必要があります。
Vinay Sajip 2013

あなたは正しい、私は彼のコードサンプルの行「#package / __ init__.py」を完全に逃しました。その指摘とあなたの忍耐に感謝します。
Quest Monger 2013

1
では、複数あるとif __name__ == '__main__'どうなりますか?(これが事実であるかどうかは、問題として明示的に言及されていません)
kon psych

回答:


293

各モジュールで、ロガーを次のように定義することをお勧めします。

import logging
logger = logging.getLogger(__name__)

モジュールの上部近く、そしてモジュールの他のコードで、例えば

logger.debug('My message with %s', 'variable data')

モジュール内でロギングアクティビティを分割する必要がある場合は、たとえば

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

とにログを記録loggerAし、loggerB必要に応じて。

メインプログラムで、たとえば次のようにします。

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

または

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

参照してくださいここで複数のモジュールからログインするため、そしてここで他のコードによって、ライブラリモジュールとして使用されるコードのロギング構成のために。

更新:を呼び出すときにfileConfig()disable_existing_loggers=FalsePython 2.6以降を使用しているかどうかを指定できます(詳細については、ドキュメントを参照しください)。デフォルト値はTrue下位互換性のためのものであり、既存のすべてのロガーfileConfig()は、構成でそのロガーまたはその祖先が明示的に指定されていない限り無効になります。値をに設定するとFalse、既存のロガーはそのままになります。Python 2.7 / Python 3.2以降を使用している場合dictConfig()fileConfig()、構成をより詳細に制御できるAPIよりも優れていると考えることができます。


21
私の例を見れば、私はすでにあなたが上で提案したことをしています。私の質問は、これらの3つのステートメントを繰り返す必要がないように、このログの初期化をどのように集中化するかでした。また、あなたの例では、 'logging.config.fileConfig(' logging.conf ')' stmtがありませんでした。このstmtは実際、私の懸念の根本的な原因です。すべてのモジュールでロガーを開始した場合、すべてのモジュールでこのstmtを入力する必要があります。これは、すべてのモジュールでconfファイルのパスを追跡することを意味します。これは、私にとってベストプラクティスのようには見えません(モジュール/パッケージの場所を変更するときの混乱を想像してください)。
Quest Monger 2013

4
ロガーの作成後にfileConfigを呼び出すと、同じモジュールでも別のモジュールでも(たとえば、ファイルの上部にロガーを作成する場合)機能しません。ロギング構成は、後で作成されたロガーにのみ適用されます。したがって、このアプローチは機能しないか、複数のモジュールに対して実行可能なオプションではありません。@Quest Monger:構成ファイルの場所を保持する別のファイルをいつでも作成できます..;)
Vincent Ketelaars

2
@Oxidator:必ずしもそうではありません- デフォルトのdisable_existing_loggersフラグTrueですが、に設定できますFalse
Vinay Sajip 2013

1
@Vinay Sajip、ありがとう。モジュールだけでなくクラス外でも機能するロガーの推奨事項はありますか?インポートはmain関数が呼び出される前に行われるため、これらのログはすでに記録されています。メインモジュールのすべてのインポートが唯一の方法になる前にロガーを設定すると思いますか?その後、必要に応じて、このロガーをメインで上書きできます。
Vincent Ketelaars 2013

1
すべてのモジュール固有のロガーにデフォルトの警告とは異なるログレベルを設定したい場合、各モジュールでその設定を行う必要がありますか?たとえば、すべてのモジュールをINFOでログに記録したいとします。
ラージ

128

実際、すべてのロガーは親のパッケージロガーの子です(つまり、package.subpackage.moduleから設定を継承するpackage.subpackage)ため、必要なのはルートロガーを設定することだけです。これは、logging.config.fileConfig(ロガーの独自の設定)またはlogging.basicConfig(ルートロガーを設定する)によって実現できます。。エントリモジュールのログインのセットアップ(__main__.pyまたは、実行したいものは何でも、たとえばmain_script.py。も__init__.py機能します)

basicConfigを使用:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

fileConfigを使用:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

そして、以下を使用してすべてのロガーを作成します:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

詳細については、「高度なロギングチュートリアル」を参照してください。


15
これは、これまでのところ、問題に対する最も簡単な解決策です。言うまでもなく、モジュール間の親子関係を明らかにし、活用します。これは、noobが知らなかったものです。ダンケ。
Quest Monger 2013

あなたが正しいです。そしてvinayが彼の投稿で指摘したように、あなたの解決策はinit .pyモジュールにない限り正しいです。メインモジュール(エントリポイント)に適用すると、ソリューションは機能しました。
Quest Monger 2013

2
質問は別々のモジュールに関係しているので、実際にははるかに適切な答えです。
Jan Sila

1
ばかげた質問かもしれません:ロガーがない__main__.py場合(たとえば、ロガーのないスクリプトでモジュールを使用したい場合)はlogging.getLogger(__name__)、モジュールで何らかのログを記録しますか、それとも例外を発生しますか?
ビル

1
最後に。ロガーは機能していましたが、Windowsでjoblibを使用して並列実行すると失敗しました。これはシステムを手動で調整することだと思います-Parallelの他の何かが間違っています。しかし、それは確かに機能します!ありがとう
B Furtado

17

いつも以下のようにします。

単一のpythonファイルを使用して、「log_conf.py」という名前のシングルトンパターンとしてログを構成します

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

別のモジュールで、設定をインポートするだけです。

from log_conf import Logger

Logger.logr.info("Hello World")

これは、単純かつ効率的に記録するシングルトンパターンです。


1
シングルトンパターンを詳しく説明していただきありがとうございます。私はこれを実装することを計画していましたが、@ prostソリューションははるかにシンプルで、私のニーズに完全に適合しています。しかし、私はあなたのソリューションが有用であると思います(メイン以外の)複数のエントリポイントを持つ大きなプロジェクト。ダンケ。
Quest Monger 2013

46
これは役に立たない。ルートロガーはすでにシングルトンです。Logger.logr.infoの代わりにlogging.infoを使用してください。
ポッド

9

これらの回答のいくつかは、モジュールの上部で行うことを示唆しています

import logging
logger = logging.getLogger(__name__)

これは非常に悪い習慣だと考えられていることは私の理解です。その理由は、ファイル構成がデフォルトですべての既存のロガーを無効にするためです。例えば

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

そしてあなたのメインモジュールで:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

既存のロガーがfileconfig呼び出しによって無効にされたため、logging.iniで指定されたログは空になります。

これを回避することは確かに可能ですが(disable_existing_Loggers = False)、実際には、ライブラリの多くのクライアントはこの動作を認識せず、ログを受け取りません。常にローカルでlogging.getLoggerを呼び出すことにより、クライアントにとって簡単にします。ハットヒント:この動作については、Victor LinのWebサイトから学びました。

したがって、代わりに常にローカルでlogging.getLoggerを呼び出すことをお勧めします。例えば

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

また、メインでfileconfigを使用する場合は、ライブラリ設計者がモジュールレベルのロガーインスタンスを使用する場合に備えて、disable_existing_loggers = Falseを設定します。


logging.config.fileConfig('logging.ini')前に走れないimport my_moduleこの回答で示唆されているように
lucid_dreamer 2017

確かではありませんが、インポートと実行可能コードをそのように混在させることは間違いなく悪い習慣と見なされます。また、クライアントがインポートする前にロギングを設定する必要があるかどうかをチェックする必要がないようにしたいのです。リクエストのような広く使用されているライブラリがそれを行っていたと想像してください...!
phil_20686 2017

「よくわからない-しかし、そのようにインポートと実行可能コードを混在させることは間違いなく悪い習慣と見なされるだろう。」- なぜ?
lucid_dreamer 2017

それがなぜ悪いのか、私はあまり明確ではありません。そして、私はあなたの例を完全に理解していません。この例の構成を投稿して、いくつかの使用法を示すことができますか?
lucid_dreamer 2017

1
あなたは公式のドキュメントに矛盾しているようです: 'ロガーに名前を付けるときに使用する良い慣習は、次のように名前が付けられた、ロギングを使用する各モジュールでモジュールレベルのロガーを使用することです:logger = logging.getLogger(__name__)'
iron9

9

複数のモジュールでロギングライブラリの1つのインスタンスを使用する簡単な方法は、次の解決策でした。

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

その他のファイル

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")

7

別のソリューションを投入する。

私のモジュールのinit .pyには次のようなものがあります:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

次に、各モジュールでロガーが必要です。

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

ログが欠落している場合は、ログを取得したモジュールによってソースを区別できます。


「モジュールのメインの初期化」とはどういう意味ですか?そして、「それから私はロガーを必要とする各クラスで、私はします:」called_module.pyのサンプルと、モジュールcaller_module.pyからのインポートとしての使用例を提供できますか?私が尋ねているフォーマットのインスピレーションについては、この回答参照してください。ひいきにしようとしないでください。私はあなたの答えを理解しようとしています、そしてあなたがそのようにそれを書いたとしたら私は知っているでしょう。
lucid_dreamer 2017

1
@lucid_dreamer明確にした。
トミー

4

あなたはこのようなものを思いつくこともできます!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

これで、上記が別のモジュールで定義され、ロギングが必要な場合に他のモジュールにインポートされる場合、同じモジュールでプロジェクト全体で複数のロガーを使用できます。

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")

4

@Yarkeeのソリューションはより良いように見えました。もう少し追加したいと思います-

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

そのため、LoggerManagerはアプリケーション全体にプラグイン可能です。それが理にかなっていて価値があることを願っています。


11
ロギングモジュールはすでにシングルトンを扱っています。logging.getLogger( "Hello")は、すべてのモジュールで同じロガーを取得します。
ポッド

2

いくつかの答えがあります。最終的には、私にとって意味のある、似たような別の解決策が得られました。おそらくあなたにも理解できるでしょう。私の主な目的は、レベルごとにログをハンドラーに渡すことができるようにすることでした(デバッグレベルのログをコンソールに、警告以上をファイルに)。

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

logger.pyという名前の素晴らしいutilファイルを作成しました:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

フラスコ。アプリは、フラスコ内のハードコードされた値です。アプリケーションロガーは常にモジュールの名前としてフラスコ。アプリで始まります。

今、各モジュールで、次のモードで使用できます:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

これにより、最小限の労力で「app.flask.MODULE_NAME」の新しいログが作成されます。


2

ベストプラクティスは、呼び出し側のメソッドにロガーハンドラーを提供するタスクを1つだけ持つモジュールを個別に作成することです。このファイルをm_logger.pyとして保存します

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

ここで、ロガーハンドラーが必要なときにgetlogger()メソッドを呼び出します。

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

1
これは、追加のパラメーターがない場合に適しています。しかし、たとえば、--debugアプリにオプションがあり、このパラメーターに基づいてアプリのすべてのロガーにログレベルを設定したい場合...
ゴッドファーザー

@TheGodfatherはい、この方法論ではこれを達成するのは困難です。この状況で私たちができることは、オブジェクトの作成時にフォーマッターをパラメーターとして受け取り、ロガーハンドラーを返す同様の機能を持つクラスを作成することです。
Mousam Singh

はい、同様のことを行いget_logger(level=logging.INFO)、ある種のシングルトンを返すようにしたので、メインアプリから初めて呼び出されたときに、ロガーとハンドラーを適切なレベルで初期化してloggerから、同じオブジェクトを他のすべてのメソッドに返します。
ゴッドファーザー

0

Pythonは初めてなので、これが望ましいかどうかはわかりませんが、ボイラープレートを書き直さない場合はうまく機能します。

モジュールとしてロードできるように、プロジェクトにはinit .py が必要です

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1)提案はここから来ます

次に、ロガーを他のファイルで使用するには:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

警告:

  1. ファイルはモジュールとして実行する必要があります。そうしないimport [your module]と機能しません。
    • python -m [your module name].[your filename without .py]
  2. プログラムのエントリポイントのロガーの名前はになりますが__main__、使用__name__するすべてのソリューションにその問題があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.