Python Logging (nama fungsi, nama file, nomor baris) menggunakan satu file


109

Saya mencoba mempelajari cara kerja aplikasi. Dan untuk ini saya memasukkan perintah debug sebagai baris pertama dari setiap tubuh fungsi dengan tujuan untuk mencatat nama fungsi serta nomor baris (di dalam kode) tempat saya mengirim pesan ke output log. Terakhir, karena aplikasi ini terdiri dari banyak file, saya ingin membuat satu file log sehingga saya dapat lebih memahami aliran kontrol aplikasi.

Inilah yang saya ketahui:

  1. untuk mendapatkan nama fungsi, saya dapat menggunakan function_name.__name__tetapi saya tidak ingin menggunakan nama_fungsi (sehingga saya dapat dengan cepat menyalin dan menempelkan generik Log.info("Message")di badan semua fungsi). Saya tahu ini bisa dilakukan di C menggunakan __func__makro tetapi saya tidak yakin tentang python.

  2. untuk mendapatkan nama file dan nomor baris, saya telah melihat itu (dan saya percaya bahwa) aplikasi saya menggunakan locals()fungsi Python tetapi dalam sintaks yang saya tidak sepenuhnya sadari misalnya: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())dan saya mencobanya menggunakan like LOG.info("My message %s" % locals())yang menghasilkan sesuatu seperti {'self': <__main__.Class_name object at 0x22f8cd0>}. Ada masukan tentang ini?

  3. Saya tahu bagaimana menggunakan logging dan menambahkan handler ke dalamnya untuk login ke sebuah file tapi saya tidak yakin apakah satu file dapat digunakan untuk merekam semua pesan log dalam urutan yang benar dari pemanggilan fungsi dalam proyek.

Saya akan sangat menghargai bantuan apa pun.

Terima kasih!


Anda dapat masuk ke debugger python dengan menggunakan import pdb; pdb.set_trace(), lalu menelusuri kode secara interaktif. Itu dapat membantu Anda melacak aliran program.
Matthew Schinckel

Ide yang hebat! Terima kasih Matt. Akan tetap membantu untuk mendapatkan log seperti yang disebutkan dalam pertanyaan sehingga saya tidak perlu men-debug setiap saat. Juga, apakah Anda tahu tentang IDE untuk python yang sebaik Eclipse untuk Java (ctrl + klik membawa Anda ke definisi fungsi) yang dapat saya gunakan untuk membuat debugging lebih mudah?
pengguna1126425

Jawaban:


28

Anda memiliki beberapa pertanyaan yang terkait secara marginal di sini.

Saya akan mulai dengan yang termudah: (3). Menggunakan loggingAnda dapat menggabungkan semua panggilan ke satu file log atau target keluaran lainnya: panggilan tersebut akan berada dalam urutan yang terjadi dalam proses.

Selanjutnya: (2). locals()memberikan perintah dari lingkup saat ini. Jadi, dalam metode yang tidak memiliki argumen lain, Anda memiliki selfcakupan, yang berisi referensi ke instance saat ini. Trik yang digunakan yang membingungkan Anda adalah pemformatan string menggunakan dict sebagai RHS %operator. "%(foo)s" % barakan diganti dengan apa pun nilainya bar["foo"].

Terakhir, Anda dapat menggunakan beberapa trik introspeksi, serupa dengan yang digunakan oleh pdbyang dapat mencatat lebih banyak info:

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

Ini akan mencatat pesan yang diteruskan, ditambah nama fungsi (asli), nama file di mana definisi tersebut muncul, dan baris di file itu. Lihat inspect - Inspect live object untuk lebih jelasnya.

Seperti yang saya sebutkan dalam komentar saya sebelumnya, Anda juga dapat masuk ke pdbprompt debugging interaktif kapan saja dengan memasukkan baris import pdb; pdb.set_trace(), dan menjalankan kembali program Anda. Ini memungkinkan Anda untuk menelusuri kode, memeriksa data yang Anda pilih.


Terima kasih Matt! Saya akan mencoba fungsi autolog ini. Saya memiliki sedikit kebingungan mengenai penggunaan dict sebagai RHS dari% operator: Apakah '%(foo)s : %(bar)s'juga akan mencetak bar["foo"]nilai? Atau apakah itu agak berbeda dari contoh Anda?
pengguna1126425

Pada dasarnya, semua bentuk %(<foo>)sdiganti dengan nilai objek yang direferensikan di dict by <foo>. Ada lebih banyak contoh / detail di docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
Jawaban @synthesizerpatel jauh lebih membantu.
Jan

504

Jawaban yang benar untuk ini adalah dengan menggunakan funcNamevariabel yang telah disediakan

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

Lalu di mana pun Anda mau, cukup tambahkan:

logger.debug('your message') 

Contoh keluaran dari skrip yang saya kerjakan sekarang:

[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
Ini seharusnya jawabannya!
pengguna3885927

1
Hebat .. Satu hal yang perlu ditambahkan, dapatkah kita menamai logfile sama dengan codefile secara dinamis? misal: Saya mencoba logging.basicConfig (filename = "% (filename)", format = FORMAT) untuk mengambil nama file secara dinamis, tapi butuh nilai statis. ada saran?
Outlier

2
@Outlier Tidak, cara yang disarankan untuk mencapai itu adalah melaluigetLogger(__name__)
farthVader

2
Saya punya satu pertanyaan: Di Jawa, saya pernah membaca bahwa mencetak nomor baris tidak disarankan karena perlu waktu ekstra untuk mencari tahu dari baris mana logger dipanggil. Dalam python ini tidak benar?
McSonk

2
Tidak relevan, tetapi mungkin logging.getLogger('root')bukan yang Anda harapkan, ini bukan rootlogger, tetapi logger biasa dengan nama 'root'.
0xc0de

5

funcname, linenamedan linenomemberikan informasi tentang fungsi terakhir yang melakukan logging.

Jika Anda memiliki pembungkus logger (mis. Logger tunggal), maka jawaban @ synthesizerpatel mungkin tidak berfungsi untuk Anda.

Untuk mengetahui penelepon lain dalam tumpukan panggilan, Anda dapat melakukan:

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
Jawaban Anda persis seperti yang saya butuhkan untuk menyelesaikan masalah saya. Terima kasih.
Kesalahan - Penyesalan Sintaksis

Sejak Python 3.8, loggingtingkat dukungan kelas tumpukan skipping out-of-the-box: metode seperti log(), debug(), dll sekarang menerima stacklevelargumen. Lihat dokumennya .
mencapai
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.