Python günlük biçimi dizesine nasıl özel alan eklerim?


91

Mevcut biçim dizem:

formatter = logging.Formatter('%(asctime)s : %(message)s')

ve app_namebu biçimlendiriciyi içeren her komut dosyasında farklı bir değeri olacak adlı yeni bir alan eklemek istiyorum .

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

Ancak app_name, biçim dizesine enterpolasyon yapmak için bu değeri günlükçüye nasıl ileteceğimi bilmiyorum . Açıkçası, her seferinde geçerek günlük mesajında ​​görünmesini sağlayabilirim ama bu karmaşık bir durum.

Denedim:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

ama hiçbiri çalışmıyor.


Bunu gerçekten her logaramaya aktarmak istiyor musun ? Eğer öyleyse, bakmak docs diyor "Bu işlevsellik ... Bir LogRecord içine kendi değerlerini enjekte etmek için kullanılabilir" Ama bu kullanmak için bir asal vaka gibi görünüyor logger = logging.getLogger('myapp')ve onu içine pişmiş olan logger.infoçağrı.
abarnert

python günlüğü bunu zaten yapabilir. Farklı bir kullanırsanız loggerher uygulamada nesne, kendi başlatmasını tarafından her biri kullanacağım farklı bir ad yapabilirsiniz loggerşöyle s: logger = logging.getLogger(myAppName). __name__bunun python modül adı olduğunu unutmayın , bu nedenle her uygulama kendi python modülüyse, bu da işe yarayacaktır.
Florian Castellane

Yanıtlar:


131

Bir LoggerAdapter kullanabilirsiniz, böylece her günlüğe kaydetme çağrısında fazladan bilgi iletmek zorunda kalmazsınız:

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

günlükler (benzeri)

2013-07-09 17:39:33,596 Super App : The sky is so blue

Filtreler , bağlamsal bilgi eklemek için de kullanılabilir.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

benzer bir günlük kaydı oluşturur.


3
Bunu bir config.inidosyada nasıl belirtebiliriz ? Mevcut ana bilgisayar adını eklemek istiyorum socket.gethostname().
Laurent LAPORTE

Bu örnek benim için çalışmıyor. import uuid uniqueId = str(uuid.uuid4()) extra = {"u_id" : uniqueId} RotatingHandler = RotatingFileHandler(LOG_FILENAME,encoding='utf-8',maxBytes=maxSize, backupCount=batchSize) logger.basicConfig(handlers=[RotatingHandler],level=logLevel.upper(),format='%(levelname)s %(u_id)s %(funcName)s %(asctime)s %(message)s ',datefmt='%m/%d/%Y %I:%M:%S %p') logger = logger.LoggerAdapter(logger=logger, extra=extra)
Hayat


2
Bir dizi Ek Bilgi iletebilir miyim? Şöyle bir şey: "Çalışan kimliği 1029382 için hata oluştu" Herhangi bir sözlük oluşturmadan.
shreesh katti

50

Bu şekilde yapmak için dikteyi fazladan bir parametre olarak iletmeniz gerekir.

logging.info('Log message', extra={'app_name': 'myapp'})

Kanıt:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

Ayrıca, bir not olarak, dikteyi geçmeden bir mesaj kaydetmeye çalışırsanız, başarısız olur.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

Bu da işe yarayacak logging.info()mı? En son denediğimde başarısız oldu. : /
Prakhar Mohan Srivastava

