Bagaimana cara menambahkan bidang khusus ke string format log Python?

91

Format string saya saat ini adalah:

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

dan saya ingin menambahkan bidang baru bernama app_nameyang akan memiliki nilai berbeda di setiap skrip yang berisi pemformat ini.

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

Tapi saya tidak yakin bagaimana meneruskan app_namenilai itu ke logger untuk diinterpolasi ke dalam format string. Saya jelas bisa membuatnya muncul di pesan log dengan meneruskannya setiap kali tetapi ini berantakan.

Saya sudah mencoba:

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

tapi tidak ada yang berhasil.

nickponline
sumber
Apakah Anda benar-benar ingin meneruskan ini ke setiap logpanggilan? Jika demikian, lihat dokumen yang menyatakan "Fungsionalitas ini dapat digunakan untuk memasukkan nilai Anda sendiri ke dalam LogRecord…" Tapi ini sepertinya kasus utama untuk menggunakan logger = logging.getLogger('myapp')dan memasukkannya ke dalam logger.infopanggilan.
abarnert
penebang python sudah bisa melakukan itu afaik. jika Anda menggunakan berbagai loggerobjek di setiap aplikasi, Anda dapat membuat masing-masing menggunakan nama yang berbeda oleh instantiating Anda loggers seperti: logger = logging.getLogger(myAppName). perhatikan itu __name__adalah nama modul python, jadi jika setiap aplikasi adalah modul pythonnya sendiri, itu juga akan berfungsi.
Florian Castellane

Jawaban:

131

Anda bisa menggunakan LoggerAdapter sehingga Anda tidak perlu meneruskan info tambahan dengan setiap panggilan logging:

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

log (sesuatu seperti)

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

Filter juga dapat digunakan untuk menambahkan informasi kontekstual.

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

menghasilkan catatan log serupa.

unutbu
sumber
3
Bagaimana kita bisa menentukannya dalam sebuah config.inifile? Saya ingin menambahkan nama host saat ini socket.gethostname().
Laurent LAPORTE
Saya memiliki sampel ini yang tidak berfungsi untuk saya. 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
Apakah mungkin menambahkan field "level" yang sama dengan "levelname"? Lihat: Bagaimana saya bisa mengganti nama “levelname” menjadi “level” dalam pesan log Python?
Martin Thoma
2
Bisakah saya meneruskan serangkaian Info Tambahan. Sesuatu seperti ini: "Terjadi kesalahan untuk ID karyawan 1029382" Tanpa membuat kamus apa pun.
shreesh katti
50

Anda perlu meneruskan dict sebagai parameter ke ekstra untuk melakukannya dengan cara itu.

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

Bukti:

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

Selain itu, sebagai catatan, jika Anda mencoba memasukkan pesan tanpa meneruskan dict, maka pesan itu akan gagal.

