Günlük kaydını birden çok modülde kullanma


257

Aşağıdaki yapıya sahip küçük bir python projem var -

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

Mesajları stdout'a ve bir günlük dosyasına yazdırmak için varsayılan günlük modülünü kullanmayı planlıyorum. Günlük modülünü kullanmak için bir miktar başlatma gerekir -

import logging.config

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

logger.info('testing')

Şu anda, iletileri günlüğe kaydetmeye başlamadan önce her modülde bu başlatmayı gerçekleştiriyorum. Bu başlatmayı, aynı ayarların proje üzerinde oturum açarak yeniden kullanılması için tek bir yerde yalnızca bir kez yapmak mümkün müdür?


3
Cevabım hakkındaki yorumunuza yanıt olarak: fileConfighepsinde if __name__ == '__main__'mantık yoksa, günlük kaydı yapan her modülü aramak zorunda değilsiniz . Prost'un yanıtı, paket bir kütüphane ise iyi bir uygulama değildir, ancak sizin için işe yarayabilir - bir tanesi eklemek yerine kütüphane paketlerindeki günlük kaydını yapılandırmamalıdır NullHandler.
Vinay Sajip

1
prost, her modülde import ve logger stmts'lerini çağırmamız gerektiğini ve sadece ana modülde fileconfig stmt'i çağırmamız gerektiğini ima etti. Söylediklerine benzemiyor mu?
Görev Monger

6
Prost, günlük yapılandırma kodunu girmeniz gerektiğini söylüyor package/__init__.py. Normalde bu if __name__ == '__main__'kodu koyduğunuz yer değil . Ayrıca, prost örneği, içe aktarma sırasında koşulsuz olarak yapılandırma kodunu çağıracak gibi görünüyor, bu da bana doğru görünmüyor. Genellikle, yapılandırma yapılandırma kodu tek bir yerde yapılmalıdır ve __main__ dosyasını içe aktarmanız dışında içe aktarmanın bir yan etkisi olarak gerçekleşmemelidir.
Vinay Sajip

haklısın, kod örneğinde '# package / __ init__.py' satırını tamamen kaçırdım. Puanınız ve sabrınız için teşekkürler.
Görev Monger

1
Peki, birden fazlasınız varsa ne olur if __name__ == '__main__'? (bu durumda söz konusu olduğu açıkça belirtilmemiştir)
kon psych

Yanıtlar:


294

En iyi uygulama, her modülde, aşağıdaki gibi bir günlükçünün tanımlanmasıdır:

import logging
logger = logging.getLogger(__name__)

modülün üst kısmına yakın bir yerdeyken modülün diğer kodlarında ör.

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

Günlüğe kaydetme etkinliğini bir modül içinde alt bölümlere ayırmanız gerekiyorsa, örn.

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

ve giriş loggerAve loggerBuygun olarak.

Ana programınızda veya programlarınızda örneğin:

def main():
    "your program code"

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

veya

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

if __name__ == '__main__':
    main()

Birden çok modülden oturum açmak için buraya bakın ve buraya bakın diğer kod tarafından bir kütüphane modül olarak kullanılacak kodu için günlük yapılandırması için.

Güncelleme: Arama yaparken fileConfig(), disable_existing_loggers=FalsePython 2.6 veya üstünü kullanıp kullanmadığınızı belirtmek isteyebilirsiniz ( daha fazla bilgi için dokümanlara bakın ). Varsayılan değer, Truegeriye dönük uyumluluk içindir; bu, varolan tüm günlüklerin fileConfig(), ataları veya atamaları yapılandırmada açıkça adlandırılmadıkça devre dışı bırakılmasına neden olur . Değer olarak ayarlandığında False, varolan günlükçüler yalnız bırakılır. Python 2.7 / Python 3.2 veya daha yenisini kullanıyorsanız , yapılandırma üzerinde daha fazla kontrol sağladığından daha dictConfig()iyi olan API'yı düşünebilirsiniz fileConfig().