2
@ Mr2ert cevabını beğendim. logging.FormatterSınıfı genişleterek ekstra alana varsayılan bir değer verebilirsiniz : class CustomFormatter (logging.Formatter): def format (self, record): eğer hasattr değilse (kayıt, 'foo'): record.foo = 'default_foo' return super (CustomFormatter, self.format (kayıt) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ('% (foo) s% (mesaj) s') logger = logging.getLogger ('bar') logger.addHandler ( h) logger.error ('hey!', extra = {'foo': 'FOO'}) logger.error ('hey!')
loutre

Bu yöntem daha hızlıdır, ancak her günlük mesajına unutması kolay ve hatalara açık ekstra satırlar eklemeniz gerekir. Süper () çağrılarını değiştirmek unutbu'dan gelen cevaptan daha karmaşıktır.
pevogam

@Prakhar Mohan Srivastava Evet logging.info () için de iyi çalışacak. Hangi hata mesajını alıyorsunuz?
shreesh katti

Bir dizi Ek Bilgi iletebilir miyim? Şuna benzer bir şey: "Çalışan kimliği 1029382 için hata oluştu" Herhangi bir sözlük oluşturmadan ve anahtarları
iletmeden

23

Python3

Python3.2'den itibaren artık LogRecordFactory'yi kullanabilirsiniz

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

Tabii ki record_factoryherhangi bir çağrılabilir olacak şekilde özelleştirilebilir custom_attributeve fabrikada çağrılabilir bir referans tutarsanız değeri güncellenebilir.

Bu neden Adaptörler / Filtreler kullanmaktan daha iyidir?

  • Kaydedicinizi uygulamanın etrafından geçirmenize gerek yok
  • Aslında kendi kaydedicilerini kullanan (sadece arayarak logger = logging.getLogger(..)) 3. taraf kitaplıkları ile çalışır, artık aynı günlük biçimine sahip olacaktır. (Aynı kaydedici nesnesini kullanmanız gereken Filtreler / Adaptörler için durum böyle değildir)
  • Birden fazla fabrikayı istifleyebilir / zincirleyebilirsiniz

Python 2.7 için herhangi bir alternatif var mı?
karolch

Aynı avantajlarla değil, 2.7 ile Adaptörler veya Filtreler ile gitmeniz gerekir.
Ahmad

5
Bu günümüzün python3 en iyi cevabı
Stéphane

Docs.python.org/3/howto/logging-cookbook.html'ye göre : Bu model, farklı kitaplıkların fabrikaları birbirine zincirlemesine ve birbirlerinin özniteliklerinin üzerine yazmadıkları veya standart olarak sağlanan özniteliklerin üzerine istemeden üzerine yazmadıkları sürece, orada sürpriz olmamalı. Bununla birlikte, zincirdeki her bir bağlantının tüm kayıt işlemlerine çalışma süresi ek yükü eklediği ve tekniğin yalnızca bir Filtrenin kullanılması istenen sonucu sağlamadığında kullanılması gerektiği unutulmamalıdır.
steve0hh

1
@ steve0hh, istenen anahtar sonuçlardan biri, bağlamsal bilgileri farklı kitaplıklar / modüller arasında günlüğe kaydetme yeteneğidir, bu ancak bu yolla elde edilebilir. Çoğu durumda, kitaplıklar kaydedici yapılandırmasına dokunmamalıdır, bu ana uygulamanın sorumluluğundadır.
Ahmad

9

Başka bir yol da özel bir LoggerAdapter oluşturmaktır. Bu, özellikle biçimi değiştiremediğinizde VEYA biçiminiz benzersiz anahtarı göndermeyen kodla paylaşılıyorsa (sizin durumunuzda uygulama_adı ) yararlıdır :

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

Ve kodunuzda, kaydedicinizi her zamanki gibi oluşturur ve başlatırsınız:

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

Son olarak, gerektiğinde bir önek eklemek için sarmalayıcı bağdaştırıcısı oluşturursunuz:

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

Çıktı şunun gibi görünecek:

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

1

Bu SO sorusunu kendim uyguladıktan sonra buldum. Umarım birine yardımcı olur. Aşağıdaki kodda claim_id, günlükçü biçiminde adlandırılan fazladan bir anahtar oluşturuyorum . Ortamda bir claim_idanahtar mevcut olduğunda, iddia_ kimliğini günlüğe kaydedecektir . Kullanım durumumda, bu bilgileri bir AWS Lambda işlevi için kaydetmem gerekiyordu.

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'


class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})


def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger


LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

Ana bilgi: https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652


0

Mr2ert'in cevabını kullanarak, bu rahat çözümü buldum (Önerilmiyor olsa da) - Özel argümanı kabul etmek için yerleşik günlükleme yöntemlerini geçersiz kılın ve yöntemlerin extraiçinde sözlüğü oluşturun :

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

Çıktı:

2019-03-02 20:06:51,998 [bar] test

Bu, referans için yerleşik işlevdir:

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)

0

içe aktarma günlüğü;

class LogFilter (logging.Filter):

def __init__(self, code):
    self.code = code

def filter(self, record):
    record.app_code = self.code
    return True

logging.basicConfig (format = '[% (asctime) s:% (levelname) s] :: [% (modül) s ->% (name) s] - APP_CODE:% (app_code) s - MSG:% (mesaj ) s ');

class Logger:

def __init__(self, className):
    self.logger = logging.getLogger(className)
    self.logger.setLevel(logging.ERROR)

@staticmethod
def getLogger(className):
    return Logger(className)

def logMessage(self, level, code, msg):
    self.logger.addFilter(LogFilter(code))

    if level == 'WARN':        
        self.logger.warning(msg)
    elif level == 'ERROR':
        self.logger.error(msg)
    else:
        self.logger.info(msg)

class Test: logger = Logger.getLogger ('Test')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')

Bu uygulama çok verimsiz olacaktır.
blakev
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.