Что такое лог программы файл с инструкцией по ее запуску
Написали программу с применением нейросети, но она выдает кучу ошибок? Где потом искать эти ошибки? Как структурировать полученную информацию?
Помочь с поиском ошибок может логирование — группа методов для сбора и сохранения информации о работе программы. Всю интересующую нас информацию мы можем записывать в текстовые файлы и потом их обрабатывать. К примеру, вот таким образом в случае деления на 0:
Тогда консоль нам покажет следующее:
А в логе с файлом увидим:
Конечно, реализовать самостоятельно такой способ — просто, и многие этим пользуются. Но у него тоже есть минус: если проект большой, надо не забывать придерживаться определенного формата их заполнения.
Но Python же один из самых дружелюбных языков.) Разработчики уже подумали о нас и создали хорошую библиотеку «logging».
Для работы с ней нам необходимо импортировать библиотеку logging и указать основные параметры. Всего параметров для настройки 6.
Так же существует 5 уровней логирования информации: от DEBUG (отладка) до critical (критичные ошибки).
На этом можно закончить с теорией, и перейдем к практике.
Теперь мы будем логировать нашу функцию деления уже с учетом модуля logging и попытаемся собрать максимум информации о ее работе. Давайте рассмотрим код нашего простенького скрипта, но уже с учетом использования логов.
Как мы видим он немного увеличился в размерах, но при этом, для записи также использует лишь одна строчка.
В начале мы создаем переменную, в которой указываем идентификатор лога. Это нужно для того, к примеру, чтобы мы не путались из какого скрипта записываем лог. Это делается строкой -
После – мы указываем уровень лога и имя файла, в который мы будем его записывать:
format_log = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_name.setFormatter(format_log) logger.addHandler(file_name)
Вот, на этом и все) В дальнейшем мы можем использовать наш логгер простым вызовом logger.info('Division') или в случае описания ошибки logger.error(error_text). По окончанию работы скрипта данные будут сохранены в файл 'data.log'.
А теперь посмотрим, что мы получили в логе:
Использование модуля «logger» на маленьких программах, может, и не заметно, а вот на больших польза становится очевидна. Особенно, если эти логи в дальнейшем нуждаются в обработке, например, для Process Mining-а.
Вот таким простым способом мы с вами научились делать понятную и удобную запись логов в нашем скрипте!
Решая различные компьютерные задачи, можно не раз столкнуться с таким понятием как лог (с англ. log). Лог какой-то программы. Давайте попробуем разобраться что это такое и для чего это нужно.
Log (с англ. журнал). У большинства программ, которые установлены на вашем компьютере, есть этот самый журнал.
Журнал - это специальный текстовый файл, в который программа может вносить какие-то записи.
Т.е. это такой же обычный файл, который мы с вами можем создать на компьютере. Таким же образом программа работая на компьютере, может создать этот файл и вносить туда программным образом какие-то текстовые пометки.
Зачем же программе вести какие-то записи, какой-то журнал?
Дело в том, что если мы с вами будем следить за человеком, который работает на компьютере, мы можем сказать, что этот человек делал в конкретный момент времени, какие программы он запускал, какие ошибки он совершал при работе на компьютере и.т.д.
Но, если мы говорим о компьютерной программе, здесь все не так ясно. Все действия, которые производит программа, они скрыты от взгляда обычного пользователя. Они обычно происходят с такой большой скоростью событий, что человеческий глаз просто не успеет за все этим уследить.
Для того, чтобы отслеживать состояние какой-то программы. Что делала программа в какой-то конкретный момент времени, какие при этом возникали ошибки, кто с этой программой взаимодействовал и др. вопросы. Все события, которые происходили с этой программой, эта программа может записывать в специальный журнал, так называемый лог-файл.
Лог файлов для программы может быть несколько. В зависимости от назначения может быть так называемый access_log - это журнал, где фиксируются все взаимодействия пользователей с этой программой, что они делали и.т.д.
В лог файле может множество записей. Каждая текстовая строка - это одно взаимодействие с программой.
В каждой записи содержится информация о том, что происходило с программой и когда это происходило.
Также можно часто встретить так называемый error_log - это лог тех ошибок, которые возникали при работе с программой. В этом логе можно увидеть код ошибки, которая произошла в программе, когда эта ошибка произошла, каким пользователем операционной системы эта ошибка была вызвана и.т.д.
Давайте подведем итог, что такое лог и зачем он нужен. Это текстовый файл, в который программа записывает какие-то события, которые с ней происходят. Благодаря этим событиям мы можем получить какую-то дополнительную информацию, что происходило с этой программой в какой-то определенный момент времени, получить отладочную информацию, чтобы легче устранить какую-то ошибку.
В общем лог - это бесценная информация, который может воспользоваться любой пользователь компьютера, чтобы узнать что и в какой момент времени происходило с программой.
Лог - это первоисточник, в который нужно заглядывать если ваша программа работает с каким-то ошибками и не так, как нужно.
Надеюсь, что стало понятнее что такое лог-файл и зачем он нужен и вы теперь будете использовать этот журнал в своей работе.
К написанию данной статьи меня сподвиг опыт работы с проектами в которых либо отсутсвоала система логирования как таковая, либо присутствовало ее жалкое подобие, по которому было невозможно ни определить проблему, ни даже примерное место ее появления. Под катом немного теории и непосредственно практическая реализация класса для записи логов на С++.
Мне часто приходилось сталкиваться с полным отсутствием понимания назначения логирования в приложениях, хотя система логирования это далеко не второстепенная фаза в разработке проекта. Но, зачастую, люди это понимают уже на стадии сдачи поекта, когда введение полноценной системы логирования — процесс достаточно затратный и как результат, оставляют все как есть. Что в результате имеем? А имеем мы систему, в которой любая проблема у заказчика превращается в головную боль разработчика, т.к невозможно восстановить причины возникновения проблемы у заказчика, т.е у разработчика есть только описание проблемы от человека, у которого эта проблема воспроизвелась. К сожалению, мы живем не в идеальном мире и как правило описания проблемы носит малоинформативный характер, т.к пользователи системы не являются грамотными тестерами и не могут описать проблему подробно(есть конечно приятные исключения, но их мало). Особенно остро проблема логирования стоит когда нет возможности воспроизведения проблемы на своей стороне.
- Возможность быстрого определения причин проблемы, без многочасового сидения в дебагере, пытаясь определить проблему.
- Возможность «удаленной отладки», т.е возможность отлаживать приложение по имеющимся логам от заказчика(очень полезно когда нет возможности тестировать свое приложение без участия заказчика)
- Перекладывания части работы на техническую поддержку. Т.е support обучается типовым проблемам, секциям в логах и т.д. После чего они сами в состоянии будут определить лежащие на поверхности проблемы, не отвлекая каждый раз разработчиков по пустякам.
- Выбор имени лог файла
- Выбор директории где будут храниться лог файлы
- Включение\выключение межпроцессорной синхронизации
- Ограничение размера лог файла
- Ограничение числа лог файлов, которые могут быть сохранены в директории
- Задание процедуры, котрая будет вызвана при ошибке в записи в лог файл, или при его открытии
- Задание процедуры, которая будет изменять каждую строчку при записи, так, как необходимо пользователю. Удобно, когда Вы не хотите, чтобы кто-то посторонний читал Ваш лог.
- Уровень логов, которые в данный момент могут быть выведены(всего в классе представлен 3 уровня: Обычный, Расширеный и Дебажный). Т.е отсекает все логи приоритет которых выше порога.
namespace smart_log
typedef void (*ErrorHandler)( const std:: string &);
typedef const std:: string (*Obfuscater)( const std:: string &);
//This type provides constant which set the writing log mode.
//If a current mode is bigger then methods mode the writing will be ignored.
//It needs for log flood control
enum LogLevel;
struct LogParameters
std:: string m_strLogFileName;
//Pointer to a function which does appropriate manipulations in case of a log corruption. Set to 0 if it doesn't need.
ErrorHandler m_pErrorHandler;
//Pointer to a function which obfuscates each string writing to a log file. Set to 0 if it doesn't need.
Obfuscater m_pObfuscater;
size_t m_uiLogMaxSize;
unsigned short m_siMaxSavedLogs;
//Is the thread synchronization needed?
bool m_bIsMultiThreaded;
//Indicates whether log will be saved or not. If this flag is set then log will be saved when its size exceed m_uiLogMaxSize.
//Use m_siMaxSavedLogs to control how many log files will be saved. If the current number of log files has reached the m_siMaxSavedLogs then
//new saved log would replace the oldest one.
bool m_bIsSaveLog;
LogLevel m_xLogLevel;
//Path to the log location. It will be created if no exists.
boost::filesystem::path m_xLogSavedPath;
>;
class Logger
//---------------------------------------Data
size_t m_uiCurrentLogSize;
short int m_siCurrentSavedLogs;
LogParameters m_xParameters;
std::ofstream m_xOfstream;
boost::interprocess::named_mutex m_xMutex;
//File name with full path in which it will be create
boost::filesystem::path m_xFullFileName;
//--------------------------------------Internal methods
private :
//Common method for message writing
void WriteLog(LogLevel xLevel, const std:: string & strMessage);
void HandleLogSizeExcess();
std:: string Timestamp();
void CreatePath(boost::filesystem::path xPath);
//--------------------------------------Interface
public :
Logger( const LogParameters& xParameters);
* This source code was highlighted with Source Code Highlighter .
namespace fs = boost::filesystem;
namespace multiprocess = boost::interprocess;
using namespace smart_log;
void Logger::WriteLog(LogLevel xLevel, const std:: string & strMessage)
try
multiprocess::scoped_lock xLock;
if (m_xParameters.m_bIsMultiThreaded)
xLock = multiprocess::scoped_lock(m_xMutex, multiprocess::defer_lock_type());
xLock. lock ();
>
CreatePath(m_xParameters.m_xLogSavedPath);
//Don't do anything if the current log level less then the message demands
if (xLevel > m_xParameters.m_xLogLevel)
return ;
if (m_uiCurrentLogSize + strMessage.length() > m_xParameters.m_uiLogMaxSize)
HandleLogSizeExcess();
if ( !m_xOfstream.is_open() )
m_xOfstream.open( (m_xFullFileName.file_string() + "-" + Timestamp() + ".log" ).c_str() );
//Make an output string
std::ostringstream xStream;
xStream const std:: string & xFinalString = xStream.str();
if (m_xParameters.m_pObfuscater)
m_xOfstream else
m_xOfstream m_uiCurrentLogSize += strMessage.length();
>
catch (std::ofstream::failure xFail)
if (m_xParameters.m_pErrorHandler)
m_xParameters.m_pErrorHandler( "Problem with a file creation or writing to the already existing file." );
else
throw ;
>
void Logger::HandleLogSizeExcess()
if (m_xOfstream.is_open())
m_xOfstream.close();
//Goes through the log directory and finds files which looks like "m_strLogFileName-*Timestamp*"
fs::directory_iterator xEndIterator;
std:: set xFileList;
for ( fs::directory_iterator it(m_xParameters.m_xLogSavedPath); it != xEndIterator; ++it )
std:: string xCurrentFile = it->path().filename();
if ( fs::is_regular_file(it->status()) )
if (xCurrentFile.find(m_xParameters.m_strLogFileName) != std:: string ::npos)
xFileList.insert(xCurrentFile);
>
//If the log files number exceeds the m_siMaxSavedLogs then keep on removing
//files until current files number won't be less then threshold
if (m_xParameters.m_siMaxSavedLogs)
if (xFileList.size() >= m_xParameters.m_siMaxSavedLogs)
for (std:: set ::iterator it = xFileList.begin(); it != xFileList.end()
&& xFileList.size() >= m_xParameters.m_siMaxSavedLogs;)
fs::remove(fs::path(m_xParameters.m_xLogSavedPath) /= *it);
xFileList.erase(it++);
>
>
>
else //Save files property is turned off hence remove all existing log files
for (std:: set ::iterator it = xFileList.begin(); it != xFileList.end();)
fs::remove(fs::path(m_xParameters.m_xLogSavedPath) /= *it);
xFileList.erase(it++);
>
>
m_uiCurrentLogSize = 0;
//Create a new file
m_xOfstream.open( (m_xFullFileName.file_string() + "-" + Timestamp() + ".log" ).c_str() );
>
void Logger::CreatePath(fs::path xPath)
try
//If a directory doesn't exist then try to create full path up to the required directory
if ( !fs::exists(m_xParameters.m_xLogSavedPath) )
fs::path xTmpPath;
for (fs::path::iterator xIt = m_xParameters.m_xLogSavedPath.begin();
xIt != m_xParameters.m_xLogSavedPath.end();
++xIt)
xTmpPath /= *xIt;
if ( !fs::exists(xTmpPath) )
fs::create_directory(xTmpPath);
>
>
>
catch (fs::basic_filesystem_error)
<
if (m_xParameters.m_pErrorHandler)
m_xParameters.m_pErrorHandler( "Problem with a directory creation" );
else
throw ;
>
* This source code was highlighted with Source Code Highlighter .
Пример создания удобного интерфейса к классу:
class LogInstance
static boost::scoped_ptr m_spLogger;
public :
static const boost::scoped_ptr& GetLog()
if (!m_spLogger)
smart_log::LogParameters xParams;
xParams.m_bIsMultiThreaded = true ;
xParams.m_pErrorHandler = 0;
xParams.m_pObfuscater = 0;
xParams.m_siMaxSavedLogs = 0;
xParams.m_strLogFileName = "log_file" ;
xParams.m_uiLogMaxSize = 8192;
xParams.m_xLogLevel = smart_log::eNormal;
xParams.m_xLogSavedPath = "./log/log/log" ;
m_spLogger.reset( new smart_log::Logger(xParams));
>
return m_spLogger;
>
* This source code was highlighted with Source Code Highlighter .
Надеюсь этим постом я сподвигну людей не использующих логи, на их использование и надеюсь моя реализацию будет им полезна. Удачной всем отладки :)
Мой опыт разработки в основном строится вокруг разнообразных сетевых cервисов под Windows и Linux. Я обычно стремлюсь добиться максимальной кроссплатформенности вплоть до бинарной совместимости. И конечно, накопилось некоторое количество стабильных решений связанных с логированием.
Топик написан как продолжение к этой статье и будет полезен в первую очередь начинающим программистам.
Итак, начну со своих дополнений к предыдущей статье.
Я как и автор пользуюсь NLog'ом и разумеется широко использую его особенности. Конечно, после
их реализации в любом другом логгере, нижеописанную практику можно применять и у них.
Кстати, log4net продолжает развиваться.
- Наблюдение/перезагрузка файла конфигурации — это уже не просто полезное, а весьма необходимое умение.
- Минимальное вмешательство в выключенном состоянии.
Под капотом NLog
Сразу обсудим полезность второй фичи.
Часто, при разработке кода, возникает необходимость посмотреть значение какой либо переменной в процессе выполнения. Обычно, используют дебаггер и останавливают программу в интересующем месте. Для меня же, это явный признак, что этом месте будет полезен Trace вывод. В комплекте с юнит-тестами мы сразу получаем развертку этой переменной во времени и протокол для сравнения с тестами в других условиях. Таким образом, дебаггером я практически не пользуюсь.
Очевидно, что в боевом применении, даже выключенное подробное логирование, может мешать как скорости выполнения так и параллельности.
Исходный код класса можно посмотреть тут.
Что и как логировать
Следует придерживаться правил:
В целом, при выводе в лог, всегда отмечайте, то количество потенциально лишних вычислений, которые потребуются для случая когда лог отключен.
Простой пример (фрагмент некоторого класса):
private static Logger Log = LogManager. GetCurrentClassLogger ( ) ;
public string Request ( string cmd, string getParams )
<
Uri uri = new Uri ( _baseUri, cmd + "?" + getParams ) ;
Log. Debug ( "Request for uri:`'" , uri ) ;
HttpWebRequest webReq = ( HttpWebRequest ) WebRequest. Create ( uri ) ;
webReq. Method = "GET" ;
webReq. Timeout = _to ;
string respText ;
try
<
string page ;
using ( WebResponse resp = webReq. GetResponse ( ) )
using ( Stream respS = resp. GetResponseStream ( ) )
using ( StreamReader sr = new StreamReader ( respS ) )
page = sr. ReadToEnd ( ) ;
Log. Trace ( "Response page:`'" , page ) ;
return page ;
>
catch ( Exception err )
<
Log. Warn ( "Request for uri:`' exception: " , uri, err. Message ) ;
throw ;
>
>
Вообще, в текущем обработчике ошибок полезно детализировать контекст который к привел к исключению и специфичные особенности исключения. В примере было бы полезно вывести поле Status для случая WebException.
Гарантии сохранности лога
Несмотря на некоторые возможности NLog по авто записи логов, нет гарантии сохранности лога при завершении процесса.
Первое, что следует сделать, это обработать событие AppDomain.UnhandledException. В нем следует записать в лог полную информацию об ошибке и вызвать LogManager.Flush(). Обработчик этого события использует тот же поток, который и вызвал исключение, а по окончании, немедленно выгружает приложение.
private static readonly Logger Log = LogManager. GetCurrentClassLogger ( ) ;
public static void Main ( string [ ] args )
<
AppDomain. CurrentDomain . UnhandledException += OnUnhandledException ;
( . )
LogManager. Flush ( ) ;
>
static void OnUnhandledException ( object sender, UnhandledExceptionEventArgs e )
<
Log. Fatal ( "Unhandled exception: " , e. ExceptionObject ) ;
LogManager. Flush ( ) ;
>
Кроме того, следует вызывать LogManager.Flush() везде, где потенциально возможно завершение процесса. В конце всех не фоновых потоков.
Сколько логировать
Серьезная проблема для разработчика. Всегда хочется получать больше информации, но код начинает выглядеть очень плохо. Я руководствуюсь следующими соображениями.
- Trace — вывод всего подряд. На тот случай, если Debug не позволяет локализовать ошибку. В нем полезно отмечать вызовы разнообразных блокирующих и асинхронных операций.
- Debug — журналирование моментов вызова «крупных» операций. Старт/остановка потока, запрос пользователя и т.п.
- Info — разовые операции, которые повторяются крайне редко, но не регулярно. (загрузка конфига, плагина, запуск бэкапа)
- Warning — неожиданные параметры вызова, странный формат запроса, использование дефолтных значений в замен не корректных. Вообще все, что может свидетельствовать о не штатном использовании.
- Error — повод для внимания разработчиков. Тут интересно окружение конкретного места ошибки.
- Fatal — тут и так понятно. Выводим все до чего дотянуться можем, так как дальше приложение работать не будет.
Боевое развертывание
Предположим, разработка дошла до внедрения.
Отложим вопросы ротации логов, размера файлов и глубины истории. Это все очень специфично для каждого проекта и настраивается в зависимости от реальной работы сервиса.
Остановлюсь только на смысловой организации файлов. Их следует разделить на 3 группы. Может потребуется развести логи модулей в разные файлы, но дальше я все равно буду говорить об одном файле для каждой группы.
- Группа Info, с соответствующим уровнем для всех источников. Это информация для администратора. Здесь могут быть следующие вещи: когда приложение стартовало, правильно ли вычитаны конфиги, доступны ли требуемые сервисы, и т.д. Его основное свойство: файл изменяет размер только при перезагрузке приложения. В процессе работы, файл расти не должен. Это поможет обеспечить автоматизированный внешний контроль успешности запуска приложения. Достаточно проверить отсутствие в файле ключевых слов Error и Fatal. Проверка всегда будет занимать предсказуемо малое время.
- Группа Warning. Это тоже информация для администратора. Этот файл при нормальной работе должен отсутствовать или быть пустым. Соответственно мониторинг его состояния сразу укажет на сбои в работе. Гибко настроив фильтры по разным источникам, можно подобрать достаточно точный критерий, когда вообще следует обратить внимание на сервис.
- Группа Наблюдение. Как правило в ходе внедрения выделяются некоторые проблемные модули. Информация от них в детализации Debug как раз и направляется сюда.
Если приложение успешно внедрено, то в работе остаются только первые две группы.
Расследование сбоев
Когда работающий сервис подает признаки ошибки, то не следует его пытаться сразу перезагружать. Возможно нам «повезло» поймать ошибки связанные с неверной синхронизацией потоков. И не известно сколько в следующий раз ждать ее повторения.
В первую очередь следует подключить заготовленные заранее конфиги для группы наблюдения. Как раз это и должен позволять делать приличный логгер. Когда мы получили подтверждение о том, что новая конфигурация успешно применена, то пытаемся опять спровоцировать сбой. Желательно несколько раз. Это обеспечит возможность для его воспроизведения в «лабораторных» условиях. Дальше уже работа программистов. А пока можно и перезагрузиться.
Вывод в лог желательно сделать асинхронным.
Пример, боевой настройки.
Чего с логгером делать не следует
Логгер должен быть простым и надежным как молоток. И у него должна быть четко очерчена область применения в конкретном проекте. К сожалению, разработчиков часто трудно удержать. Паттерны проектирования, это в основном полезно, но не этом случае. Достаточно часто стал замечать предложения выделить для логгера обобщенный интерфейс (пример) или реализовать обертку в проекте, чтобы отложить муки выбора NLog vs log4net на потом.
Что бы ни было тому причиной, надо точно помнить, что в первую очередь, такие удобства напрочь убивают компилятору возможность оптимизации.
Не стоит напрямую выводить информацию логгера пользователю, даже с фильтрами. Проблема в том, что эта информация зависит от внутренней структуры программы. Вряд ли вы в процессе рефакторинга пытаетесь сохранить вывод логгера. Наверное, здесь стоит просто задуматься и разделить функционал. Возможно, в проекте просто требуется еще один уровень логирования. В этом случае как раз и уместна будет обертка над логгером.
Чего же мне еще не хватает в NLog?
NLog, Log4Net, Enterprise Library, SmartInspect.
Разнообразные сравнения логгеров между собой, упускают одну важную деталь.
Важно сравнивать не только ограничения/возможности, но и возможность быстро добавить свои «хотелки».
Журналирование является основным источником информации о работе системы и ее ошибках. В этом кратком руководстве рассмотрим основные аспекты журналирования операционной системы, структуру каталогов, программы для чтения и обзора логов.
Основные лог файлы
Все файлы журналов, можно отнести к одной из следующих категорий:
Большинство же лог файлов содержится в директории /var/log .
Для каждого дистрибутива будет отдельный журнал менеджера пакетов.
- /var/log/yum.log — Для программ установленных с помощью Yum в RedHat Linux.
- /var/log/emerge.log — Для ebuild -ов установленных из Portage с помощью emerge в Gentoo Linux.
- /var/log/dpkg.log — Для программ установленных с помощью dpkg в Debian Linux и всем семействе родственных дистрибутивах.
И немного бинарных журналов учета пользовательских сессий.
- /var/log/lastlog — Последняя сессия пользователей. Прочитать можно командой last .
- /var/log/tallylog — Аудит неудачных попыток входа в систему. Вывод на экран с помощью утилиты pam_tally2 .
- /var/log/btmp — Еже один журнал записи неудачных попыток входа в систему. Просто так, на всякий случай, если вы еще не догадались где следует искать следы активности взломщиков.
- /var/log/utmp — Список входов пользователей в систему на данный момент.
- /var/log/wtmp — Еще один журнал записи входа пользователей в систему. Вывод на экран командой utmpdump .
И другие журналы
Так как операционная система, даже такая замечательная как Linux, сама по себе никакой ощутимой пользы не несет в себе, то скорее всего на сервере или рабочей станции будет крутится база данных, веб сервер, разнообразные приложения. Каждое приложения или служба может иметь свой собственный файл или каталог журналов событий и ошибок. Всех их естественно невозможно перечислить, лишь некоторые.
В домашнем каталоге пользователя могут находится журналы графических приложений, DE.
- ~/.xsession-errors — Вывод stderr графических приложений X11.
Чем просматривать — lnav
Недавно я обнаружил еще одну годную и многообещающую, но слегка еще сыроватую, утилиту — lnav, в расшифровке Log File Navigator.
Установка пакета как обычно одной командой.
Навигатор журналов lnav понимает ряд форматов файлов.
- Access_log веб сервера.
- CUPS page_log
- Syslog
- glog
- dpkg.log
- strace
- Произвольные записи с временными отметками
- gzip, bzip
- Журнал VMWare ESXi/vCenter
Что в данном случае означает понимание форматов файлов? Фокус в том, что lnav больше чем утилита для просмотра текстовых файлов. Программа умеет кое что еще. Можно открывать несколько файлов сразу и переключаться между ними.
Программа умеет напрямую открывать архивный файл.
Кроме этого поддерживается подсветка синтаксиса, дополнение по табу и разные полезности в статусной строке. К недостаткам можно отнести нестабильность поведения и зависания. Надеюсь lnav будет активно развиваться, очень полезная программа на мой взгляд.
Читайте также: