การบันทึกข้อมูลตัวแปรด้วยสตริงรูปแบบใหม่


85

ฉันใช้สิ่งอำนวยความสะดวกในการบันทึกสำหรับ python 2.7.3 เอกสารสำหรับ Python เวอร์ชันนี้กล่าวว่า :

แพ็กเกจการบันทึกล่วงหน้าวันที่ตัวเลือกการจัดรูปแบบที่ใหม่กว่าเช่น str.format () และ string.Template รองรับตัวเลือกการจัดรูปแบบที่ใหม่กว่าเหล่านี้ ...

ฉันชอบรูปแบบ 'ใหม่' ที่มีวงเล็บปีกกา ดังนั้นฉันกำลังพยายามทำสิ่งที่ชอบ:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

และรับข้อผิดพลาด:

TypeError: ไม่ใช่อาร์กิวเมนต์ทั้งหมดที่แปลงระหว่างการจัดรูปแบบสตริง

ฉันพลาดอะไรที่นี่?

ปล. ไม่อยากใช้

log.debug("format this message {0}".format(1))

เนื่องจากในกรณีนี้ข้อความจะถูกจัดรูปแบบเสมอโดยไม่คำนึงถึงระดับคนตัดไม้


1
คุณสามารถทำได้: log.debug("format this message%d" % 1)
ronak

1
คุณต้องกำหนดค่าFormatterให้ใช้ "{" เป็นสไตล์
mata

2
@ronak ขอบคุณสำหรับคำแนะนำ แต่ไม่ โปรดดูที่ส่วน "ps" ว่าทำไม BTW log.debug ("จัดรูปแบบข้อความนี้% d", 1) - ใช้งานได้ดี
MajesticRa

@mata จะกำหนดค่าอย่างไร? มีเอกสารกำกับการทำหรือไม่?
MajesticRa

@mata เจอแล้ว. ช่วยให้คำตอบเป็นคำตอบที่ถูกต้องขอบคุณอีกครั้ง
MajesticRa

คำตอบ:


38