21
örneğime bakarsanız, yukarıda önerdiğiniz şeyi zaten yapıyorum. sorum bu 3 günlük ifadeleri tekrarlamak zorunda değilim nasıl bu günlük başlatma nasıl merkezi oldu. ayrıca, örneğinizde 'logging.config.fileConfig (' logging.conf ')' stmt dosyasını kaçırdınız. bu stmt aslında endişemin temel nedenidir. Eğer her modülde logger başlattıysanız, her modülde bu stmt yazmak zorunda kalacak, görüyorsunuz. Bu, her modülde conf dosyasının yolunu izlemek anlamına gelir, bu benim için en iyi uygulama gibi görünmüyor (modül / paket konumlarını değiştirirken tahribatı hayal edin).
Görev Monger

4
Kaydediciyi oluşturduktan sonra fileConfig öğesini çağırırsanız, aynı veya başka bir modülde (örneğin, dosyanın üst kısmında logger oluşturduğunuzda) çalışmaz. Günlüğe kaydetme yapılandırması yalnızca sonradan oluşturulan günlükçilere uygulanır. Dolayısıyla bu yaklaşım işe yaramıyor ya da birden fazla modül için uygun bir seçenek değil. @Quest Monger: Her zaman yapılandırma dosyasının yerini tutan başka bir dosya oluşturabilirsiniz ..;)
Vincent Ketelaars

2
@Oxidator: Mutlaka değil - varsayılan olarak ancak ayarlanabilen disable_existing_loggersbayrağa bakın . TrueFalse
Vinay Sajip

1
@Vinay Sajip, teşekkür ederim. Modüllerde çalışan ancak sınıfların dışında çalışan kaydediciler için önerileriniz var mı? Ana işlev çağrılmadan önce içe aktarma yapıldığından, bu günlükler zaten günlüğe kaydedilmiş olacaktır. Sanırım ana modüldeki tüm ithalatın tek yolu günlükçünüzün kurulumudur? Daha sonra isterseniz bu kaydedicinin üzerine yazılabilir.
Vincent Ketelaars

1
Modüle özgü tüm kaydedicilerimin varsayılan uyarı seviyesinden farklı olmasını istiyorsanız, bu ayarı her modülde yapmam gerekir mi? Diyelim ki tüm modüllerimin INFO'da oturum açmasını istiyorum.
Raj

128

Aslında her günlükçü, üst öğenin paket günlükçüsünün bir alt öğesidir (yani package.subpackage.moduleyapılandırmayı kaynağından devralır package.subpackage), bu nedenle yapmanız gereken tek şey yalnızca kök günlükçüyü yapılandırmaktır. Bu logging.config.fileConfig(günlükçüler için kendi yapılandırmanız) veya logging.basicConfig(kök günlükçüyü ayarlar) . Girişiniz modülünde Kur günlük ( __main__.pyya da örneğin, çalıştırmak istediğiniz ne olursa olsun main_script.py. __init__.pyyanısıra çalışır)

basicConfig kullanarak:

# package/__main__.py
import logging
import sys

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

fileConfig kullanarak:

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

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

ve daha sonra aşağıdakileri kullanarak her kaydediciyi oluşturun:

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

log.info("Hello logging!")

Daha fazla bilgi için, bkz. Gelişmiş Günlük Eğitimi .


15
bu, bugüne kadar, problemin en basit çözümü, modüller arasındaki ebeveyn-çocuk ilişkisini ortaya çıkarır ve bundan yararlanır, bir noob olarak bilmediğim bir şeydir. danke.
Görev Monger

Haklısın. vinay görevinde de belirtildiği gibi, çözümünüz init .py modülünde olmadığı sürece doğrudur . senin çözüm ana modül (giriş noktası) uygulandığında işe yaradı.
Görev Monger

2
aslında soru ayrı modüller ile ilgili olduğundan çok daha ilgili bir cevap.
Jan Sila

1
Aptalca bir soru belki: eğer herhangi bir logger __main__.pyyoksa (örn. Modülü logger olmayan bir kodda kullanmak istiyorsanız) logging.getLogger(__name__)hala modülde bir tür günlük kaydı yapar mı yoksa bir istisna oluşturur mu?
Bill

1
En sonunda. Çalışan bir günlükçüm vardı, ancak Windows için Paralib joblib ile çalışır. Sanırım bu sistem için manuel bir tweak - Paralel ile başka bir şey yanlış. Ama, kesinlikle işe yarıyor! Teşekkürler
B Furtado

17

Her zaman aşağıdaki gibi yaparım.

