2016-09-14 9 views
2

Pythonのlogging.Loggerクラスを継承する基本的なログクラスを使いたいと思います。しかし、継承されたロガーをカスタマイズするために必要な基礎を確立できるように、クラスをどのように構築するべきかはわかりません。logger.Loggingクラスを拡張するには?

これは私がこれまでのところ、私のlogger.pyファイルにしたものである:

import entity 
from core import MyLogger 

my_logger = MyLogger("myApp") 

def cmd(): 
    my_logger.info("Hello from %s!" % ("__CMD")) 

entity.third_party() 
entity.another_function() 
cmd() 

そして、これがentity.pyモジュールです::

import sys 
import logging 
from logging import DEBUG, INFO, ERROR 

class MyLogger(object): 
    def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO): 
     # Initial construct. 
     self.format = format 
     self.level = level 
     self.name = name 

     # Logger configuration. 
     self.console_formatter = logging.Formatter(self.format) 
     self.console_logger = logging.StreamHandler(sys.stdout) 
     self.console_logger.setFormatter(self.console_formatter) 

     # Complete logging config. 
     self.logger = logging.getLogger("myApp") 
     self.logger.setLevel(self.level) 
     self.logger.addHandler(self.console_logger) 

    def info(self, msg, extra=None): 
     self.logger.info(msg, extra=extra) 

    def error(self, msg, extra=None): 
     self.logger.error(msg, extra=extra) 

    def debug(self, msg, extra=None): 
     self.logger.debug(msg, extra=extra) 

    def warn(self, msg, extra=None): 
     self.logger.warn(msg, extra=extra) 

これがメインmyApp.pyある

# Local modules 
from core import MyLogger 

# Global modules 
import logging 
from logging import DEBUG, INFO, ERROR, CRITICAL 

my_logger = MyLogger("myApp.entity", level=DEBUG) 

def third_party(): 
    my_logger.info("Initial message from: %s!" % ("__THIRD_PARTY")) 

def another_function(): 
    my_logger.warn("Message from: %s" % ("__ANOTHER_FUNCTION")) 

メインアプリケーションを実行すると、次のようになります。

2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY! 
2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY! 
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION 
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION 
2016-09-14 12:40:50,445 | INFO | Hello from __CMD! 
2016-09-14 12:40:50,445 | INFO | Hello from __CMD! 

おそらく私がロガークラスを適切に設定できなかったため、すべてが2回印刷されます。

--- UPDATE(01):私の目標の明確化---

(1)私はこれを行うことができますので、1つの場所での主なロギング機能をカプセル化したいと思います:

from mylogger import MyLogger 
my_logger = MyLogger("myApp") 
my_logger.info("Hello from %s!" % ("__CMD")) 

(2)私はCustomFormatterCustomAdapterクラスを使用する予定です。このビットはカスタムロギングクラスを必要とせず、すぐにプラグインすることができます。

(3)私はおそらくlogger.infoを傍受、根本的なロガークラス(記録など)のカスタマイズの面で非常に深い行く必要はありません、loggin.debugなど十分なはずです。

だから、戻ってこれらのフォーラムで何度も循環されていますthis python receiptへの参照:

私はLogger Classを持つとの甘い点を見つけ、それでもFormattersを割り当てるように構築された関数を使用することができるようにしようとしていますAdaptersなどです。したがって、すべてがloggingモジュールと互換性があります。

class OurLogger(logging.getLoggerClass()): 
    def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): 
     # Don't pass all makeRecord args to OurLogRecord bc it doesn't expect "extra" 
     rv = OurLogRecord(name, level, fn, lno, msg, args, exc_info, func) 
     # Handle the new extra parameter. 
     # This if block was copied from Logger.makeRecord 
     if extra: 
      for key in extra: 
       if (key in ["message", "asctime"]) or (key in rv.__dict__): 
        raise KeyError("Attempt to overwrite %r in LogRecord" % key) 
       rv.__dict__[key] = extra[key] 
     return rv 

--- UPDATE(02):作業中の可能な解決策 ---

私は可能な解決策を実証する簡単なPythonアプリケーションとのレポを作成しました。ピーク時には気をつけて、これを改善してください。

xlog_example

この例では、効果的に継承によってlogging.Loggerクラスとlogging.LogRecordクラスをオーバーライドする手法を示しています。

ログストリームには、FormattersまたはAdaptersを使用せずに、2つの外部項目が混在しています(funcnameおよびusername)。

+0

なぜあなたはそれをしますか? –

+0

@ JonasWielicki私は、既存の機能に基づいてロギングメカニズムをカプセル化したいと思います。私はカスタムの 'Formatters'、' Handlers'、 'Adapters'などで作業したいと思います。アイデアは、ロギングの言いましたが、私はこれがいつもコミュニティである程度の論争を起こしていることを知っています。私はまだカスタムクラスなどでそれを行う方法があると信じています。私はそれを行う他の**公式**の方法があることを知っています。 – symbolix

+0

私は動作する基本バージョンでレポを作成しました。詳細は 'UPDATE(02)'を参照してください。 – symbolix

答えて

4

