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


257

Tôi có một dự án python nhỏ có cấu trúc như sau -

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Tôi dự định sử dụng mô-đun ghi nhật ký mặc định để in thông báo ra thiết bị xuất chuẩn và tệp nhật ký. Để sử dụng mô-đun đăng nhập, một số khởi tạo được yêu cầu -

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

Hiện tại, tôi thực hiện việc khởi tạo này trong mọi mô-đun trước khi tôi bắt đầu đăng nhập thông điệp. Có thể thực hiện việc khởi tạo này chỉ một lần ở một nơi để các cài đặt tương tự được sử dụng lại bằng cách đăng nhập toàn bộ dự án không?


3
Đáp lại nhận xét của bạn về câu trả lời của tôi: bạn không phải gọi fileConfigtrong mọi mô-đun đăng nhập, trừ khi bạn có if __name__ == '__main__'logic trong tất cả các mô-đun . Câu trả lời của gái mại dâm không phải là thực hành tốt nếu gói là một thư viện, mặc dù nó có thể phù hợp với bạn - người ta không nên định cấu hình đăng nhập trong các gói thư viện, ngoài việc thêm một NullHandler.
Vinay Sajip

1
gái mại dâm ngụ ý rằng chúng ta cần gọi stmts nhập và logger trong mỗi mô-đun, và chỉ gọi stmt fileconfig trong mô-đun chính. không giống với những gì bạn đang nói sao?
Quest Monger

6
gái mại dâm đang nói rằng bạn nên đặt mã cấu hình đăng nhập package/__init__.py. Đó thường không phải là nơi bạn đặt if __name__ == '__main__'mã. Ngoài ra, ví dụ của gái mại dâm có vẻ như nó sẽ gọi mã cấu hình vô điều kiện khi nhập, điều này không phù hợp với tôi. Nói chung, mã cấu hình ghi nhật ký nên được thực hiện ở một nơi và không nên xảy ra như là một tác dụng phụ của nhập ngoại trừ khi bạn đang nhập __main__.
Vinay Sajip

bạn nói đúng, tôi hoàn toàn bỏ lỡ dòng '# gói / __ init__.py' trong mẫu mã của anh ấy. cảm ơn vì điểm đó và sự kiên nhẫn của bạn
Quest Monger

1
Vậy điều gì xảy ra nếu bạn có nhiều if __name__ == '__main__'? (nó không được đề cập rõ ràng trong câu hỏi nếu đây là trường hợp)
kon psych

Câu trả lời:


293

Thực hành tốt nhất là, trong mỗi mô-đun, để có một bộ ghi chép được định nghĩa như thế này:

import logging
logger = logging.getLogger(__name__)

gần đỉnh của mô-đun, và sau đó trong mã khác trong mô-đun làm ví dụ

logger.debug('My message with %s', 'variable data')

Nếu bạn cần chia nhỏ hoạt động ghi nhật ký trong một mô-đun, hãy sử dụng, vd

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

và đăng nhập loggerAloggerBkhi thích hợp.

Trong chương trình hoặc chương trình chính của bạn, hãy làm, ví dụ:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

hoặc là

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Xem ở đây để đăng nhập từ nhiều mô-đun và ở đây để ghi nhật ký cấu hình cho mã sẽ được sử dụng làm mô-đun thư viện bởi mã khác.

Cập nhật: Khi gọi fileConfig(), bạn có thể muốn chỉ địnhdisable_existing_loggers=False nếu bạn đang sử dụng Python 2.6 trở lên (xem tài liệu để biết thêm thông tin). Giá trị mặc định là Trueđể tương thích ngược, điều này làm cho tất cả các logger hiện tại bị vô hiệu hóa fileConfig()trừ khi chúng hoặc tổ tiên của chúng được đặt tên rõ ràng trong cấu hình. Với giá trị được đặt thành False, các logger hiện có được để lại một mình. Nếu sử dụng Python 2.7 / Python 3.2 trở lên, bạn có thể muốn xem xét dictConfig()API tốt hơn fileConfig()vì nó mang lại nhiều quyền kiểm soát hơn cho cấu hình.


