ฉันจะเพิ่มฟิลด์ที่กำหนดเองลงในสตริงรูปแบบบันทึก Python ได้อย่างไร


96

สตริงรูปแบบปัจจุบันของฉันคือ:

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โทร
ยกเลิก

การบันทึกไพ ธ อนสามารถทำ afaik ได้แล้ว ถ้าคุณใช้ที่แตกต่างกันloggerของวัตถุในแต่ละ app ที่คุณสามารถทำให้แต่ละคนใช้ชื่อที่แตกต่างกันโดยอินสแตนซ์ของคุณเหมือนดังนั้น:logger logger = logging.getLogger(myAppName)โปรดทราบว่านั่น__name__คือชื่อโมดูล python ดังนั้นหากแต่ละแอปเป็นโมดูล python ของตัวเองสิ่งนั้นก็ใช้ได้เช่นกัน
Florian Castellane

คำตอบ:


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

ฉันมีตัวอย่างนี้ไม่ได้ผลสำหรับฉัน 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)
Hayat

เป็นไปได้หรือไม่ที่จะเพิ่มฟิลด์ "ระดับ" ซึ่งมีค่าเท่ากับ "levelname"? โปรดดู: ฉันจะเปลี่ยนชื่อ“ levelname” เป็น“ level” ในข้อความบันทึก Python ได้อย่างไร
Martin Thoma

2
ฉันสามารถส่งสตริงข้อมูลพิเศษได้ไหม สิ่งนี้: "เกิดข้อผิดพลาดสำหรับรหัสพนักงาน 1029382" โดยไม่ต้องสร้างพจนานุกรมใด ๆ
shreesh katti

51

คุณต้องส่งคำสั่งเป็นพารามิเตอร์ไปยังส่วนเสริมเพื่อทำเช่นนั้น

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 

นอกจากนี้โปรดทราบว่าหากคุณพยายามบันทึกข้อความโดยไม่ผ่านคำสั่งนั้นจะล้มเหลว

>>> 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

2
ชอบคำตอบ @ mr2ert คุณสามารถกำหนดค่าดีฟอลต์ให้กับฟิลด์พิเศษได้โดยการขยายlogging.Formatterคลาส: คลาส CustomFormatter (logging.Formatter): รูปแบบ def (self, record): if not hasattr (record, 'foo'): record.foo = 'default_foo' return super (CustomFormatter, self.format (บันทึก) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ('% (foo) s% (ข้อความ) s') คนตัดไม้ = logging.getLogger ('bar') logger.addHandler ( h) logger.error ('hey!', extra = {'foo': 'FOO'}) logger.error ('hey!')
loutre

วิธีนี้เร็วกว่า แต่คุณต้องเพิ่มบรรทัดพิเศษในแต่ละข้อความบันทึกซึ่งง่ายต่อการลืมและมีแนวโน้มที่จะเกิดข้อผิดพลาด การแทนที่การโทร super () นั้นยุ่งกว่าคำตอบจาก unutbu
pevogam

@Prakhar Mohan Srivastava ใช่มันจะทำงานได้ดีสำหรับ logging.info () เช่นกัน คุณได้รับข้อความแสดงข้อผิดพลาดอะไร
shreesh katti

ฉันสามารถส่งสตริงข้อมูลพิเศษได้ไหม สิ่งนี้: "เกิดข้อผิดพลาดสำหรับรหัสพนักงาน 1029382" โดยไม่ต้องสร้างพจนานุกรมและส่งคีย์
shreesh katti

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สามารถอัปเดตมูลค่าได้หากคุณยังคงอ้างอิงถึงโรงงานที่เรียกได้

เหตุใดจึงดีกว่าการใช้ Adapters / Filters

  • คุณไม่จำเป็นต้องส่งคนตัดไม้ของคุณไปรอบ ๆ แอปพลิเคชัน
  • ใช้งานได้จริงกับไลบรารีของบุคคลที่สามที่ใช้คนตัดไม้ของตัวเอง (โดยการโทรlogger = logging.getLogger(..)) ตอนนี้จะมีรูปแบบบันทึกเหมือนกัน (นี่ไม่ใช่กรณีของฟิลเตอร์ / อะแดปเตอร์ที่คุณต้องใช้อ็อบเจ็กต์คนตัดไม้เดียวกัน)
  • คุณสามารถวางซ้อน / เชื่อมโรงงานได้หลายแห่ง

มีทางเลือกอื่นสำหรับ python 2.7 หรือไม่?
karolch

ไม่ได้มีประโยชน์เหมือนกันกับ 2.7 คุณต้องใช้ Adapters หรือ Filters
Ahmad

5
นี่คือคำตอบที่ดีที่สุดของ python3 ในวันนี้
Stéphane

อ้างอิงจากdocs.python.org/3/howto/logging-cookbook.html : รูปแบบนี้ช่วยให้ไลบรารีที่แตกต่างกันสามารถเชื่อมโยงโรงงานเข้าด้วยกันได้และตราบใดที่พวกเขาไม่เขียนทับแอตทริบิวต์ของกันและกันหรือเขียนทับแอตทริบิวต์ที่ให้ไว้เป็นมาตรฐานโดยไม่ได้ตั้งใจ ไม่ควรแปลกใจ อย่างไรก็ตามโปรดทราบว่าแต่ละลิงก์ในเครือข่ายจะเพิ่มค่าใช้จ่ายรันไทม์ให้กับการดำเนินการบันทึกทั้งหมดและควรใช้เทคนิคนี้ก็ต่อเมื่อการใช้ตัวกรองไม่ได้ให้ผลลัพธ์ที่ต้องการ
steve0hh

1
@ steve0hh หนึ่งในผลลัพธ์ที่ต้องการที่สำคัญคือความสามารถในการบันทึกข้อมูลบริบทในไลบรารี / โมดูลต่างๆซึ่งทำได้โดยใช้วิธีนี้เท่านั้น ในสถานการณ์ส่วนใหญ่ไลบรารีไม่ควรสัมผัสกับคอนฟิกูเรชันคนตัดไม้ถือเป็นความรับผิดชอบของแอปพลิเคชันหลัก
Ahmad

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)

สุดท้ายคุณจะสร้างอะแดปเตอร์ Wrapper เพื่อเพิ่มคำนำหน้าตามต้องการ:

    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คีย์อยู่ในสภาพแวดล้อม ในกรณีการใช้งานของฉันฉันจำเป็นต้องบันทึกข้อมูลนี้สำหรับฟังก์ชัน AWS Lambda

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")

Gist: 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:% (ข้อความ ) s ');

คลาส Logger:

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.