Cara menggunakan logging python

Artikel ini terutama memperdalam pengenalan python logging. Lebih penting untuk membahas cara menggunakan logging untuk mengeluarkan log di lingkungan multi -proses, cara memotong file log dengan aman.

1.logging PENDAHULUAN MODUL LOG

Modul logging dari python menyediakan modul standar yang fleksibel, sehingga program Python apa pun dapat menggunakan modul bagian ketiga ini untuk mengimplementasikan catatan log ini.python logging dokumen resmi

logging Kerangka kerja ini terutama terdiri dari empat bagian:

Loggers: antarmuka yang dapat dipanggil secara langsung

Handlers: memutuskan untuk mendistribusikan catatan log ke tujuan yang tepat

Filters: memberikan penilaian log yang lebih baik

Formatters: merumuskan tata letak format dari pencetakan rekaman akhir

2.logging Komposisi

loggers

loggers adalah antarmuka log yang dapat dihubungi oleh program secara langsung, dan dapat menulis informasi log secara langsung ke logger. logger tidak secara langsung digunakan untuk digunakan, tetapi untuk mendapatkan objek melalui logging.getLogger(name). Faktanya, objek logger adalah satu contoh. sama. Namun sayangnya, logger tidak mendukung multi.

<Catatan> loggers objek memiliki hubungan ayah -son. Ketika tidak ada orang tua logger objek, objek ayahnya adalah root.. Misalnya, logging.getLogger("abc.xyz") akan membuat dua objek logger, satu adalah objek ayah abc, yang lainnya adalah xyz sub -object, dan abc tidak memiliki objek indukroot dan abc tidak memiliki objek indukroot dan abc memiliki orangtuaroot6abc memiliki orangtuarootIVJ6. >. Tetapi pada kenyataannya, abc adalah objek yang menahan objek (objek log virtual). Anda tidak dapat handler untuk menangani log. Namun, root bukan target suatu tempat. Jika objek log memutar log, objek induknya akan menerima log pada saat yang sama, sehingga beberapa pengguna akan menemukan objek logger dua kali. Karena ia membuat log logger, dan pada saat yang sama, objek root juga tekan log.

Masing -masing logger memiliki tingkat log. logging mendefinisikan level berikut

LevelNumeric value
NOTSET 0
DEBUG 10
INFO 20
WARNING 30
ERROR 40
CRITICAL 50

Ketika a logger menerima informasi log, pertama -tama tentukan apakah memenuhi level, jika diputuskan untuk menangani informasi, informasi tersebut diteruskan ke Handlers untuk diproses.

Handlers

Handlers Informasi yang dikirim oleh logger dialokasikan secara akurat ke tempat yang tepat. Ambil chestnut dan kirimkan ke konsol atau file atau both atau tempat lain (saluran proses dan sejenisnya). Ini menentukan perilaku masing -masing log. Ini adalah area utama yang perlu dikonfigurasi nanti.

Masing -masing Handler juga memiliki level log. Satu logger dapat memiliki beberapa handler artinya logger dapat meneruskan log ke berbagai handler> sesuai dengan tingkat log yang berbeda.. Tentu saja, dapat diteruskan ke beberapa handlers pada tingkat yang sama. Ini secara fleksibel diatur sesuai dengan kebutuhan.

Filters

Filters memberikan penilaian granular yang lebih baik untuk menentukan apakah log perlu dicetak. Pada prinsipnya, handler mendapat log akan diproses secara seragam sesuai dengan level, tetapi jika handler memiliki Filter. Misalnya, Filter dapat mencegat or untuk memodifikasi atau bahkan memodifikasi level lognya (setelah modifikasi).

logger dan handler dapat diinstal filter atau bahkan beberapa filter seri yang menghubungkannya..

Formatters

Formatters Menentukan tata letak format dari pencetakan rekaman akhir. Formatter akan menjahit informasi yang dikirimkan ke string tertentu. Secara default. Format Ada beberapa atribut LogRecord yang dapat digunakan, seperti yang ditunjukkan dalam bentuk berikut:

AttributeFormatDescription
asctime %(asctime)s Membangun waktu log ke dalam bentuk yang dapat dibaca, standarnya adalah '2016-02-08 12: 00: 00, 123' akurat hingga milidetik
filename %(filename)s Nama file yang berisi path
funcName %(funcName)s Yang function dikirim log
levelname %(levelname)s Level akhir log (dimodifikasi oleh filter)
message %(message)s Informasi log
lineno %(lineno)d Jumlah baris log saat ini
pathname %(pathname)s Rute lengkap
process %(process)s Proses saat ini
thread %(thread)s Utas saat ini

A Handler hanya dapat memiliki satu Formatter Oleh karena itu untuk mencapai beberapa format untuk mencapai beberapa Handler. <

3.logging konfigurasi