>>> 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
mr2ert.dll
sumber
Apakah ini juga akan berhasil logging.info()? Itu gagal ketika saya mencoba terakhir. : /
Prakhar Mohan Srivastava
2
Saya suka jawaban @ mr2ert. Anda dapat memberikan nilai default ke bidang ekstra dengan memperluas logging.Formatterkelas: class CustomFormatter (logging.Formatter): format def (self, record): if not hasattr (record, 'foo'): record.foo = 'default_foo' return super (CustomFormatter, self.format (record) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ('% (foo) s% (message) s') logger = logging.getLogger ('bar') logger.addHandler ( h) logger.error ('hey!', extra = {'foo': 'FOO'}) logger.error ('hey!')
loutre
Metode ini lebih cepat tetapi Anda perlu menambahkan baris tambahan pada setiap pesan log yang mudah dilupakan dan rawan kesalahan. Mengganti panggilan super () lebih berantakan daripada jawaban dari unutbu.
pevogam
@Prakhar Mohan Srivastava Ya Ini akan bekerja dengan baik untuk logging.info () juga. Pesan kesalahan apa yang Anda dapatkan?
shreesh katti
Bisakah saya meneruskan serangkaian Info Tambahan. Sesuatu seperti ini: "Terjadi kesalahan untuk ID karyawan 1029382" Tanpa membuat kamus apa pun dan meneruskan kunci
shreesh katti
23

Python3

Mulai Python3.2, Anda sekarang dapat menggunakan LogRecordFactory

>>> 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

Tentu saja record_factorydapat disesuaikan agar dapat dipanggil dan nilai custom_attributedapat diperbarui jika Anda menyimpan referensi ke dapat dipanggil pabrik.

Mengapa itu lebih baik daripada menggunakan Adaptor / Filter?

  • Anda tidak perlu melewatkan logger Anda di sekitar aplikasi
  • Ini benar-benar berfungsi dengan pustaka pihak ke-3 yang menggunakan logger mereka sendiri (hanya dengan menelepon logger = logging.getLogger(..)) sekarang akan memiliki format log yang sama. (ini tidak terjadi pada Filter / Adaptor di mana Anda harus menggunakan objek pencatat yang sama)
  • Anda dapat menumpuk / merantai beberapa pabrik
Ahmad
sumber
Apakah ada alternatif untuk python 2.7?
karolch
Tidak dengan manfaat yang sama, dengan 2.7 Anda harus menggunakan Adaptor atau Filter.
Ahmad
5
Ini adalah jawaban terbaik python3 jaman sekarang
Stéphane
Menurut docs.python.org/3/howto/logging-cookbook.html : Pola ini memungkinkan perpustakaan yang berbeda untuk menghubungkan pabrik bersama-sama, dan selama mereka tidak menimpa atribut satu sama lain atau secara tidak sengaja menimpa atribut yang disediakan sebagai standar, ada seharusnya tidak ada kejutan. Namun, harus diingat bahwa setiap link dalam rantai menambahkan overhead waktu proses ke semua operasi logging, dan teknik tersebut hanya boleh digunakan jika penggunaan Filter tidak memberikan hasil yang diinginkan.
steve0hh
1
@ steve0hh salah satu hasil kunci yang diinginkan adalah kemampuan untuk mencatat informasi kontekstual di berbagai pustaka / modul, yang hanya dapat dicapai dengan menggunakan cara ini. Dalam kebanyakan keadaan, pustaka tidak boleh menyentuh konfigurasi logger, itu tanggung jawab aplikasi induk.
Ahmad
9

Cara lainnya adalah dengan membuat LoggerAdapter khusus. Ini sangat berguna ketika Anda tidak dapat mengubah format ATAU jika format Anda dibagikan dengan kode yang tidak mengirimkan kunci unik (dalam kasus Anda app_name ):

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

Dan dalam kode Anda, Anda akan membuat dan menginisialisasi logger Anda seperti biasa:

    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)

Terakhir, Anda akan membuat adaptor pembungkus untuk menambahkan awalan sesuai kebutuhan:

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

Outputnya akan terlihat seperti ini:

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

Saya menemukan pertanyaan SO ini setelah menerapkannya sendiri. Semoga bisa membantu seseorang. Dalam kode di bawah ini, saya memasukkan kunci tambahan yang disebut claim_iddalam format logger. Ini akan mencatat klaim_id setiap kali ada claim_idkunci yang ada di lingkungan. Dalam kasus penggunaan saya, saya perlu mencatat informasi ini untuk fungsi AWS Lambda.

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")

Inti: https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652

rhn89
sumber
0

Menggunakan jawaban mr2ert, saya menemukan solusi yang nyaman ini (Meskipun saya kira itu tidak disarankan) - Ganti metode logging bawaan untuk menerima argumen khusus dan membuat extrakamus di dalam metode:

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

Keluaran:

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

Ini adalah fungsi bawaan untuk referensi:

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)
Yaniv K.
sumber
0

mengimpor penebangan;

kelas 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] :: [% (module) s ->% (name) s] - APP_CODE:% (app_code) s - MSG:% (pesan ) s ');

kelas 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)

Tes kelas: logger = Logger.getLogger ('Test')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')
pengguna3672617
sumber
Implementasi ini akan sangat tidak efisien.
blakev