21
nếu bạn nhìn vào ví dụ của tôi, tôi đã làm những gì bạn đề xuất ở trên. Câu hỏi của tôi là làm thế nào để tôi tập trung vào việc khởi tạo đăng nhập này để tôi không phải lặp lại 3 câu lệnh đó. Ngoài ra, trong ví dụ của bạn, bạn đã bỏ lỡ stmt 'log.config.fileConfig (' log.conf ')'. stmt này thực sự là nguyên nhân gốc rễ của mối quan tâm của tôi. bạn thấy đấy, nếu tôi đã khởi tạo logger trong mọi mô-đun, tôi sẽ phải gõ stmt này trong mỗi mô-đun. điều đó có nghĩa là theo dõi đường dẫn của tệp conf trong mỗi mô-đun, có vẻ không phải là cách thực hành tốt nhất đối với tôi (hãy tưởng tượng sự tàn phá khi thay đổi vị trí mô-đun / gói).
Quest Monger

4
Nếu bạn gọi fileConfig sau khi tạo bộ ghi, cho dù trong cùng hoặc trong một mô-đun khác (ví dụ: khi bạn tạo bộ ghi ở đầu tệp) không hoạt động. Cấu hình ghi nhật ký chỉ áp dụng cho loggers được tạo sau. Vì vậy, phương pháp này không hoạt động hoặc không phải là một lựa chọn khả thi cho nhiều mô-đun. @Quest Monger: Bạn luôn có thể tạo một tệp khác chứa vị trí của tệp cấu hình ..;)
Vincent Ketelaars

2
@Oxidator: Không nhất thiết - xem disable_existing_loggerscờ Truetheo mặc định nhưng có thể được đặt thành False.
Vinay Sajip

1
@Vinay Sajip, cảm ơn bạn. Bạn có đề xuất cho logger hoạt động trong các mô-đun nhưng bên ngoài các lớp không? Vì việc nhập được thực hiện trước khi chức năng chính được gọi, các nhật ký đó sẽ được ghi lại. Tôi đoán thiết lập logger của bạn trước khi tất cả các nhập trong mô-đun chính là cách duy nhất? Logger này sau đó có thể được ghi đè lên chính, nếu bạn muốn.
Vincent Ketelaars

1
Nếu tôi muốn tất cả các trình ghi nhật ký cụ thể mô-đun của mình có mức ghi nhật ký khác với CẢNH BÁO mặc định, tôi có phải thực hiện cài đặt đó trên mỗi mô-đun không? Nói rằng, tôi muốn có tất cả các mô-đun của tôi đăng nhập tại INFO.
Raj

128

Trên thực tế mỗi logger là con của logger gói của cha mẹ (tức là package.subpackage.modulecấu hình thừa hưởng từ package.subpackage), vì vậy tất cả bạn cần làm chỉ là cấu hình các logger gốc. Điều này có thể đạt được bằng cách logging.config.fileConfig(config của riêng bạn cho logger) hoặc logging.basicConfig(bộ các logger root) Cài đặt đăng nhập trong mô-đun nhập của bạn ( __main__.pyhoặc bất cứ điều gì bạn muốn chạy, chẳng hạn main_script.py. __init__.pyHoạt động tốt)

sử dụng basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

sử dụng fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

và sau đó tạo mọi logger bằng cách sử dụng:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Để biết thêm thông tin, xem Hướng dẫn đăng nhập nâng cao .


15
cho đến nay, đây là giải pháp đơn giản nhất cho vấn đề, chưa kể nó phơi bày & thúc đẩy mối quan hệ cha-con giữa các mô-đun, một điều mà tôi với tư cách là một người không biết. danke.
Quest Monger

bạn đúng rồi. và như vinay đã chỉ ra trong bài đăng của anh ấy, giải pháp của bạn là đúng miễn là nó không nằm trong mô-đun init .py. giải pháp của bạn đã làm việc khi tôi áp dụng nó cho mô-đun chính (điểm vào).
Quest Monger

2
thực sự câu trả lời phù hợp hơn nhiều vì câu hỏi liên quan đến các mô-đun riêng biệt.
Jan Sila

1
Câu hỏi ngớ ngẩn có lẽ: nếu không có logger trong __main__.py(ví dụ: Nếu tôi muốn sử dụng mô-đun trong tập lệnh không có logger) logging.getLogger(__name__)vẫn sẽ thực hiện một số loại đăng nhập trong mô-đun hoặc nó sẽ đưa ra một ngoại lệ?
Bill

1
Cuối cùng. Tôi đã có một bộ ghi chép hoạt động, nhưng nó đã thất bại trong Windows khi chạy song song với joblib. Tôi đoán đây là một chỉnh sửa thủ công cho hệ thống - có gì đó không ổn với Parallel. Nhưng, nó chắc chắn hoạt động! Cảm ơn
B Furtado

17

Tôi luôn luôn làm như dưới đây.