Konfigurasi sederhana

Pertama -tama, ini dijelaskan dalam bab loggers. Kami memiliki objek log default root. Keuntungan dari root log ini adalah bahwa kita dapat secara langsung menggunakan <logging untuk mengkonfigurasi dan memainkan log logs .. Misalnya:

logging.basicConfig(level=logging.INFO,filename='logger.log')
logging.info("info message")

Jadi konfigurasi sederhana di sini mengacu pada objek log root, kasual. Masing -masing logger adalah contoh tunggal, sehingga dapat dipanggil di mana saja dalam program setelah konfigurasi. Kita hanya perlu memanggil basicConfig untuk membuat konfigurasi sederhana dari objek log root. Sebenarnya, metode ini cukup efektif dan mudah digunakan. Itu membuatnya menjamin bahwa setidaknya satu Handler dapat diproses saat memanggil logger.

Konfigurasi sederhana dapat diatur seperti ini:

logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='[%Y-%m_%d %H:%M:%S]',
filename='../log/my.log',
filemode='a')

Konfigurasi Kode

Metode pengaturan lain yang lebih rinci adalah mengonfigurasi dalam kode, tetapi metode pengaturan ini adalah cara yang paling sedikit untuk digunakan. Bagaimanapun, tidak ada yang mau menulis pengaturan ke kode. Tapi di sini sedikit diperkenalkan, meskipun tidak banyak, Anda dapat menggunakannya bila perlu. (Make up nanti)

Konfigurasi File Konfigurasi

File konfigurasi logging di python adalah fungsi berdasarkan ConfigParser. Dengan kata lain, format file konfigurasi juga ditulis dengan cara ini. Mari kita taruh file konfigurasi yang lebih umum dan kemudian bicarakan

##############################################
[loggers]
keys=root, log02
[logger_root]
level=INFO
handlers=handler01
[logger_log02]
level=DEBUG
handler=handler02
qualname=log02
##############################################
[handlers]
keys=handler01,handler02
[handler_handler01]
class=FileHandler
level=INFO
formatter=form01
args=('../log/cv_parser_gm_server.log',"a")
[handler_handler02]
class=StreamHandler
level=NOTSET
formatter=form01
args=(sys.stdout,)
##############################################
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(process)d %(message)s
datefmt=[%Y-%m-%d %H:%M:%S]
[formatter_form02]
format=(message)s
##############################################

Saya percaya bahwa setelah menontonnya sekali, saya juga menemukan aturannya. Saya membagi potongan -potongan besar dengan#. Masing -masing logger atau handler atau formatter memiliki nama key. Ambil logger sebagai contoh, pertama -tama, Anda perlu menambahkan key ke konfigurasi <loggers> untuk mewakili logger ini. Kemudian gunakan <loggers_xxxx> di antara mereka, xxxx untuk key, ini logger, dalam log02 saya mengkonfigurasi level dan a handler beberapa hander. Menurut nama handler ini, go <handlers> untuk menemukan konfigurasi handler spesifik, dan sebagainya.

Kemudian dalam kode, lalu muat file konfigurasi:

logging.config.fileConfig(log_conf_file)

Ada konfigurasi class di handler, dan beberapa pembaca mungkin tidak tahu banyak. Faktanya, ini adalah beberapa kelas handler yang awalnya ditulis dalam logging, Anda dapat menelepon langsung di sini. class Kelas menunjuk ke kategori ini setara dengan pelaksana Handler dari pemrosesan spesifik. Dalam dokumentasi logging, Anda dapat mengetahui bahwa semua kelas Handler di sini adalah thread -safe, semua orang dapat menggunakannya dengan percaya diri. Lalu pertanyaannya adalah, bagaimana jika ada lebih banyak proses?. Di bab berikutnya, saya terutama menulis kelas Handler untuk mencapai penggunaan logging di beberapa lingkungan proses. Kami menulis ulang atau membuat kelas Handler oleh diri kami sendiri, dan kemudian arahkan kelas Handler ke konfigurasi Handler kami <Handler kami.

4.logging menemukan beberapa proses (important)

Bagian ini sebenarnya adalah niat asli saya untuk menulis artikel ini. python Karena beberapa alasan historis, kinerja multi -threading pada dasarnya dapat diabaikan. Karena itu. Tapi python logging tidak mendukung banyak proses, jadi saya akan menghadapi banyak masalah.

Kali ini saya mengambil masalah TimedRotatingFileHandler sebagai contoh sebagai contoh. Ini Handler peran asli adalah: memotong file log demi hari. (File hari itu adalah xxxx.log file kemarin adalah xxxx.log.2016-06-01). Keuntungan dari ini adalah bahwa seseorang dapat menemukan log sesuai hari, dan yang kedua dapat membuat file log tidak terlalu besar, dan log yang kedaluwarsa dapat dihapus dari hari itu.

