Pythonログ形式の文字列にカスタムフィールドを追加するにはどうすればよいですか?


95

私の現在のフォーマット文字列は次のとおりです。

formatter = logging.Formatter('%(asctime)s : %(message)s')

そして、app_nameこのフォーマッターを含む各スクリプトで異なる値を持つという新しいフィールドを追加したいと思います。

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

しかし、そのapp_name値をロガーに渡してフォーマット文字列に補間する方法がわかりません。毎回渡すことで明らかにログメッセージに表示させることができますが、これは面倒です。

私はもう試した:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

しかし、どれも機能しません。


本当にこれをすべてのlog呼び出しに渡しますか?もしそうなら、「この機能はあなた自身の値をLogRecordに注入するために使用することができます…」と書かれているドキュメントを見てください。しかし、これはlogger = logging.getLogger('myapp')それを使用してlogger.info呼び出しに焼き付けるための主要なケースのようです。
abarnert 2013

Pythonロギングはすでにそのafaikを行うことができます。logger各アプリで異なるオブジェクトを使用する場合は、次loggerのようにインスタンス化することで、それぞれに異なる名前を使用させることができますlogger = logging.getLogger(myAppName)。これ__name__はPythonモジュール名であることに注意してください。したがって、各アプリが独自のPythonモジュールである場合は、それも機能します。
フロリアンカステラーヌ

回答:


135

LoggerAdapterを使用すると、ロギング呼び出しごとに追加情報を渡す必要がなくなります。

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

ログ(のようなもの)

2013-07-09 17:39:33,596 Super App : The sky is so blue

フィルタを使用して、コンテキスト情報を追加することもできます。

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

同様のログレコードを生成します。


3
それをconfig.iniファイルでどのように指定できますか?現在のホスト名を追加したいと思いsocket.gethostname()ます。
Laurent LAPORTE 2016

このサンプルは機能しません。 import uuid uniqueId = str(uuid.uuid4()) extra = {"u_id" : uniqueId} RotatingHandler = RotatingFileHandler(LOG_FILENAME,encoding='utf-8',maxBytes=maxSize, backupCount=batchSize) logger.basicConfig(handlers=[RotatingHandler],level=logLevel.upper(),format='%(levelname)s %(u_id)s %(funcName)s %(asctime)s %(message)s ',datefmt='%m/%d/%Y %I:%M:%S %p') logger = logger.LoggerAdapter(logger=logger, extra=extra)
ハヤト

「levelname」と等しいフィールド「level」を追加することは可能ですか?参照:Pythonログメッセージで「levelname」の名前を「level」に変更するにはどうすればよいですか?
Martin Thoma

2
追加情報の文字列を渡すだけでいいですか。次のようなもの:「従業員ID1029382でエラーが発生しました」辞書を作成せずに。
shreeshkatti19年

50

そのようにするには、dictをパラメーターとしてextraに渡す必要があります。

logging.info('Log message', extra={'app_name': 'myapp'})

証明:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

また、注意として、dictを渡さずにメッセージをログに記録しようとすると、失敗します。

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

これも機能しlogging.info()ますか?最後に試したときに失敗しました。:/
Prakhar Mohan Srivastava 2015

