Python Logging (ชื่อฟังก์ชันชื่อไฟล์หมายเลขบรรทัด) โดยใช้ไฟล์เดียว


109

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

นี่คือสิ่งที่ฉันรู้:

  1. สำหรับการรับชื่อฟังก์ชันฉันสามารถใช้ได้function_name.__name__แต่ฉันไม่ต้องการใช้ function_name (เพื่อให้ฉันสามารถคัดลอกและวางข้อมูลทั่วไปLog.info("Message")ในเนื้อความของฟังก์ชันทั้งหมดได้อย่างรวดเร็ว) ฉันรู้ว่าสิ่งนี้สามารถทำได้ใน C โดยใช้__func__มาโคร แต่ฉันไม่แน่ใจเกี่ยวกับ python

  2. สำหรับการรับชื่อไฟล์และหมายเลขบรรทัดผมได้เห็นว่า (และผมเชื่อว่า) ใบสมัครของฉันคือการใช้ Python locals()ฟังก์ชั่น แต่ในไวยากรณ์ว่าผมไม่ได้ตระหนักถึงความสมบูรณ์เช่น: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())และฉันพยายามมันใช้เหมือนซึ่งเป็นผู้ผลิตบางอย่างเช่นLOG.info("My message %s" % locals()) {'self': <__main__.Class_name object at 0x22f8cd0>}โปรดป้อนข้อมูลใด ๆ เกี่ยวกับเรื่องนี้

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

ฉันขอขอบคุณอย่างยิ่งสำหรับความช่วยเหลือใด ๆ

ขอบคุณ!


คุณสามารถวางลงในดีบักเกอร์ python ได้โดยใช้import pdb; pdb.set_trace()จากนั้นทำตามขั้นตอนผ่านโค้ดแบบโต้ตอบ ซึ่งอาจช่วยคุณติดตามโฟลว์โปรแกรม
Matthew Schinckel

ความคิดที่ดี! ขอบคุณ Matt การขอรับบันทึกตามที่กล่าวไว้ในคำถามจะเป็นประโยชน์เพื่อที่ฉันจะได้ไม่ต้องแก้ไขข้อบกพร่องทุกครั้ง นอกจากนี้คุณรู้จัก IDE สำหรับ python ซึ่งดีพอ ๆ กับ Eclipse สำหรับ Java (ctrl + click นำคุณไปสู่การกำหนดฟังก์ชัน) ที่ฉันสามารถใช้เพื่อทำให้การดีบักง่ายขึ้น
user1126425

คำตอบ:


28

คุณมีคำถามที่เกี่ยวข้องเล็กน้อยที่นี่

ฉันจะเริ่มด้วยวิธีที่ง่ายที่สุด: (3) การใช้loggingคุณสามารถรวมการเรียกทั้งหมดไปยังล็อกไฟล์เดียวหรือเอาต์พุตเป้าหมายอื่น ๆ โดยจะเรียงตามลำดับที่เกิดขึ้นในกระบวนการ

ถัดไป: (2). locals()ให้คำสั่งของขอบเขตปัจจุบัน ดังนั้นในวิธีการที่ไม่มีอาร์กิวเมนต์อื่นคุณมีselfขอบเขตซึ่งมีการอ้างอิงถึงอินสแตนซ์ปัจจุบัน เคล็ดลับที่ใช้ซึ่งทำให้คุณสะดุดคือการจัดรูปแบบสตริงโดยใช้ dict เป็น RHS ของตัว%ดำเนินการ "%(foo)s" % barจะถูกแทนที่ด้วยสิ่งที่คุ้มค่าของการbar["foo"]มี

สุดท้ายคุณสามารถใช้กลอุบายการวิปัสสนาบางอย่างเช่นเดียวกับที่ใช้โดยpdbสามารถบันทึกข้อมูลเพิ่มเติม:

def autolog(message):
    "Automatically log the current function details."
    import inspect, logging
    # Get the previous frame in the stack, otherwise it would
    # be this function!!!
    func = inspect.currentframe().f_back.f_code
    # Dump the message + the name of this function to the log.
    logging.debug("%s: %s in %s:%i" % (
        message, 
        func.co_name, 
        func.co_filename, 
        func.co_firstlineno
    ))

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

ดังที่ฉันได้กล่าวไว้ในความคิดเห็นของฉันก่อนหน้านี้คุณยังสามารถเข้าสู่pdbพรอมต์การดีบักแบบโต้ตอบได้ตลอดเวลาโดยการแทรกบรรทัดimport pdb; pdb.set_trace()และเรียกใช้โปรแกรมของคุณใหม่ สิ่งนี้ช่วยให้คุณสามารถดำเนินการตามโค้ดตรวจสอบข้อมูลตามที่คุณเลือก


