nhật ký thông báo xuất hiện hai lần với Python Logging


100

Tôi đang sử dụng ghi nhật ký Python và vì một số lý do, tất cả các thư của tôi đều xuất hiện hai lần.

Tôi có một mô-đun để định cấu hình ghi nhật ký:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Sau đó, tôi gọi phương thức này để định cấu hình ghi nhật ký:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

Và sau đó, trong mô-đun buy_ham, tôi sẽ gọi:

self.logger.info('Successfully able to write to %s' % path)

Và vì lý do nào đó, tất cả các tin nhắn đều xuất hiện hai lần. Tôi đã nhận xét ra một trong những trình xử lý luồng, vẫn như vậy. Hơi kỳ lạ, không hiểu tại sao điều này lại xảy ra ... lol. Giả sử tôi đã bỏ lỡ điều gì đó hiển nhiên.

Chúc mừng, Victor


1
Bạn có chắc chắn configure_logging()không được gọi hai lần (ví dụ: từ hàm tạo)? Có phải chỉ một phiên bản Boy () được tạo không?
Jacek Konieczny

1
Sử dụng self.logger.handlers = [ch]thay thế sẽ giải quyết được vấn đề này, mặc dù tốt nhất bạn chỉ nên đảm bảo rằng bạn không chạy mã này hai lần, chẳng hạn bằng cách sử dụng if not self.loggerlúc bắt đầu.
Ninjakannon

Câu trả lời:


133

Bạn đang gọi configure_logginghai lần (có thể trong __init__phương thức của Boy): getLoggersẽ trả về cùng một đối tượng, nhưng addHandlerkhông kiểm tra xem một trình xử lý tương tự đã được thêm vào trình ghi nhật ký chưa.

Hãy thử theo dõi các cuộc gọi đến phương thức đó và loại bỏ một trong những phương thức này. Hoặc thiết lập một cờ được logging_initializedkhởi tạo Falsetrong __init__phương thức của Boyvà thay đổi configure_loggingđể không làm gì nếu logging_initializedTrue, và để đặt nó thành Truesau khi bạn khởi tạo trình ghi nhật ký.

Nếu chương trình của bạn tạo một số Boytrường hợp, bạn sẽ phải thay đổi cách thực hiện với một configure_logginghàm toàn cục thêm các trình xử lý và Boy.configure_loggingphương thức chỉ khởi tạo self.loggerthuộc tính.

Một cách khác để giải quyết vấn đề này là kiểm tra thuộc tính trình xử lý của trình ghi nhật ký của bạn:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
Vâng, bạn đã đúng - tôi ngớ ngẩn. Tôi đã gọi nó trong init , cũng như ở những nơi khác. Cười lớn. Cảm ơn =).
victorhooi,

Cảm ơn. Giải pháp của bạn đã cứu tôi ngày hôm nay.
ForeverLearner

1
Trong trường hợp của tôi, chúng đã xuất hiện 6 lần. Tôi đã nghi ngờ rằng vì tôi đã tuyên bố cùng một loại logger trong 6 lớp oop
answerSeeker

5
Tôi muốn chia sẻ kinh nghiệm của mình ở đây: đối với một ứng dụng Flask mà tôi đã phát triển, các thông báo nhật ký xuất hiện HƠN HAI LẦN. Tôi muốn nói rằng chúng đang tăng lên trên tệp nhật ký, do thực tế là, khi ứng dụng và các mô-đun được tải, loggerbiến được sử dụng, không phải là biến được khởi tạo từ một trong các lớp của tôi, mà là loggerbiến có trên bộ đệm Python3 và trình xử lý được thêm sau mỗi 60 giây bởi AppScheduler mà tôi đã định cấu hình. Vì vậy, đây if not logger.handlerslà một cách khá thông minh để tránh loại hiện tượng này. Cảm ơn về giải pháp, đồng chí :)!
ivanleoncz,

2
Tôi gặp sự cố này trong ứng dụng Flask của mình. Giải pháp này đã khắc phục sự cố đối với thông báo nhật ký được tạo trong ứng dụng bình chính, nhưng ứng dụng của tôi đóng các chức năng trong mô-đun thư viện và những thông báo từ thư viện đó vẫn được ghi lại nhiều lần. Tôi không biết làm thế nào để sửa lỗi này.
Cas

24

Nếu bạn gặp sự cố này và bạn không thêm trình xử lý hai lần thì hãy xem câu trả lời của abarnert tại đây

Từ các tài liệu :

Lưu ý: Nếu bạn đính kèm một trình xử lý vào một trình ghi nhật ký và một hoặc nhiều tổ tiên của nó, nó có thể phát ra cùng một bản ghi nhiều lần. Nói chung, bạn không cần phải đính kèm một trình xử lý vào nhiều hơn một trình ghi nhật ký - nếu bạn chỉ gắn nó vào trình ghi nhật ký thích hợp cao nhất trong phân cấp trình ghi nhật ký, thì nó sẽ thấy tất cả các sự kiện được ghi lại bởi tất cả các trình ghi nhật ký con, miễn là cài đặt được đặt thành Đúng. Một kịch bản phổ biến là chỉ đính kèm các trình xử lý vào trình ghi gốc và để việc truyền bá thực hiện phần còn lại.

