Menggunakan masuk dalam banyak modul

257

Saya memiliki proyek python kecil yang memiliki struktur berikut -

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

Saya berencana untuk menggunakan modul logging standar untuk mencetak pesan ke stdout dan file log. Untuk menggunakan modul logging, diperlukan beberapa inisialisasi -

import logging.config

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

logger.info('testing')

Saat ini, saya melakukan inisialisasi ini di setiap modul sebelum saya mulai mencatat pesan. Apakah mungkin untuk melakukan inisialisasi ini hanya sekali di satu tempat sehingga pengaturan yang sama digunakan kembali dengan masuk ke seluruh proyek?

Quest Monger
sumber
3
Menanggapi komentar Anda pada jawaban saya: Anda tidak perlu memanggil fileConfigsetiap modul yang melakukan logging, kecuali Anda memiliki if __name__ == '__main__'logika di dalamnya. Jawaban prost bukanlah praktik yang baik jika paket tersebut adalah pustaka, meskipun mungkin cocok untuk Anda - seseorang tidak boleh mengonfigurasi login dalam paket pustaka, selain menambahkan a NullHandler.
Vinay Sajip
1
prost menyiratkan bahwa kita perlu memanggil impor dan logger stmts di setiap modul, dan hanya memanggil stmt fileconfig di modul utama. Bukankah itu mirip dengan apa yang Anda katakan?
Quest Monger
6
prost mengatakan bahwa Anda harus memasukkan kode konfigurasi logging package/__init__.py. Itu biasanya bukan tempat Anda meletakkan if __name__ == '__main__'kode. Juga, contoh prost kelihatannya akan memanggil kode konfigurasi tanpa syarat pada impor, yang tidak cocok untuk saya. Secara umum, kode konfigurasi logging harus dilakukan di satu tempat dan tidak boleh terjadi sebagai efek samping dari impor kecuali ketika Anda mengimpor __main__.
Vinay Sajip
Anda benar, saya benar-benar melewatkan baris '# package / __ init__.py' dalam contoh kodenya. terima kasih atas hal itu dan kesabaran Anda.
Quest Monger
1
Jadi apa yang terjadi jika Anda memiliki banyak if __name__ == '__main__'? (tidak disebutkan secara eksplisit dalam pertanyaan jika ini masalahnya)
kon psych

Jawaban:

293

Praktik terbaik adalah, di setiap modul, untuk memiliki logger yang didefinisikan seperti ini:

import logging
logger = logging.getLogger(__name__)

di dekat bagian atas modul, dan kemudian di kode lain dalam modul lakukan misalnya

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

Jika Anda perlu membagi aktivitas logging di dalam sebuah modul, gunakan mis

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

dan masuk loggerAdan loggerBjika perlu.

Di program atau program utama Anda, lakukan misalnya:

def main():
    "your program code"

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

atau

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

if __name__ == '__main__':
    main()

Lihat di sini untuk login dari beberapa modul, dan di sini untuk konfigurasi logging untuk kode yang akan digunakan sebagai modul perpustakaan dengan kode lain.

Pembaruan: Saat menelepon fileConfig(), Anda mungkin ingin menentukan disable_existing_loggers=Falseapakah Anda menggunakan Python 2.6 atau yang lebih baru (lihat dokumen untuk informasi lebih lanjut). Nilai default adalah Trueuntuk kompatibilitas mundur, yang menyebabkan semua penebang yang ada dinonaktifkan oleh fileConfig()kecuali mereka atau leluhur mereka secara eksplisit disebutkan dalam konfigurasi. Dengan nilai yang disetel False, penebang yang ada dibiarkan sendiri. Jika menggunakan Python 2.7 / Python 3.2 atau yang lebih baru, Anda mungkin ingin mempertimbangkan dictConfig()API yang lebih baik daripada fileConfig()karena memberikan kontrol lebih besar atas konfigurasi.