Tetapi masalahnya datang. Jika Anda menggunakan beberapa proses untuk menampilkan log, hanya ada satu proses untuk beralih. Proses lain akan terus diputar dalam dokumen asli. Mungkin ada proses lain dalam beberapa proses saat beralih. File log terkena hit , maka dia akan menghapusnya tanpa henti, dan kemudian membuat file log baru. Bagaimanapun, saya akan sangat berantakan dan berantakan..

Jadi di sini saya telah memikirkan beberapa cara untuk menyelesaikan masalah beberapa proses logging

alasan

Sebelum memecahkan, mari kita lihat mengapa ini menyebabkan alasan ini.

Posting pertama kode sumber TimedRotatingFileHandler bagian ini adalah operasi yang dibuat selama switching:

def doRollover(self):
"""
do a rollover; in this case, a date/time stamp is appended to the filename
when the rollover happens. However, you want the file to be named for the
start of the interval, not the current time. If there is a backup count,
then we have to get a list of matching filenames, sort them and remove
the one with the oldest suffix.
"""
if self.stream:
self.stream.close()
self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(t + addend)
dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
if os.path.exists(dfn):
os.remove(dfn)
# Issue 18940: A file may not have been created if delay is True.
if os.path.exists(self.baseFilename):
os.rename(self.baseFilename, dfn)
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.stream = self._open()
newRolloverAt = self.computeRollover(currentTime)
while newRolloverAt <= currentTime:
newRolloverAt = newRolloverAt + self.interval
#If DST changes and midnight or weekly rollover, adjust for this.
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
self.rolloverAt = newRolloverAt

Mari kita amati baris if os.path.exists(dfn). Logikanya di sini adalah bahwa jika file dfn ada, kemudian hapus terlebih dahulu, lalu ganti nama file dfn dfn baseFilename. Kemudian buka file baseFilename dan mulai menulis sesuatu. Maka logikanya di sini sangat jelas

Misalkan file log saat ini disebut current.log Nama file adalah current.log.2016-06-01

Tentukan apakah current.log.2016-06-01 ada, jika ada, hapus

Nama file log saat ini diganti namanya current.log.2016-06-01

Buka file baru lagi (saya mengamati mode "a" dalam kode sumber, yang dikatakan "w")

Jadi dalam hal beberapa proses, suatu proses diaktifkan, dan pegangan proses lain masih current.log.2016-06-01 dan akan terus menulis hal -hal di dalam. Atau mungkin sakelar eksekusi proses, dan proses lainnya akan diganti namanya current.log.2016-06-01 file langsung untuk menghapus secara langsung. Atau ada situasi lain. Ketika satu proses menulis sesuatu, proses lainnya sudah beralih, yang akan menyebabkan situasi yang tidak terduga.. Ada juga satu kasus dua proses memotong file secara bersamaan. Proses pertama adalah mengeksekusi Langkah 3, proses kedua baru saja menyelesaikan langkah kedua, dan kemudian proses pertama telah menyelesaikan ganti nama tetapi belum membuat current.log Proses kedua mulai mengganti nama. Pada saat ini, proses kedua akan salah karena current. Jika proses pertama telah berhasil membuat current.log Proses kedua akan menyimpan file kosong ini sebagai current.log.2016-06-01. Jadi tidak hanya menghapus file log, tetapi juga, proses pertama kali berpikir bahwa itu telah selesai dan divisi tidak akan dipotong lagi, tetapi sebenarnya pegangannya menunjuk ke current.log.2016-06-01.

Nah, kelihatannya rumit di sini, pada kenyataannya, karena ketika operasi file dioperasikan, tidak ada kendala pada multi -proses..

Jadi bagaimana menyelesaikan masalah ini dengan elegan?. Saya telah mengusulkan dua rencana, tentu saja, saya akan mengusulkan solusi yang lebih layak di bawah ini untuk semua orang dapat dicoba.

Solusi 1

Sebelumnya kami menemukan cacat logis di TimedRotatingFileHandler. Kita hanya perlu sedikit memodifikasi logika:

Tentukan apakah file current.log.2016-06-01 ada, jika tidak ada keberadaan, itu akan diganti namanya. (Jika ada proses lain yang telah dipotong, saya tidak perlu memotongnya, ubah saja pegangannya.)

Buka current.log dalam mode "a"

Sangat sederhana setelah menemukan modifikasi~

talking is cheap show me the code:

class SafeRotatingFileHandler(TimedRotatingFileHandler):
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
TimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding, delay, utc)
"""
Override doRollover
lines commanded by "##" is changed by cc
"""
def doRollover(self):
"""
do a rollover; in this case, a date/time stamp is appended to the filename
when the rollover happens. However, you want the file to be named for the
start of the interval, not the current time. If there is a backup count,
then we have to get a list of matching filenames, sort them and remove
the one with the oldest suffix.

Override, 1. if dfn not exist then do rename
2. _open with "a" model
"""
if self.stream:
self.stream.close()
self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(t + addend)
dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
## if os.path.exists(dfn):
## os.remove(dfn)