Sử dụng một tệp python duy nhất để định cấu hình nhật ký của tôi dưới dạng mẫu đơn có tên ' log_conf.py'

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

Trong một mô-đun khác, chỉ cần nhập cấu hình.

from log_conf import Logger

Logger.logr.info("Hello World")

Đây là một mẫu singleton để đăng nhập, đơn giản và hiệu quả.


1
cảm ơn vì đã mô tả chi tiết mẫu singleton. tôi đã lên kế hoạch thực hiện điều này, nhưng sau đó giải pháp @prost đơn giản hơn nhiều và hoàn toàn phù hợp với nhu cầu của tôi. Tuy nhiên tôi thấy giải pháp của bạn hữu ích là các dự án lớn hơn có nhiều điểm vào (trừ chính). danke.
Quest Monger

46
Điều này là vô ích. Bộ ghi gốc đã là một singleton. Chỉ cần sử dụng log.info thay vì Logger.logr.info.
Pod

9

Một vài trong số những câu trả lời này gợi ý rằng ở đầu mô-đun bạn làm

import logging
logger = logging.getLogger(__name__)

Theo hiểu biết của tôi rằng đây được coi là thực hành rất xấu . Lý do là cấu hình tập tin sẽ vô hiệu hóa tất cả các logger hiện có theo mặc định. Ví dụ

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

Và trong mô-đun chính của bạn:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Bây giờ nhật ký được chỉ định trong log.ini sẽ trống, vì trình ghi nhật ký hiện tại đã bị vô hiệu hóa bởi lệnh gọi fileconfig.

Mặc dù chắc chắn có thể khắc phục điều này (vô hiệu hóa_ex hiện_Loggers = Sai), nhưng thực tế nhiều khách hàng của thư viện của bạn sẽ không biết về hành vi này và sẽ không nhận được nhật ký của bạn. Làm cho nó dễ dàng cho khách hàng của bạn bằng cách luôn gọi log.getLogger cục bộ. Hat Tip: Tôi đã tìm hiểu về hành vi này từ Trang web của Victor Lin .

Vì vậy, thực hành tốt là thay vì luôn gọi log.getLogger cục bộ. Ví dụ

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Ngoài ra, nếu bạn sử dụng fileconfig trong chính của mình, hãy đặt vô hiệu hóa_ex hiện_loggers = Sai, chỉ trong trường hợp các nhà thiết kế thư viện của bạn sử dụng các phiên bản logger cấp mô-đun.


Bạn có thể không chạy logging.config.fileConfig('logging.ini')trước import my_module? Như đề xuất trong câu trả lời này .
lucid_dreamer

Không chắc chắn - nhưng chắc chắn cũng sẽ bị coi là thực tiễn xấu khi trộn lẫn nhập khẩu và mã thực thi theo cách đó. Bạn cũng không muốn khách hàng của mình phải kiểm tra xem họ có cần định cấu hình ghi nhật ký trước khi nhập hay không, đặc biệt là khi có một sự thay thế tầm thường! Hãy tưởng tượng nếu một thư viện được sử dụng rộng rãi như yêu cầu đã làm điều đó ....!
phil_20686

"Không chắc chắn - nhưng chắc chắn cũng sẽ bị coi là thực tiễn xấu khi trộn lẫn nhập khẩu và mã thực thi theo cách đó." - tại sao?
lucid_dreamer

Tôi không quá rõ tại sao điều đó là xấu. Và tôi không hoàn toàn hiểu ví dụ của bạn. Bạn có thể gửi cấu hình của bạn cho ví dụ này và hiển thị một số cách sử dụng?
lucid_dreamer

1
Bạn dường như mâu thuẫn với các tài liệu chính thức : 'Một quy ước tốt để sử dụng khi đặt tên logger là sử dụng bộ ghi cấp độ mô-đun, trong mỗi mô-đun sử dụng ghi nhật ký, được đặt tên như sau: logger = logging.getLogger(__name__)'
iron9

9

Một cách đơn giản để sử dụng một thể hiện của thư viện ghi nhật ký trong nhiều mô-đun đối với tôi là giải pháp sau:

cơ sở_logger

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Những tập tin khác

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")

7

Ném vào một giải pháp khác.

Trong init .py của mô-đun của tôi, tôi có một cái gì đó như:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Sau đó, trong mỗi mô-đun tôi cần một logger, tôi làm:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Khi các bản ghi bị bỏ lỡ, bạn có thể phân biệt nguồn của chúng theo mô-đun chúng đến từ.