Vì vậy, nếu bạn muốn một trình xử lý tùy chỉnh trên "test" và bạn không muốn các thông báo của nó cũng đi đến trình xử lý gốc, câu trả lời rất đơn giản: tắt cờ truyền của nó:

logger.propagate = Sai


1
Đó là câu trả lời tốt nhất. Nó không phù hợp với mục đích của người đăng (lỗi logic trong mã hóa) nhưng hầu hết các trường hợp, đây là trường hợp.
Artem

Hoan hô. ĐÂY là nguyên nhân thực tế của các bản sao (đối với các trường hợp chung nhất).
Ông Duhart

Đây cũng là vấn đề với mã của tôi. Cảm ơn rất nhiều.
harshit

Câu trả lời hay nhất đến nay. Cảm ơn bạn!
Foivos Ts

8

Trình xử lý được thêm vào mỗi khi bạn gọi từ bên ngoài. Hãy thử Xóa Trình xử lý sau khi bạn hoàn thành công việc của mình:

self.logger.removeHandler(ch)

1
Tôi đã sử dụng logger.handlers.pop() trong python 2.7, hiện các trick
radtek

6

Tôi là một người mới chơi python, nhưng điều này dường như hiệu quả với tôi (Python 2.7)

while logger.handlers:
     logger.handlers.pop()

4

Trong trường hợp của tôi, tôi muốn đặt logger.propagate = Falseđể ngăn in hai lần.

Trong mã dưới đây nếu bạn xóa logger.propagate = Falsethì bạn sẽ thấy in hai lần.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

Đây là vấn đề tôi có. Cảm ơn bạn
q0987

Câu trả lời chính xác; thêm logger.propagate = Falselà giải pháp để ngăn đăng nhập hai lần vào một ứng dụng Flask do Waitress lưu trữ, khi đăng nhập vào app.loggerphiên bản của Flask .
bluebinary

1

Một cuộc gọi đến logging.debug()các cuộc gọi logging.basicConfig()nếu không có trình xử lý gốc nào được cài đặt. Điều đó đã xảy ra với tôi trong một khung thử nghiệm mà tôi không thể kiểm soát thứ tự mà các trường hợp thử nghiệm được kích hoạt. Mã khởi tạo của tôi đang cài đặt mã thứ hai. Mặc định sử dụng ghi nhật ký.BASIC_FORMAT mà tôi không muốn.


Tôi nghĩ đây là những gì đang xảy ra với tôi. Làm cách nào để bạn ngăn chặn việc tạo tự động các trình ghi bảng điều khiển?
Robert

@Robert đó là việc đảm bảo rằng bạn được khởi tạo với trình ghi nhật ký bạn muốn, trước cuộc gọi ghi nhật ký đầu tiên. Các khuôn khổ thử nghiệm có thể che khuất điều này, nhưng cần phải có một cách để làm điều đó. Ngoài ra, nếu bạn đang xử lý đa quy trình, bạn phải thực hiện tương tự với từng quy trình.
JimB

1

Có vẻ như nếu bạn xuất một cái gì đó vào bộ ghi (vô tình) rồi cấu hình nó thì đã quá muộn. Ví dụ, trong mã của tôi, tôi có

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

Tôi sẽ nhận được một cái gì đó giống như (bỏ qua các tùy chọn định dạng)

look out
hello
hello

và mọi thứ đã được viết thành stdout hai lần. Tôi tin rằng điều này là do lệnh gọi đầu tiên logging.warningtự động tạo một trình xử lý mới và sau đó tôi đã thêm một trình xử lý khác một cách rõ ràng. Vấn đề đã biến mất khi tôi xóa logging.warningcuộc gọi đầu tiên tình cờ .


0

Tôi đã gặp phải một tình huống kỳ lạ trong đó nhật ký bảng điều khiển được nhân đôi nhưng nhật ký tệp của tôi thì không. Sau một tấn đào, tôi đã tìm ra.

Xin lưu ý rằng các gói của bên thứ ba có thể đăng ký trình ghi nhật ký. Đây là điều cần đề phòng (và trong một số trường hợp không thể ngăn chặn được). Trong nhiều trường hợp, mã của bên thứ ba kiểm tra xem có bất kỳ trình xử lý trình ghi gốc nào hiện có không ; và nếu không có - họ đăng ký một trình xử lý bảng điều khiển mới.

Giải pháp của tôi cho vấn đề này là đăng ký trình ghi bảng điều khiển của tôi ở cấp cơ sở:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
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.