2
@ mr2ertの答えが好きです。logging.Formatterクラスを拡張することにより、追加フィールドにデフォルト値を与えることができます:class CustomFormatter(logging.Formatter):def format(self、record):if not hasattr(record、 'foo'):record.foo = 'default_foo' return super(CustomFormatter、self.format(record)h = loggin.StreamHandler()h.setFormatter(CustomFormatter( '%(foo)s%(message)s')logger = logging.getLogger( 'bar')logger.addHandler( h)logger.error( 'hey!'、extra = {'foo': 'FOO'})logger.error( 'hey!')
loutre 2016年

この方法の方が高速ですが、各ログメッセージに行を追加する必要があります。これは、忘れやすく、エラーが発生しやすいものです。super()呼び出しを置き換えることは、unutbuからの答えよりも厄介です。
pevogam 2016

@Prakhar Mohan Srivastavaはいlogging.info()でも問題なく動作します。どのようなエラーメッセージが表示されますか?
shreeshkatti19年

追加情報の文字列を渡すだけでいいですか。次のようなもの:「従業員ID 1029382でエラーが発生しました」辞書を作成せず、キーを渡さない
shreeshkatti19年

25

Python3

Python3.2以降、LogRecordFactoryを使用できるようになりました

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

もちろんrecord_factory、任意の呼び出し可能にカスタマイズすることがcustom_attributeでき、ファクトリ呼び出し可能への参照を保持する場合はの値を更新できます。

アダプター/フィルターを使用するよりも優れているのはなぜですか?

  • ロガーをアプリケーションに渡す必要はありません
  • 実際には、(を呼び出すだけでlogger = logging.getLogger(..))独自のロガーを使用するサードパーティライブラリで動作し、同じログ形式になります。(これは、同じロガーオブジェクトを使用する必要があるフィルター/アダプターには当てはまりません)
  • 複数のファクトリをスタック/チェーンできます

Python 2.7の代替手段はありますか?
karolch

同じ利点はありません。2.7では、アダプターまたはフィルターを使用する必要があります。
アフマド

5
これはnowadayののpython3最良の答えである
ステファン・

docs.python.org/3/howto/logging-cookbook.htmlによると:このパターンにより、さまざまなライブラリがファクトリをチェーン化でき、互いの属性を上書きしたり、標準として提供されている属性を意図せずに上書きしたりしない限り、驚くべきことではありません。ただし、チェーン内の各リンクはすべてのロギング操作に実行時のオーバーヘッドを追加することに注意してください。この手法は、フィルターを使用しても目的の結果が得られない場合にのみ使用してください。
steve0hh

1
@ steve0hh重要な望ましい結果の1つは、さまざまなライブラリ/モジュール間でコンテキスト情報をログに記録する機能です。これは、この方法を使用した場合にのみ実現できます。ほとんどの場合、ライブラリはロガー構成に触れてはなりません。親アプリケーションの責任です。
アフマド

10

別の方法は、カスタムLoggerAdapterを作成することです。これは、形式を変更できない場合、または形式が一意のキー(この場合はapp_name)を送信しないコードと共有されている場合に特に役立ちます。

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

そして、コードでは、通常どおりロガーを作成して初期化します。

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

最後に、必要に応じてプレフィックスを追加するラッパーアダプターを作成します。

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

出力は次のようになります。

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

1

自分で実装した後、このSOの質問を見つけました。それが誰かを助けることを願っています。以下のコードclaim_idでは、ロガー形式で呼び出される追加のキーを誘導しています。claim_id環境にキーが存在する場合は常に、claim_idをログに記録します。私のユースケースでは、AWSLambda関数についてこの情報をログに記録する必要がありました。

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'


class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})


def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger


LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

要旨:https//gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652


1

受け入れられた回答は、フォーマットをログファイルに記録しませんでしたが、フォーマットはsys出力に反映されていました。あるいは、より単純なアプローチを使用して、次のように作業しました。

logging.basicConfig(filename="mylogfile.test",
                    filemode="w+",
                    format='%(asctime)s: ' +app_name+': %(message)s ',
                    level=logging.DEBUG)


0

mr2ertの答えを使用して、この快適なソリューションを思いつきました(推奨されていないと思いますが)-組み込みのロギングメソッドをオーバーライドしてカスタム引数を受け入れextra、メソッド内に辞書を作成します。

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

出力:

2019-03-02 20:06:51,998 [bar] test

これは、参照用の組み込み関数です。

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)

0

インポートログ;

クラスLogFilter(logging.Filter):

def __init__(self, code):
    self.code = code

def filter(self, record):
    record.app_code = self.code
    return True

logging.basicConfig(format = '[%(asctime)s:%(levelname)s] :: [%(module)s->%(name)s] -APP_CODE:%(app_code)s-MSG:%(message )s ');

クラスロガー:

def __init__(self, className):
    self.logger = logging.getLogger(className)
    self.logger.setLevel(logging.ERROR)

@staticmethod
def getLogger(className):
    return Logger(className)

def logMessage(self, level, code, msg):
    self.logger.addFilter(LogFilter(code))

    if level == 'WARN':        
        self.logger.warning(msg)
    elif level == 'ERROR':
        self.logger.error(msg)
    else:
        self.logger.info(msg)

クラステスト:logger = Logger.getLogger( 'Test')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')

この実装は非常に非効率的です。
blakev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.