Vinay Sajip
sumber
21
jika Anda melihat contoh saya, saya sudah melakukan apa yang Anda sarankan di atas. pertanyaan saya adalah bagaimana cara memusatkan inisialisasi logging ini sehingga saya tidak perlu mengulangi 3 pernyataan itu. juga, dalam contoh Anda, Anda melewatkan stmt 'logging.config.fileConfig (' logging.conf ')'. STMT ini sebenarnya adalah akar penyebab keprihatinan saya. Anda lihat, jika saya telah memulai logger di setiap modul, saya harus mengetikkan stmt ini di setiap modul. itu berarti melacak jalur file conf di setiap modul, yang tidak terlihat seperti praktik terbaik bagi saya (bayangkan kekacauan ketika mengubah lokasi modul / paket).
Quest Monger
4
Jika Anda memanggil fileConfig setelah membuat logger, apakah dalam modul yang sama atau yang lain (misalnya ketika Anda membuat logger di bagian atas file) tidak berfungsi. Konfigurasi logging hanya berlaku untuk penebang yang dibuat setelah. Jadi pendekatan ini tidak berfungsi atau bukan pilihan yang layak untuk beberapa modul. @Quest Monger: Anda selalu dapat membuat file lain yang menyimpan lokasi file konfigurasi ..;)
Vincent Ketelaars
2
@Oxidator: Tidak harus - melihat disable_existing_loggersbendera yang secara Truedefault tetapi dapat diatur ke False.
Vinay Sajip
1
@ Vinay Sajip, terima kasih. Apakah Anda memiliki rekomendasi untuk penebang yang bekerja dalam modul tetapi juga di luar kelas? Karena impor dilakukan sebelum fungsi utama dipanggil, log-log itu sudah dicatat. Saya kira pengaturan logger Anda sebelum semua impor di modul utama adalah satu-satunya cara? Logger ini kemudian dapat ditimpa di main, jika Anda suka.
Vincent Ketelaars
1
Jika saya ingin semua logger khusus modul saya memiliki level logging berbeda dari PERINGATAN default, apakah saya harus membuat pengaturan itu pada setiap modul? Katakan, saya ingin semua modul saya masuk di INFO.
Raj
128

Sebenarnya setiap logger adalah anak dari paket logger orangtua (yaitu package.subpackage.modulemewarisi konfigurasi dari package.subpackage), jadi yang perlu Anda lakukan hanyalah mengkonfigurasi logger root. Ini dapat dicapai dengan logging.config.fileConfig(konfigurasi Anda sendiri untuk logger) atau logging.basicConfig(set logger root) Setup logging di modul entri Anda ( __main__.pyatau apa pun yang ingin Anda jalankan, misalnya main_script.py. __init__.pyBerfungsi juga)

menggunakan basicConfig:

# package/__main__.py
import logging
import sys

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

menggunakan fileConfig:

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

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

lalu buat setiap logger menggunakan:

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

log.info("Hello logging!")

Untuk informasi lebih lanjut, lihat Tutorial Logging Lanjutan .

Stan Prokop
sumber
15
ini, sejauh ini, solusi paling sederhana untuk masalah ini, belum lagi itu mengekspos & memanfaatkan hubungan orangtua-anak antara modul, sesuatu yang saya sebagai noob tidak sadari. danke.
Quest Monger
kamu benar. dan seperti yang ditunjukkan vinay dalam postingannya, solusi Anda benar asalkan tidak ada dalam modul .py init . solusi Anda berhasil ketika saya menerapkannya pada modul utama (titik masuk).
Quest Monger
2
sebenarnya jawaban yang jauh lebih relevan karena pertanyaannya berkaitan dengan modul yang terpisah.
Jan Sila
1
Mungkin pertanyaan bodoh: jika tidak ada logger di __main__.py(mis. Jika saya ingin menggunakan modul dalam skrip yang tidak memiliki logger) logging.getLogger(__name__)masih akan melakukan semacam logging di modul atau akankah itu menimbulkan pengecualian?
Bill
1
Akhirnya. Saya memiliki logger yang berfungsi, tetapi gagal di Windows untuk menjalankan Paralel dengan joblib. Saya kira ini adalah tweak manual ke sistem - ada hal lain yang salah dengan Paralel. Tapi, itu pasti berhasil! Terima kasih
B Furtado
17

Saya selalu melakukannya seperti di bawah ini.