"Init chính của mô-đun của tôi" nghĩa là gì? Và "Sau đó, trong mỗi lớp tôi cần một logger, tôi làm:"? Bạn có thể cung cấp một mẫu được gọi là_module.py và một ví dụ về việc sử dụng nó dưới dạng nhập từ mô-đun caller_module.py không? Xem câu trả lời này cho cảm hứng về định dạng tôi đang hỏi về. Không cố gắng để được bảo trợ. Tôi đang cố gắng để hiểu câu trả lời của bạn và tôi biết tôi sẽ làm thế nếu bạn viết nó theo cách đó.
lucid_dreamer

1
@lucid_dreamer Tôi đã làm rõ.
Tommy

4

Bạn cũng có thể nghĩ ra thứ gì đó như thế này!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Bây giờ bạn có thể sử dụng nhiều logger trong cùng một mô-đun và trên toàn bộ dự án nếu phần trên được xác định trong một mô-đun riêng biệt và được nhập trong các mô-đun khác là ghi nhật ký là bắt buộc.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")

4

@ Giải pháp của Yarkee có vẻ tốt hơn. Tôi muốn thêm một chút vào nó -

class Singleton(type):
    _instances = {}

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


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

Vì vậy, LoggerManager có thể là một bản cắm cho toàn bộ ứng dụng. Hy vọng nó có ý nghĩa và giá trị.


11
Các mô-đun đăng nhập đã giao dịch với singletons. log.getLogger ("Xin chào") sẽ nhận được cùng một logger trên tất cả các mô-đun của bạn.
Pod

2

Có một vài câu trả lời. tôi đã kết thúc với một giải pháp tương tự nhưng khác biệt có ý nghĩa với tôi, có lẽ nó cũng có ý nghĩa với bạn. Mục tiêu chính của tôi là có thể chuyển nhật ký cho người xử lý theo cấp độ của họ (nhật ký mức gỡ lỗi cho bảng điều khiển, cảnh báo và ở trên cho tệp):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

đã tạo một tập tin tiện ích có tên logger.py:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

the jar.app là một giá trị mã hóa cứng trong bình. bộ ghi ứng dụng luôn bắt đầu với jar.app là tên của mô-đun.

Bây giờ, trong mỗi mô-đun, tôi có thể sử dụng nó trong chế độ sau:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Điều này sẽ tạo ra một nhật ký mới cho "app.flask.MODULE_NAME" với nỗ lực tối thiểu.


2

Cách thực hành tốt nhất là tạo một mô-đun riêng biệt chỉ có một phương thức mà chúng ta có nhiệm vụ là cung cấp một trình xử lý logger cho phương thức gọi. Lưu tệp này dưới dạng m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Bây giờ hãy gọi phương thức getlogger () bất cứ khi nào cần trình xử lý logger.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

1
Điều này là tốt nếu bạn không có bất kỳ tham số bổ sung. Nhưng nếu, giả sử, bạn có --debugtùy chọn trong ứng dụng và muốn đặt mức ghi nhật ký trong tất cả các logger trong ứng dụng của bạn dựa trên tham số này ...
Bố già

@TheGod lương Có điều này khó đạt được bằng phương pháp này. Những gì chúng ta có thể làm trong tình huống này là tạo một lớp để lấy định dạng làm tham số tại thời điểm tạo đối tượng và sẽ có chức năng tương tự để trả về trình xử lý logger. Quan điểm của bạn về điều này là gì?
Mousam Singh

Có, tôi đã làm điều tương tự, được thực hiện get_logger(level=logging.INFO)để trả về một số loại singleton, vì vậy khi nó được gọi lần đầu tiên từ ứng dụng chính, nó khởi tạo bộ ghi và trình xử lý với mức phù hợp và sau đó trả lại cùng một loggerđối tượng cho tất cả các phương thức khác.
Bố già

0

Mới sử dụng python nên tôi không biết điều này có nên không, nhưng nó hoạt động rất tốt vì không viết lại bản tóm tắt.

Dự án của bạn phải có init .py để có thể tải nó dưới dạng mô-đun

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1) đề nghị đến từ đây

Sau đó, để sử dụng logger của bạn trong bất kỳ tập tin khác:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Hãy cẩn thận:

  1. Bạn phải chạy các tệp của mình dưới dạng các mô-đun, nếu không import [your module]sẽ không hoạt động:
    • python -m [your module name].[your filename without .py]
  2. Tên của logger cho điểm vào của chương trình của bạn sẽ là __main__, nhưng bất kỳ giải pháp nào sử dụng __name__sẽ có vấn đề đó.
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.