# Issue 18940: A file may not have been created if delay is True.
## if os.path.exists(self.baseFilename):
if not os.path.exists(dfn) and os.path.exists(self.baseFilename):
os.rename(self.baseFilename, dfn)
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.mode = "a"
self.stream = self._open()
newRolloverAt = self.computeRollover(currentTime)
while newRolloverAt <= currentTime:
newRolloverAt = newRolloverAt + self.interval
#If DST changes and midnight or weekly rollover, adjust for this.
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
self.rolloverAt = newRolloverAt

Jangan berpikir bahwa kode itu begitu lama. Faktanya, bagian modifikasi adalah tempat di mana komentar "##" hanya.. Kelas ini telah mewarisi TimedRotatingFileHandler untuk menulis ulang proses ini. Solusi ini sangat elegan, ada sangat sedikit perubahan, dan juga sangat efektif. Namun, beberapa netizen telah menyarankan bahwa masih ada tempat yang tidak sempurna di sini, yaitu, langkah rename. Jika sangat kebetulan, dua atau lebih proses telah memasuki pernyataan if, dan rename masih sama. Log akan terjadi. Situasi ini memang terjadi. Karena file divisi hanya sekali sehari, ada dua operasi Handler pada saat yang sama ketika divisi dipotong, dan kebetulan ada di sini pada saat yang sama. Di kunci file sebelumnya, if dikunci, dan kemudian dinilai setelah mendapatkan kunci, dan kemudian metode rename sempurna.. Kode tidak diposting lagi, melibatkan kode kunci, yang mempengaruhi keindahan.

Solusi 2

Saya pikir solusi paling sederhana dan efektif. Membunuh kembali kelas FileHandler (kelas ini adalah Handler bahwa semua menulis file yang akan diwarisi TimedRotatingFileHandler adalah kelas ini;Kami dapat menambahkan beberapa penilaian dan operasi sederhana.

Logika kami seperti ini:

Tentukan apakah stempel waktu saat ini bersamaan dengan nama file yang diarahkan

Jika tidak, ganti file arah

Akhir, apakah itu logika yang sangat sederhana.

talking is cheap show me the code:

class SafeFileHandler(FileHandler):
def __init__(self, filename, mode, encoding=None, delay=0):
"""
Use the specified filename for streamed logging
"""
if codecs is None:
encoding = None
FileHandler.__init__(self, filename, mode, encoding, delay)
self.mode = mode
self.encoding = encoding
self.suffix = "%Y-%m-%d"
self.suffix_time = ""
def emit(self, record):
"""
Emit a record.
Always check time
"""
try:
if self.check_baseFilename(record):
self.build_baseFilename()
FileHandler.emit(self, record)
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)

def check_baseFilename(self, record):
"""
Determine if builder should occur.

record is not used, as we are just comparing times,
but it is needed so the method signatures are the same
"""
timeTuple = time.localtime()

if self.suffix_time != time.strftime(self.suffix, timeTuple) or not os.path.exists(self.baseFilename+'.'+self.suffix_time):
return 1
else:
return 0
def build_baseFilename(self):
"""
do builder; in this case,
old time stamp is removed from filename and
a new time stamp is append to the filename
"""
if self.stream:
self.stream.close()
self.stream = None

# remove old suffix
if self.suffix_time != "":
index = self.baseFilename.find("."+self.suffix_time)
if index == -1:
index = self.baseFilename.rfind(".")
self.baseFilename = self.baseFilename[:index]

# add new suffix
currentTimeTuple = time.localtime()
self.suffix_time = time.strftime(self.suffix, currentTimeTuple)
self.baseFilename = self.baseFilename + "." + self.suffix_time

self.mode = 'a'
if not self.delay:
self.stream = self._open()

check_baseFilename adalah logika eksekusi 1 penilaian;build_baseFilename adalah untuk mengeksekusi logika 2 untuk mengubah pegangan. Sangat mudah untuk diselesaikan.

Solusi semacam ini berbeda dari sebelumnya bahwa file saat ini adalah current.log.2016-06-01. By Tomorrow, file saat ini adalah current.log.2016-06-02. Sangat sederhana dan elegan. Ini juga dapat menyelesaikan masalah beberapa proses logging.

Solusi dan lainnya

Tentu saja, ada solusi lain, seperti log proses yang seragam logging, dan proses lain memasuki pipa proses logging dengan proses lain.. Ada juga alasan yang sama di antara log ke dalam jaringan socket.

5. Bahan referensi

python logging dokumen resmi