Gunakan file python tunggal untuk mengkonfigurasi log saya sebagai pola tunggal yang bernama ' 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')

Di modul lain, cukup impor konfigurasi.

from log_conf import Logger

Logger.logr.info("Hello World")

Ini adalah pola tunggal untuk dicatat, sederhana dan efisien.

Yarkee
sumber
1
terima kasih telah merinci pola tunggal. Saya berencana mengimplementasikan ini, tetapi kemudian solusi @prost jauh lebih sederhana dan sesuai dengan kebutuhan saya dengan sempurna. Namun saya melihat solusi Anda bermanfaat adalah proyek yang lebih besar yang memiliki banyak titik masuk (selain utama). danke.
Quest Monger
46
Ini tidak berguna. Root logger sudah menjadi singleton. Cukup gunakan logging.info alih-alih Logger.logr.info.
Pod
9

Beberapa jawaban ini menunjukkan bahwa di bagian atas modul Anda melakukannya

import logging
logger = logging.getLogger(__name__)

Ini adalah pemahaman saya bahwa ini dianggap praktik yang sangat buruk . Alasannya adalah bahwa konfigurasi file akan menonaktifkan semua logger yang ada secara default. Misalnya

#my_module
import logging

logger = logging.getLogger(__name__)

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

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

Dan di modul utama Anda:

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

Sekarang log yang ditentukan dalam logging.ini akan kosong, karena logger yang ada dinonaktifkan oleh panggilan fileconfig.

Meskipun dimungkinkan untuk mengatasi hal ini (disable_existing_Loggers = False), secara realistis banyak klien perpustakaan Anda tidak akan tahu tentang perilaku ini, dan tidak akan menerima log Anda. Permudah klien Anda dengan selalu menelepon logging.getLogger secara lokal. Tip Hat: Saya belajar tentang perilaku ini dari Situs Victor Lin .

Jadi praktik yang baik adalah sebaliknya untuk selalu memanggil logging.getLogger secara lokal. Misalnya

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

Juga, jika Anda menggunakan fileconfig di utama Anda, setel disable_existing_loggers = Salah, kalau-kalau perancang perpustakaan Anda menggunakan instance logger level modul.

phil_20686
sumber
Tidak bisakah kau lari logging.config.fileConfig('logging.ini')sebelumnya import my_module? Seperti yang disarankan dalam jawaban ini .
lucid_dreamer
Tidak yakin - tetapi tentu saja akan dianggap praktik buruk untuk mencampur impor dan kode yang dapat dieksekusi dengan cara itu. Anda juga tidak ingin klien Anda harus memeriksa apakah mereka perlu mengkonfigurasi logging sebelum mereka mengimpor, terutama ketika ada alternatif sepele! Bayangkan jika perpustakaan yang banyak digunakan seperti permintaan telah melakukan itu ....!
phil_20686
"Tidak yakin - tetapi pasti juga akan dianggap praktik buruk untuk mencampur impor dan kode yang dapat dieksekusi dengan cara itu." - mengapa
lucid_dreamer
Saya tidak terlalu jelas mengapa itu buruk. Dan saya tidak sepenuhnya mengerti teladan Anda. Bisakah Anda memposting konfigurasi Anda untuk contoh ini dan menunjukkan beberapa penggunaan?
lucid_dreamer
1
Anda tampaknya bertentangan dengan dokumen resmi : 'Konvensi yang baik untuk digunakan ketika memberi nama logger adalah dengan menggunakan modul-level logger, di setiap modul yang menggunakan logging, dinamai sebagai berikut: logger = logging.getLogger(__name__)'
iron9
9

Cara sederhana menggunakan salah satu contoh pendataan pustaka di banyak modul bagi saya adalah solusi berikut:

base_logger.py

import logging

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

File lainnya

from base_logger import logger

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

Melemparkan solusi lain.

Dalam init modul saya .py Saya punya sesuatu seperti:

# 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

Kemudian di setiap modul saya perlu logger, saya lakukan:

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

Ketika log terjawab, Anda dapat membedakan sumbernya dengan modul asalnya.