แก้ไข:ดูStyleAdapterแนวทางในคำตอบของ @Dunesซึ่งแตกต่างจากคำตอบนี้ อนุญาตให้ใช้รูปแบบการจัดรูปแบบอื่นโดยไม่ต้องใช้สำเร็จรูปในขณะที่เรียกใช้เมธอดของ logger (debug (), info (), error () ฯลฯ


จากเอกสาร - การใช้รูปแบบการจัดรูปแบบทางเลือก :

การเรียกบันทึก (logger.debug (), logger.info () ฯลฯ ) ใช้เฉพาะพารามิเตอร์ตำแหน่งสำหรับข้อความบันทึกจริงเท่านั้นโดยมีพารามิเตอร์คีย์เวิร์ดที่ใช้เพื่อกำหนดตัวเลือกสำหรับวิธีจัดการกับการเรียกบันทึกจริงเท่านั้น (เช่นพารามิเตอร์คีย์เวิร์ด exc_info เพื่อระบุว่าควรบันทึกข้อมูลย้อนกลับหรือพารามิเตอร์คีย์เวิร์ดพิเศษเพื่อระบุข้อมูลบริบทเพิ่มเติมที่จะเพิ่มลงในบันทึก) ดังนั้นคุณจึงไม่สามารถเรียกใช้การบันทึกโดยตรงโดยใช้ str.format () หรือ string.Template syntax เนื่องจากภายในแพ็กเกจการบันทึกใช้% -formatting เพื่อรวมสตริงรูปแบบและอาร์กิวเมนต์ของตัวแปร จะไม่มีการเปลี่ยนแปลงสิ่งนี้ในขณะที่รักษาความเข้ากันได้แบบย้อนหลังเนื่องจากการโทรบันทึกทั้งหมดที่มีอยู่ในโค้ดที่มีอยู่จะใช้สตริง% -format

และ:

อย่างไรก็ตามมีวิธีที่คุณสามารถใช้ {} - และ $ - การจัดรูปแบบเพื่อสร้างข้อความบันทึกแต่ละข้อความของคุณ จำไว้ว่าสำหรับข้อความคุณสามารถใช้อ็อบเจกต์ที่กำหนดเองเป็นสตริงรูปแบบข้อความและแพ็กเกจการบันทึกจะเรียกใช้ str () บนอ็อบเจ็กต์นั้นเพื่อรับสตริงรูปแบบจริง

คัดลอกวางลงในwhereverโมดูล:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

จากนั้น:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

หมายเหตุ: การจัดรูปแบบจริงจะล่าช้าจนกว่าจะจำเป็นเช่นหากไม่มีการบันทึกข้อความ DEBUG การจัดรูปแบบจะไม่ดำเนินการเลย


4
ใน Python 3.6 คุณสามารถใช้ f-strings ดังนี้num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

12
@ P1h3r1e3d13 ซึ่งแตกต่างจากรหัสการบันทึกในคำตอบ f '' - สตริงจะทำการจัดรูปแบบทันที
jfs

1
ขวา. พวกเขาทำงานที่นี่เนื่องจากจัดรูปแบบและส่งคืนสตริงปกติก่อนที่จะเรียกใช้เมธอดบันทึก สิ่งนี้อาจเกี่ยวข้องกับใครบางคนหรือไม่ก็ได้ดังนั้นฉันคิดว่ามันควรค่าแก่การกล่าวถึงเป็นตัวเลือก
Jacktose

6
@Jacktose ฉันคิดว่าผู้ใช้ไม่ควรเข้าสู่ระบบโดยใช้ f-strings มันเอาชนะบริการรวมบันทึก (เช่นยาม) มีเหตุผลที่ดีที่การบันทึก stdlib จะชะลอการสร้างเทมเพลตสตริง
wim

30

นี่เป็นอีกทางเลือกหนึ่งที่ไม่มีปัญหาคำหลักที่กล่าวถึงในคำตอบของ Dunes สามารถจัดการ{0}อาร์กิวเมนต์positional ( ) เท่านั้นและไม่ใช่อาร์กิวเมนต์ keyword ( {foo}) นอกจากนี้ยังไม่ต้องใช้การเรียกสองครั้งในการจัดรูปแบบ (โดยใช้เครื่องหมายขีดล่าง) มันมีปัจจัย ick ของคลาสย่อยstr:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

คุณใช้สิ่งนี้:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

แน่นอนคุณสามารถลบเช็คที่ระบุไว้# optionalเพื่อบังคับให้ข้อความทั้งหมดผ่านอะแด็ปเตอร์ใช้การจัดรูปแบบรูปแบบใหม่


หมายเหตุสำหรับทุกคนที่อ่านคำตอบนี้ในอีกหลายปีต่อมา : เริ่มต้นด้วยPython 3.2คุณสามารถใช้พารามิเตอร์ styleกับFormatterวัตถุ:

การบันทึก (ณ 3.2) ให้การสนับสนุนที่ดีขึ้นสำหรับรูปแบบการจัดรูปแบบเพิ่มเติมทั้งสองนี้ styleชั้นฟอร์แมตรับการปรับปรุงเพื่อใช้เวลาเพิ่มเติมอีกพารามิเตอร์ตัวเลือกคำหลักชื่อ ค่านี้เป็นค่าเริ่มต้น'%'แต่ค่าอื่น ๆ ที่เป็นไปได้คือ'{'และ'$'ซึ่งสอดคล้องกับอีกสองสไตล์การจัดรูปแบบ ความเข้ากันได้ย้อนหลังจะคงไว้โดยค่าเริ่มต้น (ตามที่คุณคาดหวัง) แต่โดยการระบุพารามิเตอร์สไตล์อย่างชัดเจนคุณจะสามารถระบุสตริงรูปแบบที่ใช้ได้str.format()string.Templateหรือ

เอกสารให้ตัวอย่าง logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

โปรดทราบว่าในกรณีนี้คุณยังไม่สามารถเรียกใช้loggerรูปแบบใหม่ได้ กล่าวคือสิ่งต่อไปนี้ยังใช้ไม่ได้:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either

6
คำชี้แจงของคุณเกี่ยวกับ Python 3 ไม่ถูกต้อง พารามิเตอร์ style ใช้กับสตริงรูปแบบฟอร์แมตเตอร์เท่านั้นไม่ใช่ข้อความบันทึกแต่ละข้อความ หน้าที่คุณเชื่อมโยงอย่างชัดเจนระบุว่า: "จะไม่มีการเปลี่ยนแปลงสิ่งนี้ในขณะที่รักษาความเข้ากันได้แบบย้อนหลัง"
mhsmith

1
ขอบคุณที่ทำให้ฉันซื่อสัตย์ ตอนนี้ส่วนแรกมีประโยชน์น้อยกว่า แต่ฉันได้เปลี่ยนวลีใหม่ในแง่ของFormatterซึ่งตอนนี้ถูกต้องแล้ว (ฉันคิดว่า) StyleAdapter งานยังคง
Felipe

@falstro - ขอบคุณที่ชี้ให้เห็น เวอร์ชันที่อัปเดตควรใช้งานได้แล้ว เนื่องจากBraceStringเป็นคลาสย่อยของสตริงจึงปลอดภัยที่จะกลับตัวเองจาก__str__
เฟลิเป้

1
ตอบเฉพาะที่กล่าวถึง style = "{", +1
Tom S.

24

ทางออกที่ง่ายกว่าคือการใช้โมดูลที่ยอดเยี่ยมlogbook

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

หรือสมบูรณ์ยิ่งขึ้น:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1

สิ่งนี้ดูดี แต่มีวิธีที่จะมีมิลลิวินาทีแทนที่จะเป็นวินาทีหรือไม่?
Jeff

@Jeff แน่ใจว่าสมุดบันทึกช่วยให้คุณกำหนดตัวจัดการแบบกำหนดเองพร้อมและใช้รูปแบบสตริงที่กำหนดเองได้
Thomas Orozco

5
@ เจฟฟ์สองสามปีต่อมาความแม่นยำของเวลาเริ่มต้นคือมิลลิวินาที
ม.ค. Vlcinsky

24

นี่เป็นวิธีแก้ปัญหาของฉันเมื่อฉันพบว่าการบันทึกใช้การจัดรูปแบบสไตล์ printf เท่านั้น ช่วยให้การบันทึกการโทรยังคงเหมือนเดิม - ไม่มีไวยากรณ์พิเศษเช่นlog.info(__("val is {}", "x")). การเปลี่ยนแปลงที่จำเป็นสำหรับรหัสคือการรวมตัวตัดไม้ในไฟล์StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

การใช้งานคือ:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

มันน่าสังเกตว่าการดำเนินการนี้จะมีปัญหาหากคำสำคัญที่ใช้ในการรั้งทดแทน ได้แก่level, msg, args, exc_info, หรือextra stack_infoเหล่านี้เป็นชื่ออาร์กิวเมนต์ใช้โดยวิธีการของlog Loggerหากคุณต้องการชื่อเหล่านี้processให้แก้ไขเพื่อยกเว้นชื่อเหล่านี้หรือเพียงแค่ลบออกlog_kwargsจากการ_logโทร ในหมายเหตุเพิ่มเติมการใช้งานนี้ยังเพิกเฉยต่อคำหลักที่สะกดผิดสำหรับ Logger (เช่นectra)


4
วิธีนี้แนะนำโดย python doc, docs.python.org/3/howto/…
eshizhan

12

ดังที่คำตอบอื่น ๆ กล่าวถึงการจัดรูปแบบสไตล์วงเล็บปีกกาที่แนะนำใน Python 3.2จะใช้กับสตริงรูปแบบเท่านั้นไม่ใช่ข้อความบันทึกจริง

ในการเปิดใช้งานการจัดรูปแบบแบบรั้งในข้อความบันทึกจริงเราสามารถปะติดปะต่อรหัสคนตัดไม้ได้เล็กน้อย

ต่อไปนี้จะแก้ไขloggingโมดูลเพื่อสร้างget_loggerฟังก์ชันที่จะส่งคืนตัวบันทึกที่ใช้การจัดรูปแบบรูปแบบใหม่สำหรับทุกระเบียนบันทึกที่จัดการ

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

การใช้งาน:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

หมายเหตุ:

  • เข้ากันได้อย่างสมบูรณ์กับวิธีการบันทึกปกติ (เพียงแค่แทนที่logging.getLoggerด้วยget_logger)
  • จะมีผลเฉพาะกับคนตัดไม้ที่สร้างโดยไฟล์ get_loggerฟังก์ชันเท่านั้น (ไม่ทำลายแพ็คเกจของบุคคลที่สาม)
  • หากมีการเข้าถึงคนตัดไม้อีกครั้งจากการlogging.getLogger()โทรปกติการจัดรูปแบบรูปแบบใหม่จะยังคงมีผล
  • kwargs ยังไม่ได้รับการสนับสนุน (ทำให้มันเป็นไปไม่ได้ขัดแย้งกับ built-in exc_info, stack_info, stacklevelและextra)
  • การตีประสิทธิภาพควรน้อยที่สุด (เขียนตัวชี้ฟังก์ชันใหม่สำหรับข้อความบันทึกแต่ละข้อความ)
  • การจัดรูปแบบของข้อความล่าช้าจนกว่าจะมีการส่งออก (หรือไม่ดำเนินการเลยหากข้อความบันทึกถูกกรอง)
  • Args จะถูกเก็บไว้ในlogging.LogRecordอ็อบเจ็กต์ตามปกติ (มีประโยชน์ในบางกรณีกับตัวจัดการบันทึกแบบกำหนดเอง)
  • จากการloggingดูซอร์สโค้ดของโมดูลดูเหมือนว่ามันควรจะใช้งานได้ตลอดไปจนถึง Python 2.6 เมื่อstr.formatเปิดตัว (แต่ฉันได้ทดสอบใน 3.5 ขึ้นไปเท่านั้น)

2
คำตอบเดียวที่พิจารณาว่าควรคำนวณสตริงการดีบักก็ต่อเมื่อต้องการพิมพ์ข้อความดีบัก ขอบคุณ!
Fafaman

2

ลองlogging.setLogRecordFactoryใน Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)

