Инструкции за заснемане и експортиране на регистрационни файлове

Регистрирането е важна възможност в Python за наблюдение на изправността на нашия код. В известен смисъл лог файловете споделят много от същите концепции като дебъгера. Можем да използваме регистрационни файлове, за да поставим навигационни трохи в нашия код, за да определим какво се случва вътре. Това е особено полезно, когато скриптът ни се срине по неизвестна причина — ако оставим достатъчно пътеки след себе си, можем да проследим пътя, по който е тръгнал кодът, и можем да съсредоточим вниманието си там. Обикновено дебъгерът на Python е полезен, когато за първи път тестваме кода си, но в даден момент смятаме, че кодът ни „работи“, преди да сме тествали всеки възможен случай на въвеждане. Ако използваме регистриране във фонов режим, ще имаме страхотна представа къде сме сбъркали, ако нещо се счупи по пътя. По-специално, ако използваме регистриране правилно, можем да видим точната уникална ситуация, която е нарушила нашия „работещ“ код.

Дневниците стават още по-критични, когато други използват нашия код. Когато даден скрипт се провали на машината на друг потребител, може да бъде предизвикателство да се възпроизведе грешката – но регистрационните файлове могат да ни насочат по пътя, който скриптът следва точно преди да се провали в системата на потребителя. Това е изключително полезно при създаването на код, който управлява всички „крайни“ случаи (защо мислите, че софтуерът винаги иска разрешение за изпращане на данни за срив, когато го инсталирате?!)

И накрая, има още една страхотна употреба на регистрационните файлове — като тракери на цикъла на разработка на кода. Представете си, че в рамките на първата седмица от пускането на други потребители вашият скрипт на Python генерира 200 грешки, 500 предупреждения и различни други флагове за регистриране. Тъй като кодът се усъвършенства и става по-стабилен чрез итерация, можем да видим, че честотата на различните проблеми намалява с времето. Това ни дава представа къде се намираме в нашия цикъл на разработка и къде да съсредоточим бъдещите си усилия, като първо се справим с най-често срещаните грешки. И така, нека да започнем:

Основи на регистрирането на Python

Влизането в Python при изпълнение на скриптове от командния ред е много добре документирано, но трябва да започнем с основите, преди да се потопим в триковете и инструментите, които можем да използваме. Първо, когато изпращаме съобщения до регистратора, ние избираме в коя категория се вписва информацията. Те са организирани по нарастваща тежест, от NotSet до Critical, както е показано по-долу. Мощното в тази йерархия е, че когато изведем данните от регистрационния файл, можем да изберем кое нивода докладваме. В този случай регистраторът ще улови всичко на това ниво и по-строго. Например, ако трябваше да изберем ниво на предупреждение, тогава регистраторът ще улови всички съобщения за предупреждение, грешка и критични съобщения. Пълна документация относно стандартите за това кога да се използва всяко ниво можете да намерите тук.

За да изградите това в Python, като съобщенията за регистриране се публикуват в конзолата, изглежда така:

import logging
#create a logger
logger = logging.getLogger()
# to control the reporting level
logger.setLevel(logging.WARNING)
#send two messages to the logger
logging.info("This is an INFO message")
logging.warning("This is a WARNING message")

Тъй като нивото е зададено на logging.WARNING, няма да видим съобщението за ниво info. Задаването на нивото е полезно, тъй като по време на първото кодиране ще изпратим навигационни трохи в DEBUG и INFO, за да сме сигурни, че правим нещата правилно. След това, когато смятаме, че вече не се нуждаем от навигационните трохи, можем да ги елиминираме от дневника и да се съсредоточим върху функционалните проблеми в предупрежденията, грешките и критичните съобщения. Това може да работи ръка за ръка с дебъгера.

За да изведем действителен лог файл вместо командния ред, трябва да настроим регистратора с помощта на basicConfig(), където можем също да зададем нивото, като това:

import logging
logging.basicConfig(filename='my_first_log.log', level=logging.INFO)

За съжаление, този метод няма да работи, когато работите в Jupyter Notebooks. Изобщо. Проблемът се крие в това, че при стартиране Jupyter Notebook стартира IPython сесия, която вече е инициирала регистриране. В резултат на това logging.basicConfig() няма да работи.

Влизане в Jupyter Notebooks

Регистрирането също е особено полезно в Jupyter Notebooks, тъй като дебъгерът може да има някои сериозни „проблеми с функционалността“. Тези проблеми с дебъгерите се избягват чрез използване на подобрения дебъгер в JupyterLab, но ако сте ограничени до Jupyter Notebooks, тогава регистрирането ви дава гъвкавостта да правите това, от което се нуждаете, за да се справите с по-широк набор от задачи за отстраняване на грешки.

Запазване на регистрационния файл във файл

За да експортираме регистрационни съобщения към външен лог файл в Jupyter, ще трябва да настроим FileHandler и да го прикачим към регистратора:

import logging
logger = logging.getLogger()
fhandler = logging.FileHandler(filename='test_log.log', mode='a')
logger.addHandler(fhandler)
logging.warning('This is a warning message')

Важно е да отбележите избора на режим в настройката на FileHandler:

  • „a“— добавяне. Всички нови съобщения се поставят в края на лог файла
  • w’— напишете. Регистрационният файл се изчиства и стартира нов всеки път, когато стартирате скрипта на python

Ако всичко върви добре, трябва да имаме нов файл „test_log.log“ в същата директория като нашия бележник Jupyter.

Форматиране на изходи

