Ghi nhật ký Python (tên hàm, tên tệp, số dòng) bằng một tệp duy nhất


109

Tôi đang cố gắng tìm hiểu cách hoạt động của một ứng dụng. Và đối với điều này, tôi đang chèn các lệnh gỡ lỗi dưới dạng dòng đầu tiên của nội dung mỗi hàm với mục tiêu ghi lại tên của hàm cũng như số dòng (trong mã) nơi tôi gửi thông báo đến đầu ra nhật ký. Cuối cùng, vì ứng dụng này bao gồm nhiều tệp, tôi muốn tạo một tệp nhật ký duy nhất để tôi có thể hiểu rõ hơn về luồng điều khiển của ứng dụng.

Đây là những gì tôi biết:

  1. để lấy tên hàm, tôi có thể sử dụng function_name.__name__nhưng tôi không muốn sử dụng tên hàm (để tôi có thể nhanh chóng sao chép và dán một tên chung Log.info("Message")vào nội dung của tất cả các hàm). Tôi biết điều này có thể được thực hiện trong C bằng cách sử dụng __func__macro nhưng tôi không chắc chắn về python.

  2. để lấy tên tệp và số dòng, tôi đã thấy rằng (và tôi tin rằng) ứng dụng của tôi đang sử dụng locals()hàm Python nhưng theo một cú pháp mà tôi không hoàn toàn biết, ví dụ: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())và tôi đã thử sử dụng hàm like LOG.info("My message %s" % locals())mà tạo ra một cái gì đó như thế nào {'self': <__main__.Class_name object at 0x22f8cd0>}. Bất kỳ đầu vào này xin vui lòng?

  3. Tôi biết cách sử dụng ghi nhật ký và thêm trình xử lý vào nó để đăng nhập vào một tệp nhưng tôi không chắc liệu một tệp duy nhất có thể được sử dụng để ghi lại tất cả các thông báo nhật ký theo đúng thứ tự của các lệnh gọi hàm trong dự án hay không.

Tôi sẽ đánh giá rất cao bất kỳ sự giúp đỡ nào.

Cảm ơn!


Bạn có thể vào trình gỡ lỗi python bằng cách sử dụng import pdb; pdb.set_trace(), sau đó chuyển qua mã một cách tương tác. Điều đó có thể giúp bạn theo dõi luồng chương trình.
Matthew Schinckel

Ý tưởng tuyệt vời! Cảm ơn Matt. Vẫn sẽ hữu ích nếu lấy nhật ký như đã đề cập trong câu hỏi để tôi không phải gỡ lỗi mỗi lần. Ngoài ra, bạn có biết IDE cho python cũng tốt như Eclipse dành cho Java (ctrl + nhấp chuột đưa bạn đến định nghĩa hàm) mà tôi có thể sử dụng để gỡ lỗi dễ dàng hơn không?
user1126425

Câu trả lời:


28

Bạn có một vài câu hỏi nhỏ liên quan ở đây.

Tôi sẽ bắt đầu với cái dễ nhất: (3). Sử dụng, loggingbạn có thể tổng hợp tất cả các lệnh gọi vào một tệp nhật ký duy nhất hoặc mục tiêu đầu ra khác: chúng sẽ theo thứ tự xảy ra trong quá trình.

Tiếp theo: (2). locals()cung cấp một chính tả của phạm vi hiện tại. Do đó, trong một phương thức không có đối số nào khác, bạn có selftrong phạm vi, có chứa một tham chiếu đến cá thể hiện tại. Thủ thuật đang được sử dụng khiến bạn bối rối là định dạng chuỗi bằng cách sử dụng một dict làm RHS của %toán tử. "%(foo)s" % barsẽ được thay thế bằng bất kỳ giá trị nào của bar["foo"]là.

Cuối cùng, bạn có thể sử dụng một số thủ thuật xem xét nội tâm, tương tự như những thủ thuật được sử dụng bởi pdbcó thể ghi thêm thông tin:

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

Thao tác này sẽ ghi lại thông báo được truyền vào, cùng với tên hàm (ban đầu), tên tệp mà định nghĩa xuất hiện và dòng trong tệp đó. Hãy xem kiểm tra - Kiểm tra các đối tượng sống để biết thêm chi tiết.

Như tôi đã đề cập trong nhận xét của mình trước đó, bạn cũng có thể đưa vào pdblời nhắc gỡ lỗi tương tác bất kỳ lúc nào bằng cách chèn dòng import pdb; pdb.set_trace()vào và chạy lại chương trình của bạn. Điều này cho phép bạn thực hiện từng bước mã, kiểm tra dữ liệu khi bạn chọn.


Cảm ơn Matt! Tôi sẽ thử chức năng tự động này. Tôi có một chút nhầm lẫn liên quan đến việc sử dụng dict làm RHS của toán tử%: '%(foo)s : %(bar)s'Cũng sẽ in bar["foo"]giá trị của? Hay nó có phần khác với ví dụ của bạn?
user1126425

Về cơ bản, mọi thứ của biểu mẫu %(<foo>)sđược thay thế bằng giá trị của đối tượng được tham chiếu trong dict bởi <foo>. Có rất nhiều ví dụ / chi tiết tại docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
Câu trả lời của @synthesizerpatel hữu ích hơn nhiều.
Ngày

503

Câu trả lời chính xác cho điều này là sử dụng funcNamebiến đã được cung cấp sẵn

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

Sau đó, bất cứ nơi nào bạn muốn, chỉ cần thêm:

logger.debug('your message') 

Ví dụ đầu ra từ một tập lệnh mà tôi đang làm việc ngay bây giờ:

[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
Đây lẽ ra phải là câu trả lời!
user3885927

1
Tuyệt vời .. Thêm một điều, chúng ta có thể đặt tên logfile giống với codefile động không? ví dụ: Tôi đã thử logging.basicConfig (filename = "% (filename)", format = FORMAT) để lấy tên tệp động, nhưng nó nhận giá trị tĩnh. Bất kì lời đề nghị nào?
Outlier

2
@Outlier Không, cách được đề xuất để đạt được điều đó là thông quagetLogger(__name__)
farthVader

2
Tôi có một câu hỏi: Trong Java ở đâu đó tôi đọc thấy rằng việc in số dòng không được khuyến khích vì phải mất thêm thời gian để tìm ra dòng mà trình ghi nhật ký đang được gọi. Trong python, điều này không đúng?
McSonk

2
Không liên quan, nhưng logging.getLogger('root')có lẽ không phải là những gì bạn đang mong đợi, nó không phải là trình rootghi nhật ký, mà là một trình ghi nhật ký thông thường có tên 'root'.
0xc0de

5

funcname, linenamelinenocung cấp thông tin về chức năng cuối cùng đã ghi nhật ký.

Nếu bạn có trình bao bọc của trình ghi nhật ký (ví dụ: trình ghi nhật ký singleton), thì câu trả lời của @ Syntizerpatel có thể không phù hợp với bạn.

Để tìm ra những người gọi khác trong ngăn xếp cuộc gọi, bạn có thể làm:

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
Câu trả lời của bạn chính xác là những gì tôi cần để giải quyết vấn đề của mình. Cảm ơn bạn.
Lỗi - Lời nhắc cú pháp vào

Kể từ khi Python 3.8, các logginghỗ trợ lớp ngăn xếp mức bỏ qua out-of-the-box: phương pháp thích log(), debug()vv bây giờ chấp nhận một stacklevelcuộc tranh cãi. Xem tài liệu .
lẹ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.