Tommy
sumber
Apa yang dimaksud dengan "init utama modul saya"? Dan "Lalu di setiap kelas saya membutuhkan seorang penebang, saya lakukan:"? Bisakah Anda memberikan contoh yang disebut_module.py, dan contoh penggunaannya sebagai impor dari modul caller_module.py? Lihat jawaban ini untuk inspirasi format yang saya tanyakan. Tidak mencoba menggurui. Saya mencoba memahami jawaban Anda dan saya tahu saya akan melakukannya jika Anda menulisnya dengan cara itu.
lucid_dreamer
1
@ lucid_dreamer saya klarifikasi.
Tommy
4

Anda juga bisa membuat sesuatu seperti ini!

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

Sekarang Anda dapat menggunakan beberapa penebang dalam modul yang sama dan di seluruh proyek jika yang di atas didefinisikan dalam modul yang terpisah dan diimpor dalam modul-modul lain diperlukan pencatatan.

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

@ Solusi Yarkee tampak lebih baik. Saya ingin menambahkan sesuatu ke sana -

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)

Jadi LoggerManager dapat menjadi pluggable ke seluruh aplikasi. Semoga ini masuk akal dan berharga.

Deeshank
sumber
11
Modul logging sudah berurusan dengan lajang. logging.getLogger ("Hello") akan mendapatkan logger yang sama di semua modul Anda.
Pod
2

Ada beberapa jawaban. Saya berakhir dengan solusi serupa namun berbeda yang masuk akal bagi saya, mungkin itu akan masuk akal bagi Anda juga. Tujuan utama saya adalah untuk dapat mengirimkan log ke penangan berdasarkan levelnya (debug level log ke konsol, peringatan dan di atas ke file):

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)

membuat file util yang bagus bernama logger.py:

import logging

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

flask.app adalah nilai hardcode dalam flask. logger aplikasi selalu dimulai dengan flask.app sebagai nama modulnya.

sekarang, di setiap modul, saya dapat menggunakannya dalam mode berikut:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Ini akan membuat log baru untuk "app.flask.MODULE_NAME" dengan upaya minimal.

Ben Yitzhaki
sumber
2

Praktik terbaik adalah membuat modul secara terpisah yang hanya memiliki satu metode yang tugasnya kita berikan penangan logger ke metode pemanggilan. Simpan file ini sebagai 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

Sekarang panggil metode getlogger () setiap kali logger handler diperlukan.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
Mousam Singh
sumber
1
Ini bagus jika Anda tidak memiliki parameter tambahan. Tetapi jika, katakanlah, Anda memiliki --debugopsi di aplikasi dan ingin mengatur tingkat logging di semua penebang di aplikasi Anda berdasarkan parameter ini ...
The Godfather
@TheGodfather Ya ini sulit dicapai dengan metodologi ini. Apa yang bisa kita lakukan dalam situasi ini adalah membuat kelas yang akan mengambil formatter sebagai parameter pada saat pembuatan objek dan akan memiliki fungsi yang sama untuk mengembalikan penangan logger. Apa pandangan Anda tentang ini?
Mousam Singh
Ya, saya melakukan hal yang sama, dibuat get_logger(level=logging.INFO)untuk mengembalikan beberapa jenis singleton, jadi ketika dipanggil pertama kali dari aplikasi utama, ia menginisialisasi logger dan handler dengan level yang tepat dan kemudian mengembalikan loggerobjek yang sama ke semua metode lain.
The Godfather
0

Baru menggunakan python, jadi saya tidak tahu apakah ini disarankan, tetapi berfungsi dengan baik untuk tidak menulis ulang boilerplate.

Proyek Anda harus memiliki init .py sehingga dapat dimuat sebagai modul

# 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)saran datang dari sini

Kemudian untuk menggunakan logger Anda di file lain:

from [your module name here] import logger

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

Peringatan:

  1. Anda harus menjalankan file sebagai modul, jika import [your module]tidak , tidak akan berfungsi:
    • python -m [your module name].[your filename without .py]
  2. Nama logger untuk titik masuk program Anda akan __main__, tetapi solusi apa pun yang menggunakan __name__akan memiliki masalah itu.
npjohns
sumber