Günlüğümü ' log_conf.py' adlı tekli desen olarak yapılandırmak için tek bir python dosyası kullan

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

Başka bir modülde, yapılandırmayı içe aktarmanız yeterlidir.

from log_conf import Logger

Logger.logr.info("Hello World")

Bu, basit ve verimli bir şekilde kaydedilecek tek bir modeldir.


1
singleton desenini detaylandırdığınız için teşekkürler. Ben bu uygulamayı planlıyordu, ama sonra @prost çözüm çok daha basit ve benim ihtiyaçlarına mükemmel uygun. Ancak çözümünüzün faydalı olduğunu görüyorsunuz (ana hariç) birden fazla giriş noktasına sahip daha büyük projeler. danke.
Görev Monger

46
Bu işe yaramaz. Kök kaydedici zaten tek bir ton. Logger.logr.info yerine logging.info kullanın.
Pod

9

Bu yanıtların birçoğu, bir modülün üstünde,

import logging
logger = logging.getLogger(__name__)

Anladığım kadarıyla bu çok kötü bir uygulama olarak görülüyor . Bunun nedeni, dosya yapılandırmasının varsayılan olarak mevcut tüm günlükleyicileri devre dışı bırakmasıdır. Örneğin

#my_module
import logging

logger = logging.getLogger(__name__)

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

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

Ve ana modülünüzde:

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

Artık logging.ini dosyasında belirtilen günlük boş olacaktır, çünkü mevcut günlük dosyası fileconfig çağrısı tarafından devre dışı bırakılmıştır.

Bu sorunun üstesinden gelmek kesinlikle mümkün olsa da (ctiv_existing_Loggers = False), kütüphanenizin birçok istemcisi bu davranışı bilmez ve günlüklerinizi almaz. Her zaman yerel olarak logging.getLogger'ı arayarak müşterileriniz için kolaylık sağlayın. Şapka İpucu: Bu davranışı Victor Lin'in Web sitesinden öğrendim .

Bunun yerine her zaman logging.getLogger'ı yerel olarak çağırmak iyi bir uygulamadır. Örneğin

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

Ayrıca, ana dosyanızda fileconfig kullanıyorsanız, kütüphane tasarımcılarınızın modül düzeyinde günlükçü örnekleri kullanmaları durumunda, enable_existing_loggers = False olarak ayarlayın.


Daha logging.config.fileConfig('logging.ini')önce kaçamaz mısın import my_module? Bu cevapta önerildiği gibi .
lucid_dreamer

Emin değilim - ama ithalat ve yürütülebilir kodu bu şekilde karıştırmak kesinlikle kötü bir uygulama olarak kabul edilir. Ayrıca müşterilerinizin, özellikle önemsiz bir alternatif olduğunda, içe aktarmadan önce günlüğe kaydetmeyi yapılandırmaları gerekip gerekmediğini denetlemelerini istemezsiniz! İstekler gibi yaygın olarak kullanılan bir kütüphanenin bunu yaptığını düşünün ....!
phil_20686

"Emin değilim - ama ithalat ve yürütülebilir kodu bu şekilde karıştırmak kesinlikle kötü bir uygulama olarak kabul edilir." - neden?
lucid_dreamer

Bunun neden kötü olduğu konusunda çok net değilim. Ve örneğini tam olarak anlamıyorum. Bu örnek için yapılandırmanızı gönderebilir ve kullanım gösterebilir misiniz?
lucid_dreamer

1
Resmi dokümanlar ile çelişiyor gibi görünüyorsunuz : 'Kaydedicilere ad verirken kullanılacak iyi bir kural, günlüğe kaydetme kullanan her modülde aşağıdaki şekilde adlandırılan modül düzeyinde bir günlükçü kullanmaktır: logger = logging.getLogger(__name__)'
iron9

9

Günlük kütüphane kitaplığının bir örneğini benim için birden çok modülde kullanmanın basit bir yolu aşağıdaki çözümü kullanmaktı:

base_logger.py

import logging

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

Diğer dosyalar

from base_logger import logger

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

7

Başka bir çözeltide fırlatma.

Modülün init .py dosyasında şöyle bir şey var:

# 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

Sonra her modülde bir günlükçüye ihtiyacım var:

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