この段階では、私がこれまでに行った研究と解決策をまとめようという意図がある例が、私の質問に対する答えとして十分であると私は信じています。一般に、ロギングソリューションをラップするために利用できる多くのアプローチがあります。この具体的な質問は、logging.Loggerクラス継承を利用したソリューションに焦点を当て、内部メカニックスを変更できるようにすることを目的としていましたが、元のlogging.Loggerクラスによって提供されるため、

これは、クラス継承のテクニックは非常に注意して使用する必要があります。ロギングモジュールによって提供される多くの機能は、安定したロギングワークフローを維持して実行するのに十分です。 logging.Loggerクラスを継承することは、ログデータが処理され、エクスポートされる方法の基本的な変更が目標である場合にはおそらく有効です。

は、私はログ機能をラップするための2つのアプローチがあることがわかり、これを要約すると:

1)伝統ログ:これは単に提供するロギングメソッドや関数が、ラップで作業している

一般的な反復タスクの一部が1つの場所にまとめられるように、それらをモジュールにまとめます。このようにして、ログファイル、ログレベル、カスタムFiltersAdaptersなどの管理などは簡単になります。

このシナリオではclassのアプローチを利用できるかどうかはわかりません(2番目のアイテムのトピックであるスーパークラスのアプローチについては言及していません)。ロギング時に複雑になっているようです呼び出しはクラス内でラップされます。私はこの問題について聞きたいと思います。私は間違いなくこの側面を探る質問を準備します。

2)ロガー継承:

このアプローチは、オリジナルlogging.Loggerクラスから継承し、既存の方法に追加または完全に内部動作を変更することによって、それらをハイジャックに基づいています。メカニックは、次のコードビットに基づいています。ここでから

# Register our logger. 
logging.setLoggerClass(OurLogger) 
my_logger = logging.getLogger("main") 

、我々は我々自身のLoggerに依存している、まだ我々はまだ他のロギング機能のすべての恩恵を受けることができます:

# We still need a loggin handler. 
ch = logging.StreamHandler() 
my_logger.addHandler(ch) 

# Confgure a formatter. 
formatter = logging.Formatter('LOGGER:%(name)12s - %(levelname)7s - <%(filename)s:%(username)s:%(funcname)s> %(message)s') 
ch.setFormatter(formatter) 

# Example main message. 
my_logger.setLevel(DEBUG) 
my_logger.warn("Hi mom!") 

AdaptersまたはFormattersを使用せずに2つのデータビットusernameおよびfuncnameの注入を実証するので、この例は重要です。

この解決方法の詳細については、xlog.py repoを参照してください。これは、私がother questionsと他のsourcesのコードのビットに基づいて準備した例です。

2

このライン

self.logger = logging.getLogger("myApp") 

常に同じロガーへの参照を取得し、あなたはそれにあなたがMyLoggerをインスタンス化するたびに、追加のハンドラを追加しています。異なる引数を指定してMyLoggerを呼び出すと、次のように現在のインスタンスが修正されます。

self.logger = logging.getLogger(name) 

しかし、あなたが複数回同じ name引数を渡す場合、あなたはまだ同じ問題を抱えていることに注意してください。

クラスでは、既に設定されているロガーを把握する必要があります。

class MyLogger(object): 
    loggers = set() 
    def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO): 
     # Initial construct. 
     self.format = format 
     self.level = level 
     self.name = name 

     # Logger configuration. 
     self.console_formatter = logging.Formatter(self.format) 
     self.console_logger = logging.StreamHandler(sys.stdout) 
     self.console_logger.setFormatter(self.console_formatter) 

     # Complete logging config. 
     self.logger = logging.getLogger(name) 
     if name not in self.loggers: 
      self.loggers.add(name) 
      self.logger.setLevel(self.level) 
      self.logger.addHandler(self.console_logger) 

これは、ロガーをまったく再構成することはできませんが、これを正しく行う方法を理解するための練習として残しています。

ただし、重要な点は、同じ名前の2つの個別に設定されたロガーを持つことができないことです。もちろん


logging.getLoggerは常に与えられた名前のために同じオブジェクトへの参照を返すということは、あなたのクラスはloggingモジュールとの対立で働いていることを意味します。プログラムの起動時に初めてロガーを設定してから、必要に応じてgetLoggerで参照を取得してください。

+0

なぜロギングを設定できないのですか?私の目標は、 'logging.Logger'オブジェクトのように動作するロギングクラスを作成することですが、カスタマイズされたさまざまなオプションを内部的にカプセル化します。私はこれを説明する多くのプロジェクトと投稿があることを知っています。私はこれをしばらく研究しています。残念ながら、情報は散在しており、理解しづらいものです。そのため、私はこのコンセプトの基礎を提供する最も基本的なソリューションを考え出し、カスタマイズの面でさらなる作業を可能にしています。 – symbolix

+0

最も基本的なレベルでは、最初に特定のロガーを設定しようとするときにのみ実行される 'if'ステートメントに設定呼び出しを置くので、ロガーを再設定することはできません。ハンドラのようなものについては、*ハンドラを設定していない、新しいハンドラを追加しています。 – chepner

関連する問題