Nhân bản đầu ra nhật ký khi sử dụng mô-đun ghi nhật ký Python


105

Tôi đang sử dụng trình ghi python. Sau đây là mã của tôi:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Vấn đề tôi gặp phải là tôi nhận được nhiều mục nhập trong tệp nhật ký cho mỗi logger.infocuộc gọi. Làm sao tôi có thể giải quyết việc này?


Làm việc cho tôi. Python 3.2 và Windows XP.
Zuljin

2
Bạn có chắc mình không tạo nhiều phiên bản trình ghi nhật ký không?
Gandi

Đúng. trong tệp khác, tôi đang lấy ví dụ mới như chúng ta đã làm trong các dự án Java. Vui lòng chỉ định cho tôi liệu điều đó có đang tạo ra vấn đề hay không.
user865438,

Câu trả lời:


94

Đã logging.getLogger()là một singleton. ( Tài liệu )

Vấn đề là mỗi khi bạn gọi myLogger(), nó sẽ thêm một trình xử lý khác vào phiên bản, điều này gây ra các bản ghi trùng lặp.

Có lẽ một cái gì đó như thế này?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
Tôi nghĩ bạn nên có logger.update (dict ((tên, người ghi))) để thay thế.
acrophobia 14/02/12

tại sao loggers.update(dict(name=logger))? không loggers[name] = loggerđơn giản hơn?
Ryan J McCall

@RyanJMcCall Vào thời điểm đó, đó là quy ước mã hóa mà tôi sử dụng. Nhưng khi xem lại mã hiện tại, tôi thấy rằng nó đã bị hỏng. loggers.update(dict(name=logger))sẽ tạo từ điển với một khóa duy nhất được gọi namevà liên tục cập nhật cùng khóa đó. Tôi ngạc nhiên là không ai không đề cập đến điều này sớm hơn vì mã này khá bị hỏng :) Sẽ thực hiện các sửa đổi cần thiết.
Werner Smit

Tôi thấy @acrophobia đã không còn tồn tại trong thời đại này nữa. Cảm ơn.
Werner Smit

không phải là loggerstừ điển toàn cầu thừa với logging.getLogger? vì bạn thực sự chỉ muốn tránh thêm các trình xử lý bổ sung, có vẻ như bạn muốn các câu trả lời bên dưới kiểm tra trình xử lý trực tiếp
mway 24/09/19

60

Kể từ Python 3.2, bạn chỉ có thể kiểm tra xem trình xử lý đã có sẵn hay chưa và nếu có, hãy xóa chúng trước khi thêm trình xử lý mới. Điều này khá thuận tiện khi gỡ lỗi và mã bao gồm khởi tạo trình ghi nhật ký của bạn

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

Tốt câu trả lời, Thx :))
Gavriel Cohen

2
Lưu ý rằng hasHandlers () sẽ trả về true trong pytest nơi một trình xử lý đã được thêm vào trình ghi gốc, ngay cả khi trình xử lý cục bộ / tùy chỉnh của bạn chưa được thêm. Len (logger.handlers) (theo câu trả lời của Guillaume) sẽ trả về 0 trong trường hợp này, vì vậy có thể là một lựa chọn tốt hơn.
Grant

Đây là giải pháp thực sự mà tôi đang tìm kiếm.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

làm cho tôi lừa

sử dụng python 2.7


1
Này hoạt động ngay cả khi các mô-đun được nạp lại (mà không phải là trường hợp của các câu trả lời khác)
yco

3
Thanks for the tip, BTW để kiểm tra xem một danh sách rỗng hay không bạn không cần phải sử dụng "len" nhà điều hành, bạn có thể trực tiếp sử dụng nếu my_list: ..
rkachach

26

Tôi đã sử dụng loggernhư một Singleton và đã kiểm tra if not len(logger.handlers), nhưng vẫn nhận được các bản sao : Đó là đầu ra được định dạng, tiếp theo là chưa được định dạng.

Giải pháp trong trường hợp của tôi: logger.propagate = False

Tín dụng cho câu trả lời nàytài liệu .


1
Tôi đã phát hiện ra rằng việc ghi nhật ký kép là từ RootLogger và StreamHandler của tôi, nhưng không thể giải quyết vấn đề (trong khi vẫn giữ bộ định dạng của tôi trên StreamHandler) cho đến khi thực hiện việc này.
Xander YzWich

10

Bạn đang gọi Logger.myLogger()nhiều hơn một lần. Lưu trữ các dụ logger nó sẽ trả về nơi nào đó và tái sử dụng .

Cũng nên lưu ý rằng nếu bạn đăng nhập trước khi bất kỳ trình xử lý nào được thêm vào, thì mặc định StreamHandler(sys.stderr)sẽ được tạo.


Trên thực tế, tôi đang cố gắng truy cập phiên bản ghi nhật ký như chúng ta sử dụng trong java, nhưng tôi không biết liệu nó có cần tạo một phiên bản chỉ một lần cho toàn bộ dự án hay không.
user865438

1
@ user865483: Chỉ một lần. Tất cả các trình ghi nhật ký thư viện tiêu chuẩn là các ổ đĩa đơn.
Matt Joiner

5

Đây là phần bổ sung cho câu trả lời của @ rm957377 nhưng kèm theo lời giải thích tại sao điều này lại xảy ra . Khi bạn chạy một hàm lambda trong AWS, chúng sẽ gọi hàm của bạn từ bên trong một phiên bản gói vẫn tồn tại cho nhiều lần gọi. Có nghĩa là, nếu bạn gọi addHandler()trong mã hàm của mình, nó sẽ tiếp tục thêm các trình xử lý trùng lặp vào singleton ghi nhật ký mỗi khi hàm chạy. Singleton ghi nhật ký vẫn tồn tại qua nhiều lần gọi hàm lambda của bạn.