Günlükler kaçırıldığında, kaynaklarını geldikleri modülle ayırt edebilirsiniz.


"Modülümün ana girişi" ne anlama geliyor? Ve "O zaman her sınıfta bir kayıt cihazına ihtiyacım var, ben:"? _Module.py adlı bir örnek ve bunun caller_module.py modülünden içe aktarma olarak kullanılmasına bir örnek verebilir misiniz? Hakkında sorduğum biçimden ilham almak için bu cevaba bakınız . Patronaj olmaya çalışmıyorum. Cevabınızı anlamaya çalışıyorum ve bu şekilde yazsaydınız yapacağımı biliyorum.
lucid_dreamer

1
@lucid_dreamer Açıkladım.
Tommy

4

Bunun gibi bir şey de bulabilirsin!

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

Yukarıdakiler ayrı bir modülde tanımlanmışsa ve diğer modüllere içe aktarılmışsa, günlüğe kaydetme gerekiyorsa, aynı modülde ve tüm projede birden fazla kaydedici kullanabilirsiniz.

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

4

@ Yarkee'nin çözümü daha iyi görünüyordu. Bir şey daha eklemek istiyorum -

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)

Dolayısıyla LoggerManager tüm uygulamaya takılabilir. Umarım mantıklı ve değerlidir.


11
Günlüğe kaydetme modülü zaten tekil öğelerle ilgilenir. logging.getLogger ("Merhaba") tüm modüllerinizde aynı günlüğü alacaktır.
Pod

2

Birkaç cevap var. Bana mantıklı gelen benzer ama farklı bir çözüm buldum, belki de sizin için de mantıklı olacaktır. Benim asıl amacım günlükleri düzeylerine göre işleyicilere aktarabilmekti (hata ayıklama düzeyi günlükleri konsola, uyarıları ve üstünü dosyalara aktarmak):

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)

logger.py adında güzel bir util dosyası oluşturdu:

import logging

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

flask.app, flaskta sabit kodlanmış bir değerdir. uygulama günlüğü her zaman modül adı olarak flask.app ile başlar.

şimdi, her modülde, aşağıdaki modda kullanabilirsiniz:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Bu, minimum çaba ile "app.flask.MODULE_NAME" için yeni bir günlük oluşturacak.


2

En iyi uygulama, görevi çağıran yönteme bir logger işleyicisi vermek olan tek bir yönteme sahip ayrı bir modül oluşturmak olacaktır. Bu dosyayı m_logger.py kaydedin

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

Şimdi günlükçü işleyicisi gerektiğinde getlogger () yöntemini çağırın.

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

1
Ek parametreleriniz yoksa bu iyidir. Ancak, diyelim ki, --debuguygulamada bir seçeneğiniz var ve bu parametreye dayalı olarak uygulamanızdaki tüm kaydedicilerde günlük kaydı düzeyini ayarlamak istiyorsanız ...
The Godfather

@TheGodfather Evet, bu metodoloji ile bunu başarmak zor. Bu durumda yapabileceğimiz, nesne oluşturma sırasında formatlayıcıyı parametre olarak alacak ve logger işleyicisini döndürmek için benzer fonksiyona sahip olacak bir sınıf oluşturmaktır.
Mousam Singh

Evet, benzer bir şey yaptım, get_logger(level=logging.INFO)bir tür singleton döndürmek için yaptım , bu yüzden ana uygulamadan ilk kez çağrıldığında, logger ve işleyicileri uygun seviyeyle başlatır ve aynı loggernesneyi diğer tüm yöntemlere döndürür .
babası

0

Python için yeni, bu yüzden bu tavsiye edilebilir olup olmadığını bilmiyorum, ama boilerplate yeniden yazma değil için harika çalışıyor.

Bir modül olarak yüklenebilmesi için projenizde bir init .py olmalıdır

# 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)öneri buradan geliyor

Ardından günlükçünüzü başka bir dosyada kullanmak için:

from [your module name here] import logger

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

Uyarılar:

  1. Dosyalarınızı modül olarak çalıştırmalısınız, aksi takdirde import [your module]çalışmaz:
    • python -m [your module name].[your filename without .py]
  2. Programınızın giriş noktası için günlükçünün adı olacaktır __main__, ancak kullanılan herhangi bir çözümde __name__bu sorun olacaktır.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.