มันใช้งานได้ แต่ปัญหาคือคุณทำลายโมดูลของบุคคลที่สามที่กำลังใช้%การจัดรูปแบบเนื่องจากโรงงานระเบียนเป็นโมดูลการบันทึกแบบโกลบอล
jtaylor

1

ฉันสร้าง Formatter แบบกำหนดเองที่เรียกว่าColorFormatterที่จัดการปัญหาเช่นนี้:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

สิ่งนี้ทำให้เข้ากันได้กับไลบรารีต่างๆ ข้อเสียเปรียบคืออาจไม่สามารถแสดงได้เนื่องจากอาจพยายามฟอร์แมตสตริงสองครั้ง


0

วิธีการแก้ปัญหาที่คล้ายกัน pR0Ps' ห่อgetMessageในLogRecordโดยการตัดmakeRecord(แทนhandleในคำตอบของพวกเขา) ในกรณีของการLoggerที่ควรจะเป็นรูปแบบใหม่ที่เปิดใช้งาน:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

ฉันทดสอบสิ่งนี้ด้วย Python 3.5.3


สิ่งนี้กำหนดว่าภาระของการแก้ไขสตริงจะไปที่ใด คุณโหลดล่วงหน้าในเวลาสร้างเรกคอร์ดหรือไม่เพื่อให้แน่ใจว่าสตริงแบบคงที่คือสิ่งที่หลบหนีส่วนหลังหรือคุณจะทำการจัดรูปแบบเฉพาะเมื่อข้อความแสดงในท้ายที่สุด กรณีธรรมดา: ข้อความอยู่ต่ำกว่าระดับที่ยอมรับได้สำหรับการแสดงผล นอกจากนี้: นี่ไม่ใช่วิธีที่ดีในการ "แก้ไข" สิ่งต่างๆ สร้างคลาสย่อย Logger และใช้มัน
amcgregor

-1

นี่คือสิ่งง่ายๆที่ใช้งานได้จริง:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

จากนั้น:

mydebuglog("hello {} {val}", "Python", val="World")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.