Можем също така да подобрим и персонализираме нашите изходи в журнала, за да съдържат повече от текста, който диктуваме в съобщенията за регистриране. С помощта на logging.Formatter() можем да добавим множество полета, които да ни помогнат да разберем повече за съобщенията, като например кога е генерирано съобщението и на кой ред в кода. Ето някои от любимите ми, с пълния списък тук:

  • %(asctime)s — дата/час на записа
  • %(levelname)s — нивото на регистриране (така че не е нужно да го изписвате във вашата грешка)
  • %(name)s — име на използвания регистратор (както в кой файл е генерирал това съобщение)
  • %(lineno)s — номерът на реда, генерирал съобщението
  • %(message)s — разбира се! Самото послание!

За да прикачим нашето форматиране към регистратора, ние го добавяме към файловия манипулатор по следния начин:

formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)s - %(levelname)s - %(message)s')
fhandler.setFormatter(formatter)

Експортиране на форматиран изход към конзолата

Можем също да добавим друг манипулатор, за да покажем това форматиране и в конзолата. Тук осъществяваме достъп до конзолата чрез StreamHandler() с помощта на sys.stdout и го прикачваме към регистратора:

import sys
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(formatter)
logger.addHandler(consoleHandler)

Става още по-добре! Можем също така независимо да регулираме нивата, извеждани от всеки манипулатор. Например, можем да настроим нашата конзола да докладва до нивото на отстраняване на грешки по следния начин: consoleHandler.setLevel(logging.DEBUG)

Важна забележка — Нивото на основния регистър трябва да бъде равно или по-ниско от нивото, дадено на манипулаторите, в противен случай няма да работи, тъй като високото ниво нивото на регистратора задава пода.

Променливи стойности

Можем също така да предаваме повече от статични съобщения към нашия регистратор. Често, ако нашият код връща предупреждения или грешки, ще искаме да знаем какви потребителски стойности са задействали събитието. За щастие можем просто да анализираме стойностите на променливи в нашите съобщения като обикновени низове, използвайки .format().

var1 = 1
var2 = 'cat'
logging.debug('var1: {}; var2: {}'.format(var1, var2))

Сглобяване на всичко

С това, което научихме досега, можем да инициираме нов регистратор в Jupyter Notebooks, да коригираме нивото на отчитане, да коригираме форматирането, извеждане във файл, извеждане към конзолата и коригиране на нивото на всеки изход. Ето една проста моментна снимка на това как изглежда в действие, с конзолата на DEBUG и файла за експортиране на INFO:

И ето какво виждаме в този file_out.log файл:

И въпреки че е хубаво да имаме възможността да експортираме регистрационния файл във файл, понякога искаме да бъдем малко мързеливи и да не превключваме към текстов редактор, за да прочетем изходния файл. За да видим регистрационния файл в Jupyter Notebook, можем просто да прочетем и отпечатаме съдържанието по следния начин:

with open("file_out.log") as log:
    print(log.read())

Подсказки и съвети:

Тъй като регистраторът се настройва автоматично като част от IPython, когато стартираме бележник, определени конфигурации са в конфликт с някои от функционалностите, които съществуват за файлове на python, изпълнявани в конзолата. Един по-специално е използването на функционалността getLogger(__name__). Когато нашите скриптове на Python станат по-сложни, често ще разделяме някои функции на отделни файлове със скриптове, които по-късно се импортират като отделни модули. Можем да предадем всички регистрационни съобщения към общ лог файл, като форматиращият %(name)s ни казва кой python скрипт е генерирал съобщението. Има дори урок как да направите това в Python, работещ от „конзолата“. За съжаление, тази възможност не е незабавно съвместима в Jupyter Notebooks. Все още не съм намерил решение... но засега знам, че използването на __name__ ще попречи на всичко да бъде отпечатано в регистрационния файл или форматирания манипулатор на конзолата.

Когато навлезете по-дълбоко в регистрационните файлове на Python, ще откриете, че можем дори да експортираме регистрационни файлове чрез имейл. Това е много полезно - след като кодът ни бъде разгърнат, можем да наблюдаваме здравето му отдалеч. Дори по-добре, отнема само няколко реда код, за да добавите SMTPHandler. Примерен код може да бъде намерен тук.

Също така, с възможността да зададем нивото за всеки манипулатор, можем да създадем едновременно множество изходни данни за регистрационни файлове, които докладват и различни нива. Например, може да искаме един лог файл, който улавя всяко събитие до нивото на отстраняване на грешки, но може също така да искаме такъв, който е филтриран да докладва само предупреждение и по-горе - това ни дава прост файл за наблюдение за големи проблеми и отделен лог файл на всичко, когато искаме да се потопим по-дълбоко.

Изводи

Регистрационните файлове предлагат чудесен начин за поддържане на качеството и функционалността на нашия код, особено когато пускаме нашите python скриптове за използване от други. Може да помогне за сигнализиране, когато нашият код действа неочаквано и оставя следа от навигационни трохи, за да покаже как и защо нещата са се повредили. Изключително предизвикателство е по време на разработката да се предвиди всеки случай на използване и всеки потребителски вход, който нашите скриптове ще получат — регистрирането ни дава начин непрекъснато да повтаряме и подобряваме устойчивостта на нашия код.

Има няколко разлики при внедряването на журнали в Jupyter Notebooks, но след като разберете модификациите, можете успешно да генерирате наистина полезни журнали. Благодаря за четенето и както винаги, целият код, показан по-горе, може да бъде достъпен в моя Github тук.