ขอบคุณ Matt! ฉันจะลองฟังก์ชั่น autolog นี้ ฉันมีความสับสนเล็กน้อยเกี่ยวกับการใช้ dict เป็นตัวดำเนินการ RHS ของ%: '%(foo)s : %(bar)s'จะพิมพ์bar["foo"]ค่าของด้วยหรือไม่ หรือแตกต่างจากตัวอย่างของคุณบ้าง?
user1126425

โดยทั่วไปทุกอย่างของแบบฟอร์ม%(<foo>)sจะถูกแทนที่ด้วยค่าของวัตถุที่อ้างถึงใน Dict <foo>โดย มีตัวอย่าง / รายละเอียดเพิ่มเติมที่docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
คำตอบของ @synthesizerpatel มีประโยชน์กว่ามาก
ม.ค.

509

คำตอบที่ถูกต้องคือการใช้funcNameตัวแปรที่ให้มาแล้ว

import logging
logger = logging.getLogger('root')
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)

จากนั้นทุกที่ที่คุณต้องการเพียงเพิ่ม:

logger.debug('your message') 

ตัวอย่างผลลัพธ์จากสคริปต์ที่ฉันกำลังทำงานอยู่:

[invRegex.py:150 -          handleRange() ] ['[A-Z]']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03050>, '{', '1', '}']]
[invRegex.py:197 -          handleMacro() ] ['\\d']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03950>, '{', '1', '}']]
[invRegex.py:210 -       handleSequence() ] [[<__main__.GroupEmitter object at 0x10b9fedd0>, <__main__.GroupEmitter object at 0x10ba03ad0>]]

61
นี่น่าจะเป็นคำตอบ!
user3885927

1
เยี่ยมมาก .. สิ่งหนึ่งที่ต้องเพิ่มเราสามารถตั้งชื่อไฟล์บันทึกให้เหมือนกับ codefile แบบไดนามิกได้หรือไม่? เช่น: ฉันลอง logging.basicConfig (filename = "% (filename)", format = FORMAT) เพื่อใช้ชื่อไฟล์แบบไดนามิก แต่ใช้ค่าคงที่ ข้อเสนอแนะใด ๆ
Outlier

2
@Outlier ไม่วิธีที่แนะนำเพื่อให้บรรลุคือผ่านgetLogger(__name__)
farthVader

2
ฉันมีคำถามหนึ่งข้อ: ใน Java บางแห่งฉันอ่านว่าการพิมพ์หมายเลขบรรทัดนั้นไม่ได้รับการสนับสนุนเนื่องจากต้องใช้เวลามากขึ้นในการพิจารณาว่าจะเรียกคนตัดไม้บรรทัดใด ใน python นี่ไม่จริงเหรอ?
McSonk

2
ไม่เกี่ยวข้อง แต่logging.getLogger('root')ไม่ใช่สิ่งที่คุณคาดหวังไม่ใช่rootคนตัดไม้ แต่เป็นคนตัดไม้ธรรมดาที่มีชื่อว่า 'ราก'
0xc0de

5

funcname, linenameและlinenoให้ข้อมูลเกี่ยวกับฟังก์ชั่นที่ผ่านมาที่ไม่ได้เข้าสู่ระบบ

หากคุณมีกระดาษห่อของคนตัดไม้ (เช่นคนตัดไม้คนเดียว) คำตอบของ @ Syntizerpatel อาจไม่เหมาะกับคุณ

หากต้องการค้นหาผู้โทรคนอื่น ๆ ในกลุ่มการโทรคุณสามารถทำได้:

import logging
import inspect

class Singleton(type):
    _instances = {}

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

class MyLogger(metaclass=Singleton):
    logger = None

    def __init__(self):
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s - %(threadName)s - %(message)s",
            handlers=[
                logging.StreamHandler()
            ])

        self.logger = logging.getLogger(__name__ + '.logger')

    @staticmethod
    def __get_call_info():
        stack = inspect.stack()

        # stack[1] gives previous function ('info' in our case)
        # stack[2] gives before previous function and so on

        fn = stack[2][1]
        ln = stack[2][2]
        func = stack[2][3]

        return fn, func, ln

    def info(self, message, *args):
        message = "{} - {} at line {}: {}".format(*self.__get_call_info(), message)
        self.logger.info(message, *args)

1
คำตอบของคุณคือสิ่งที่ฉันต้องการในการแก้ปัญหา ขอบคุณ.
Error - Syntactical Remorse

ตั้งแต่หลาม 3.8 ในloggingระดับระดับการสนับสนุนสแต็ข้ามออกจากกล่อง: วิธีการชอบlog(), debug()ฯลฯ ตอนนี้ยอมรับstacklevelข้อโต้แย้ง ดูเอกสาร
ดุเดือด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.