Để giải quyết vấn đề này, bạn có thể xóa các trình xử lý của mình trước khi đặt chúng qua:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

Bằng cách nào đó trong trường hợp của tôi, các trình xử lý trình ghi nhật ký được thêm vào sự kiện khi .info()gọi mà tôi không hiểu.
Evgeny

4

Trình ghi nhật ký của bạn sẽ hoạt động như singleton. Bạn không nên tạo nó nhiều lần. Đây là ví dụ về cách nó có thể trông như thế nào:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

sau đó một lần nữa nếu tôi sẽ lấy các trường hợp khác nhau trong tệp khác. Giả sử trong file 1 s = Logger () m = s.myLogger () và trong tập tin 2 s = Logger () Nó sẽ làm việc hay không m2 = s.myLogger ()
user865438

Tôi vẫn nhận được bản sao của cùng một bản ghi nhiều lần. Tôi nghi ngờ ở đây liệu bên trong Nhật ký luồng có in nhiều hơn một hay không. Vui lòng giúp tôi trong việc này.
user865438

1
@ user865438, chúng tôi không cần phải lo lắng về việc thực hiện một singleton (Nó đã là như vậy). Để đăng nhập vào các mô-đun phụ, hãy nhấp vào liên kết Sổ ghi nhật ký chính thức . Về cơ bản, bạn cần tuân theo hệ thống phân cấp đặt tên trong khi đặt tên cho các log và nó sẽ lo phần còn lại.
narayan

4

Việc triển khai trình ghi nhật ký đã là một singleton.

Nhiều lệnh gọi đến logging.getLogger ('someLogger') trả về một tham chiếu đến cùng một đối tượng trình ghi. Điều này không chỉ đúng trong cùng một mô-đun mà còn trên các mô-đun miễn là nó nằm trong cùng một quy trình thông dịch Python. Nó đúng đối với các tham chiếu đến cùng một đối tượng; ngoài ra, mã ứng dụng có thể xác định và cấu hình trình ghi nhật ký mẹ trong một mô-đun và tạo (nhưng không cấu hình) trình ghi nhật ký con trong một mô-đun riêng biệt và tất cả các lệnh gọi của trình ghi nhật ký đến con sẽ chuyển đến trình ghi mẹ. Đây là một mô-đun chính

Nguồn- Sử dụng đăng nhập nhiều mô-đun

Vì vậy, cách bạn nên sử dụng điều này là -

Giả sử chúng ta đã tạo và định cấu hình trình ghi nhật ký có tên là 'main_logger' trong mô-đun chính (chỉ đơn giản là định cấu hình trình ghi nhật ký, không trả về bất kỳ thứ gì).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Bây giờ trong một mô-đun con, nếu chúng ta tạo một trình ghi con theo hệ thống phân cấp đặt tên 'main_logger.sub_module_logger' , chúng ta không cần phải định cấu hình nó trong mô-đun con. Chỉ cần tạo bộ ghi theo hệ thống phân cấp đặt tên là đủ.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

Và nó cũng sẽ không thêm trình xử lý trùng lặp.

Xem câu hỏi này để có câu trả lời dài dòng hơn một chút.


1
xác định lại các trình xử lý sau khi getLogger dường như hoạt động đối với tôi: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

Đầu ra của trình ghi nhật ký tăng gấp đôi (hoặc gấp ba hoặc ..- dựa trên số lần tải lại) cũng có thể xảy ra khi bạn tải lại mô-đun của mình thông qua importlib.reload(vì lý do tương tự như đã giải thích trong câu trả lời được chấp nhận). Tôi đang thêm câu trả lời này chỉ để tham khảo trong tương lai vì tôi đã mất một lúc để tìm ra lý do tại sao đầu ra của tôi bị trùng lặp (gấp ba).


1

Một cách giải quyết đơn giản là

logger.handlers[:] = [handler]

Bằng cách này, bạn tránh thêm trình xử lý mới vào danh sách cơ bản "trình xử lý".


1

Điểm mấu chốt đối với hầu hết các trường hợp khi điều này xảy ra, người ta chỉ cần gọi logger.getLogger () một lần duy nhất cho mỗi mô-đun. Nếu bạn có nhiều lớp như tôi đã làm, tôi có thể gọi nó như vậy:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Cả hai sau đó sẽ có tên gói đầy đủ và phương thức của riêng họ khi đăng nhập.


0

Bạn có thể nhận danh sách tất cả các trình xử lý cho trình ghi nhật ký cụ thể, vì vậy bạn có thể làm điều gì đó như thế này

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

Trong ví dụ trên, chúng tôi kiểm tra xem trình xử lý cho tệp được chỉ định đã được kết nối với trình ghi nhật ký chưa, nhưng việc có quyền truy cập vào danh sách tất cả trình xử lý sẽ cho bạn khả năng quyết định xem bạn có nên thêm trình xử lý khác hay không.


0

Đã có vấn đề này ngày hôm nay. Vì các hàm của tôi là @staticmethod nên các đề xuất trên đã được giải quyết bằng random ().

Trông giống như:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

Vui lòng thêm giải thích để làm cho câu trả lời này có giá trị hơn để sử dụng lâu dài.
Aminah Nuraini
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.